SharePoint Reflection – Working with Content Types

For the past several months, I’ve been experimenting with ways to use .NET reflection with the SharePoint API.  As I’ve mentioned before, I believe that .NET attributes can not only make it easier to build great SharePoint applications, but can also provide a foundation for building better development tools.

In this series of posts about the evolving SharePoint Reflection Framework, I’ll continue to share the results of my research and I’ll include links to sample code directly within each post.  As the code matures, I’ll consider making it available on CodePlex so that others may contribute.  For now, let’s just treat it as a “thought experiment” as my friend Andrew Connell likes to call it.

The sample code and the framework assembly can be downloaded here:

JohnHolliday.SharePoint.ContentTypes.zip

There is also a short screencast (27 minutes) that shows how to use the framework that you can download from here:

SPRF-ContentTypes.zip

Now let’s take a closer look at how we can apply .NET attributes to simplify working with content types.

Some have asked, and you might also be wondering “Why do we need this?   Why not just use CAML to declare the content types and then simply publish them using a feature?  Why go to all the trouble of using this non-traditional approach?”

Well, there’s nothing wrong with the traditional CAML-based approach.  There is ample support for it and with a few simple tools, it’s pretty straight-forward once you get the hang of it.  But there are a number of limitations with the “code + markup” paradigm when it comes to reusability and extensibility.  The markup is “brittle” in the sense that subtle mistakes are hard to find.  True, you can preload the schema to help you remember what attributes go where, but there is no true intellisense and no strong typing to prevent those mistakes.  If you want to extend an existing content type, you have to  visit multiple files and switch back and forth between your event handler code and the markup.  You have to spend a good deal of time making sure that the code matches the properties that have been defined in the markup.

The unfortunate reality is that working directly with XML still requires a significant paradigm shift in the way most developers think.  With all the buzz about improving the SharePoint “developer experience”, I keep thinking there must be a better way.

There are several goals for this experiment:

  • We want to make it easier to declare content types in code.
  • We want to eliminate the need to work directly in CAML or XML.
  • We want to use the same coding idioms we are used to when working with other code.
  • We want to make it easier to write custom event receivers for our content types.
  • We want to make it easier to associate sub-components like document templates and XML documents with our content types.
  • We want to be able to build libraries of reusable components that can be leveraged across multiple projects easily.
  • We want to enable DRM for content type components at the assembly level so that developers can protect their intellectual property.

The SharePoint Reflection Framework

For this experiment, we’ll construct a content type for an expense report in C#.  But we also want to work with the expense report content type in the same way we might work with other C# classes.  For instance, we might want to create a library of reusable content type classes for use in financial solutions.

To achieve this, we need to have a set of attributes that we can attach to a class to “declare” the content type and its properties.

We can start simply by deriving a ContentType class from System.Attribute.  To control where and how it is used, we can mark it so that it can only be applied to classes and only once per class.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ContentType : Attribute
{
    public string Name { get; set; }    
    public string BaseType { get; set; }    
    public string Description { get; set; }   
    public string DocumentTemplate { get; set; }  
    public string Group { get; set; }   
    public bool Hidden { get; set; }   
    public bool Sealed { get; set; }
    
    // ...
}
Within this attribute class, we have properties that specify the various well-known components of a content type, such as its name, group, base type, etc.  These properties then show up as named properties of the attribute whenever it is applied to another class used to declare the content type itself.  For our expense report experiment, that class might look something like this:
namespace JohnHolliday.SharePoint.SampleContentTypes
{
    [
        ContentType (
            Name = "Expense Report", 
            BaseType = "Document", 
            Description = "A worksheet for recording expenses.", 
            Group = "SharePoint Reflection Framework Samples", 
            Hidden = false )
    ]
    public class ExpenseReport   
    { 
    }
}

Notice that we are basically specifying the same properties we would have to add to a <ContentType> element in CAML.  The difference is that here we get strong typing and intellisense support.  Notice also that we don’t have to worry about constructing the content type identifier.  Instead, we just supply the base type and let the framework handle the details.

Next, within the content type declaration, we need to specify the fields that make up the type.  Here again, .Net reflection comes to our aid to make things easier.  What we really want is to declare the fields of the content type in the same way that we might declare properties for any C# class.  So, if we wanted to add a description field to the expense report content type, we would naturally want to do something like this:

    [ContentType(Name = "Expense Report",
        BaseType = "Document",
        Description = "A worksheet for recording expenses.",
        Group = "SharePoint Reflection Framework Samples",
        Hidden = false)]
    public class ExpenseReport
    {
        private string m_description = string.Empty;

        [FieldRef("Description")]
        public string Description
        {
            get { return m_description; }
            set { m_description = value; }
        }
    }

Here, we can simply make use of a second attribute included in the framework to markup the properties we want to map to SharePoint fields.  The FieldRef attribute is provided for this purpose.  The framework supplies the necessary code to examine the ExpenseReport class, locate the mapped properties, determine their field types and link them automatically to the content type on creation.  For instance, if we add a DateTime property to the ExpenseReport class, we want it to surface in SharePoint not as text, but as a date field.   Similarly, the other field types can be automatically determined from the underlying property type.

Enumerations

We can extend this idea further to include built-in support for CHOICE fields if an enumerated type is mapped to a field.  For example, we might need to declare a ProjectTypeEnum enumeration and then map it to a ProjectType property as shown below.

        public enum ProjectTypeEnum
        {
            Consulting, Training, Programming, Sales
        }
        private ProjectTypeEnum m_projectType;

        [FieldRef("ProjectType",
            DisplayName = "Project Type",
            Required = true,
            Description = "The project type for which expenses are being submitted.",
            Group = "Sales")]
        public ProjectTypeEnum ProjectType
        {
            get { return m_projectType; }
            set { m_projectType = value; }
        }

In this case, the framework detects that the underlying data type of the property is an enumerated type and it then generates the appropriate choices automatically, as shown here.

Choice Fields

We can do a lot more with field references, but let’s move on to the question of how to create the actual content type within the SharePoint environment.

Creating Instances

Typically, we’ll be writing a FeatureActivated feature receiver or building a command-line utility, and we need to create a content type as part of the solution.  Ideally, we’d like to end up with an instance of SPContentType that we can work with.  So we really only need the framework to handle the dirty work of talking to the SharePoint API and then returning a properly constructed SPContentType object.  One way to implement this pattern is to provide a factory method on the ContentType attribute class that takes a reference to our implementation class and then gives us back an SPContentType object.  For the expense report content type, we might call it like this:

            using (SPSite site = new SPSite("http://contoso.com"))
            {    
                using (SPWeb web = site.OpenWeb()) 
                {       
                    // Create the expense report content type.        
                    SPContentType ctExpenseReport 
                        = ContentType.Create(web, typeof(ExpenseReport)); 
                    if (ctExpenseReport != null) 
                    {            
                        // do something with it        
                    }    
                }
            }

Note that the ExpenseReport class is completely independent of the SPContentType object it was used to create.  It can be implemented in a separate assembly.  Using this approach, we can easily build libraries of content types that can be reused across multiple solutions.  But there are still a couple of additional requirements we need to fulfill.  Namely, how do we handle item event receivers?  And what to do about the nested sub-components of a content type, such as document templates and XML documents?  These are critical requirements, because we want to apply the same coding paradigm we are used to for other C# classes.  We don’t want to have to revert to using XML markup in order to specify event receivers and other elements.

Item Event Receivers

To specify event receivers for a content type in code, we need to add the appropriate entries to the EventReceivers collection of the SPContentType object.  When working with XML markup, this is done by creating a special XML document that specifies which event we want to capture and the assembly and class in which the event receiver method is implemented.

To deal with event receivers in our ContentType attribute, we can take advantage of the fact that SharePoint provides an abstract class called SPItemEventReceiver that declares all of the event receiver methods.  By simply deriving our ExpenseReport class from SPItemEventReceiver, we get the correct method declarations in the base class and then we can selectively override the ones we want.  Instead of telling the framework explicitly which methods we are implementing as we would have to do with CAML, we can let the framework do the work for us and infer which methods have been implemented by examining the class via reflection.  So all we have to do to handle the ItemAdded event, for example, is add the derivation and override the ItemAdded method as shown in the following code.

    [
        ContentType(Name = "Expense Report",
            BaseType = "Document",
            Description = "A worksheet for recording expenses.",
            Group = "SharePoint Reflection Framework Samples",
            Hidden = false)
    ]
    public class ExpenseReport : SPItemEventReceiver
    {
        // ...properties    
        public override void ItemAdded(SPItemEventProperties properties)
        {
            base.ItemAdded(properties);
            string message = string.Format(
                "Expense report added to list: {0}", properties.ListTitle);
            EventLog.WriteEntry("SharePoint Reflection", message);
        }
    }

Still, no CAML.  We are working entirely within a C# class and we have captured all of the information needed to create the content type and to install an event receiver for the ItemAdded event.  The framework looks at the ExpenseReport class and sees that only one of the abstract classes was implemented, so it creates an event receiver for that method only.  It knows which assembly the class is declared in, so it automatically assigns the assembly and class names.  If we want to add an event receiver for another event, such as ItemUpdated, we can simply add another override and implement the method.  The framework automatically registers it for us.

Document Templates and XML Documents

The thing about document templates and other kinds of files that we might need to associate with a content type is that they are files.  Ideally, we’d like to work with them as files without having to jump through hoops just to get SharePoint to recognize them.  Fortunately, Visual Studio has a nice feature that lets us access and work with files easily from within our code.  These are embedded resources.  If we change the build action for any given file within our project to EmbeddedResource we can reference it using a path expression of the form <folder name>.<sub folder name>.<…>.<filename>.<extension>.  So if we have a project folder named “DocumentTemplates” and a file in that folder named “ExpenseReport.xlsx”, and both are part of a project named “SampleContentTypes”, we could refer to the resource using the path “SampleContentTypes.DocumentTemplates.ExpenseReport.xlsx”.

By extending the ContentType attribute to recognize and locate embedded resources, we can easily add document templates and other resources directly to our class declaration without having to worry about how they actually get copied into SharePoint.  Here again, we can delegate that responsibility to the framework and just focus on the content type implementation.

For consistency and to distinguish document templates specified as embedded resources from those specified with legitimate urls, I’ve added a “res://” prefix which the framework recognizes.  It then treats the url as a reference to an embedded resource and copies the file into the proper location when the content type is created.  Since it already knows where the resource is located, we can drop the assembly name and just specify the assembly-relative path to the resource file.  We end up with a declaration that looks like this:

    [
        ContentType(Name = "Expense Report",    
            BaseType = "Document",    
            Description = "A worksheet for recording expenses.",    
            Group = "SharePoint Reflection Framework Samples",    
            Hidden = false,   
            DocumentTemplate = "res://DocumentTemplates.ExpenseReport.xlsx")
    ]
    public class ExpenseReport : SPItemEventReceiver
    {   
        //...
    }

Future Directions

As you can see, there is a lot of promise to this approach, but we’ve only begun to scratch the surface.  I’m currently working on one-way data binding of list items to properties in the implementation class whenever an event receiver method is called.  This would essentially let you reference the mapped properties just like other properties in your event receiver methods.  It would be one-way only because there might be times when you don’t want those properties to get copied back into the list item automatically.  For that, you can always call into the SharePoint API.

There is a lot more we can do with content types as well as with other SharePoint objects that can really make our lives easier as SharePoint developers.  Give the code a spin and tell me what you think.  In the coming weeks, look for additional posts that will go into more capabilities of the framework.

Stay tuned.

15 comments

  1. Yes Tom, in fact it’s already done. I’m working on a couple of additional enhancements before releasing it. Stay tuned for future posts about CAML generation.

  2. Very nice. I looked at doing something similar a while ago but never got the time. <br /><br />Have you considered using the framework to generate the standard xml files rather than using the custom deployment code?

  3. Thank you for this post. I’ve been waiting for it since I attended your sessions at ODC. I will give the Framework a spin in the next few weeks.

  4. Hi John,<br /><br />Excellent post. Interesting thought, but I am with Tom in that we should use the web interface as our designer, and then extend the stsadm with functions such as -getlistdefintion, -getcontenttypes, etc. to generate the CAML for us. I have almost completed the list defintion code which handles Lookups, Views, Content Types, etc. I was also going to create a project on CodePlex and post my code up there, but I would love to know what you have done. Drop me a line to discuss. Again, a brilliant idea!

  5. Thanks Ryan. One problem with using the UI to define a content type is that there is additional functionality that the UI can’t “reach”, like specifying event receivers and adding custom XML. Building on the reflection approach, I’ve created a command-line utility similar to STSADM that extracts the CAML from a compiled assembly. I’ve also created an STSADM “-o reflect” command that does the same thing. That way, you get all the benefits of working in code without losing the ability to work in CAML.

  6. This is good. But needed some more information to make easy for others to learn about Share Point Reflection

  7. John, can you post more details regarding your session from ODC “Building Document Management Solutions Using Windows SharePoint Services 3.0 Content Types”, and perhaps some code samples

  8. Hi John,<br /><br />Just read your post and watched the screen cast. All is I can see is great. <br /><br />I have two questions. <br /><br />1. When will be releasing this project?<br /><br />2. Do you any attributes that can be applied to enum entries in order to display more user friendly names ie with spaces between words etc.<br /><br />3. Do you have beta that I can use in the meantime … I am starting a new project and would like to use your method rather than XML.<br /><br />Keep up your great work.<br /><br />Kind Regards,<br />Tarek<br /><br />

  9. Hi Tarek,<br />My goal is to have something ready for public consumption by early June. You can use the System.ComponentModel.Description attribute to markup enum entries, and the value will be used if the target context supports descriptive text. If you send me an email (john@johnholliday.net) I will add you to the growing list of beta testers.<br />Thanks for your support,<br />John

  10. Hi John!<br /><br />Excellent post, a most interesting read. I attended your session regarding this at the ODC, which was by far the best session at the entire Conference, and have been awaiting the framework eagerly.<br /><br />Have you considered adding support for changing properties of fields already used in a list or contenttype? <br />This is a recurring requirement in the organisation I’m currently working for. An example of this would be to change the displayname and whether or not a field is required.<br /><br />I’d also like to sign up as a betatester, if that’s possible.<br /><br />Thanks,<br />Rikard

  11. Rikard,<br /><br />Yes, I’ve thought about that scenario. The framework will allow you to modify existing properties and even add fields to existing content types. An example of this is changing the name of the default “Title” field to something else.<br /><br />John

  12. Hi John

    That’s an interesting article. The link to sample code and the framework assembly seems to be broken though. Could you fix that?

    Thanks, Jesper

  13. Very nice,

    How to retrieve a strongly typed enumerator from a list containing items of the specified content type?

    Is there, or will there be, something like:
    IEnumerable&lt;MyContentType&gt; items = ContentType.GetItems&lt;MyContentType&gt;(someSPListItemCollection);

    foreach(MyContentType item in items)
    {
    Console.WriteLine(item.StronglyTypedPropNr1);
    Console.WriteLine(item.StronglyTypedPropNr2);
    }

    I could also imagine to go with an interface declaration only with a property that inherits SPItemEventReciever. Instead of a class. But… I really like the idea. If you need a partner to finish this… just let me know. I already did some work with reflection, sharepoint lists, and creating classes on the fly based on interfaces.

    Cheers,
    Wes

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.