Tuesday, 17 April 2007

Deploying content types as a feature

This is the third article in my series of how to create common SharePoint site artifacts as features. Last time we looked at creating site columns as a feature.

Site columns are typically used in lists and/or content types. The concepts are fairly simple since a content type is also effectively a list with a series of columns (for those who have worked with CMS2002 the equivalent entity was a template definition). The columns give the data entered into content areas on a page somewhere to be stored. However, content types in SharePoint are also a valuable unit of granularity, since workflow, document lifecycle policy and several other key pieces of functionality can be configured at this level.

Any page layout you create as a template for pages on your site must be associated with a content type. Generally each column in the content type will be matched to a field control on the page layout, allowing the author to enter content to be stored in each column.

Fortunately creating content types as features is fairly simple - CAML only, no need for a feature receiver (code). Alas VSeWSS can't help us much here but don't forget to use the CAML schema intellisense in VS by ensuring the XML file we're about to write is linked to the schema at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\wss.xsd.

Initially, the part that can seem complex is the ID structure for a content type. Having been happily using GuidGen in Visual Studio every time we need an ID for something in a feature (though never trusting the first one ;-)), it comes as a surprise to have to read documentation just about IDs. As explained in Content Type IDs, the structure reflects the ancestry/parentage of a content type, meaning the parent content types can be determined very efficiently since simple string/byte matching can be used thus reducing database lookups.

The basics are:

  • find the ID of the content type you are deriving from. This can be done by either examining the content type in the SharePoint UI (Site Settings > Manage Content Types) and copying the ID from the URL querystring. Alternatively, search the feature files which the MS developers used to deploy the out-of-the-box content types. The former is probably simpler but the latter gives more scope for learning.
  • Add '00' and then a GUID you have generated (i.e. with GuidGen) to the end (suffix). You now have a valid content type ID. Note that you'll get a meaningful exception on feature activation if it's not.
  • For any child content types, to generate their IDs you can now add a simple ID such as '01' or '02' to the ID generated in the previous step. It's not necessary to suffix the ID with '00' and another GUID now since your unique ID is in the string. This means any ID's you generate will be different from anyone else's, so you can use the simple option and use a 2 digit number rather than another GUID. This means your content type IDs shouldn't grow too long.

The rest is fairly simple. Just add a FieldRef element for each site column the content type uses, specifying it's ID and name. Note that the approach we used to create our site columns meant that we got to specify the ID for them in CAML. We just need to dig out the IDs we specified there. So in both features, the IDs are in easily edited XML rather than being in any compiled code or similar so they are fairly loosely-coupled. Of course in many cases you'd choose to have both artifacts in the same feature, and in the future I'll post about factoring with relation to features.

You should end up with something like for each content type you are deploying:-


<!-- this content type is derived from the 'Page' content type from the 'Publishing' feature -->
<ContentType ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900CF38CCD7FC6E8247AA124F3EE5796C20"
Name="COBArticle"
Group="COB demo content types"
Description="Base content type for articles."
Version="0">
<FieldRefs>
<FieldRef ID="{ae8e6ab8-b151-4fa4-8694-3cd24ad3b1bc}" Name="Locations" />
<FieldRef ID="{A4983A93-4B14-4a61-AE08-09108A718628}" Name="Sectors" />
<FieldRef ID="{6F26090A-C2AE-44d7-8F70-EE1663FE29F1}" Name="Disciplines" />
<FieldRef ID="{71316CEA-40A0-49f3-8659-F0CEFDBDBD4F}" Name="Article Date" />
</FieldRefs>
</ContentType>

Note that it's not necessary to repeat the fields declared in parent content types, though I notice some of the Microsoft features do this.

So now you have your content types deployed, they can now be used in document libraries/lists or associated with any page layouts you have. So next time is deploying master pages/page layouts/CSS etc. as a feature, including having the layouts automatically bound to the content types.

33 comments:

Anonymous said...

Thanks for the great article!

I've got one problem though. I've added a custom Content Type that contains four custom site columns for my document library in the same manner that you have outlined here. I've included the feature in my onet.xml file for my site definition. The only problem I'm experiencing is that in order to view/edit the custom site columns I first need to do the following for each document library I create on a site:
Navigate to My Document Library->Settings->Advanced Settings and Allow management of content types.
I then set the Content Type as the Default Content Type.

Am I missing a step for doing this automatically?

Chris O'Brien said...

That's a good point - it doesn't typically arise in WCM sites so I've not come across it.

If your document library is a custom doc library, then if you are deploying it as a feature you should be able to amend the properties in the 'ListInstance' markup to do this.

If however, you're using a default document library, I think the answer would be to create a feature receiver, and use the API to change the properties of the document library in code. If you're not sure, I show the mechanism to use a feature receiver (doing something different here though) at
Sample code - creating list-based site columns as a feature.

Chris O'Brien said...

Anonymous - since my reply I had a think about your issue and something occurred to me.

What you actually need to do is use the ContentTypeBinding element in your feature - this performs the step of associated the content type with your document library.

The documentation for this element is at http://msdn2.microsoft.com/en-US/library/aa543152.aspx.

HTH,

Chris.

Anonymous said...

Hi Chris

Thanks for great article.

I would like to know if we can deploy custom user group using feature or not?

Thanks for your help.

Regards

Chris O'Brien said...

Regarding the question on creating security groups as a Feature. This isn't directly possible, but since SharePoint security can be manipulated using the API, this can be done with a Feature receiver.

Essentially you write code to perform your task in an event handler - this executes when your custom Feature is activated. It's also possible to write some code (perhaps to reverse the action) which executes when the feature is deactivated.

In your case I would suggest looking at the SPRoleDefinition and SPRoleAssignment classes.

My article on creating site columns as a feature shows some information on how to use Features receivers.

HTH,

Chris.

Anonymous said...

Hey Chris,

Thanks for a great article but when I followed your instructions I ran into a tiny error.

When I try to create a child content type from my custom content type I receive an error when I use the '00' to seperate the parent content type's GUID with the ID of the child content type. However, when I add the child content type's ID, in this case '01' right after the parent's GUID the feature is deployed as normal.

Regards,

Benjamin

Chris O'Brien said...

Benjamin,

Yes, I wasn't clear enough there as I managed to contradict myself in two sentences next to each other.

The '00' separator is only required if the suffix being added to the ID is a GUID. Article now updated!

Many thanks,

Chris.

Arun said...

Hi Chris,
Thanks for this good article. I have created a content type from Item and I want to add this to a custom list through a piece of code in the Feature receiver. I could add the custom content type to my list but the parent content type Item is also there. Is there any way to remove the base content type Item through C# code?

Chris O'Brien said...

Hi Arun,

Yes, you can use the API to remove a content type from a list, but it will only work if the content type is not in use. The call to make would be SPList.ContentTypes.Delete().

Note also that you don't need to use code to associate a content type with a list - it can be done in Feature XML using the ContentTypeBinding element as described in earlier comments - see http://msdn2.microsoft.com/en-US/library/aa543152.aspx for more information.

HTH,

Chris.

Amit Kulkarni said...

Hi,

I have a problem related to Content Type Id generation. Consider the following scenario:
1) My farm contains 3 Site Collections.
2) On each site Collection i have a content type names 'xyz' created using Sharepoint UI. As per my knowledge (i am new to Sharepoint), content type Id will be unique throughput Site Collection. So the 3 instances of 'xyz' (from each Site Collection) will have 3 different Ids (and they are).
3) Now I want to create a new Content Type 'abc' under the 'xyz' using a Feature.
4) I am confused with the Content Type Id attribute in my ContentType.xml. Since the 'xyz' on each Site Collection will have 3 different Ids, through which Id should I create the new Id? And what will happen with other Site Collection's 'xyz'?

Please help me.

Thanks in advance.

Chris O'Brien said...

Hi Amit,

Interesting scenario. Yes, if your content type IDs do not have the same stem (beginning part) because they were creating using the UI, it won't be easy to create child content types with a Feature.

There is one way though. What you could do is create the content types using the API, but write the code as part of a Feature receiver (as opposed to just running it against your SharePoint instance with a console app for example). This would allow you to use Feature activation/deactivation etc. but use code to have full control over the content type creation. Using this technique, you could use the API to 'ask' what the content type ID is for that content type/site collection, then add the suffix onto the end to generate the ID for the child content type.

HTH,

Chris.

Andris said...

Hi, Chris,

How does content type versioning work? I've created content type for news pages, added some news - so far everything is fine. Now when I change some column type in content type feature and deploy it - old news throw exception ...

Chris O'Brien said...

Hi Andris,

Sorry for the late reply. Content types aren't explicitly versioned as such, and managing content type updates can be complex. In actual fact this shouldn't be done by changing the Feature XML, but by using the standard screens or the API instead. Suggest the latter is a good approach (e.g. in a Feature receiver) if you want your changes to be easily replicated across different environments.

The MSDN documentation has some good reading on this - see:

- Updating content types
- Updating child content types
- Content type change control

If something's still unclear and you think I can help, drop me another comment.

HTH,

Chris.

Obieg Dokumentow said...

Hi, thanks for a very nice article. Could you post an example of deploying lookup fields in a CAML feature?

Chris O'Brien said...

Obieg,

Have a look at Creating lookup columns as a Feature.

Thanks,

Chris.

fromonesource said...

Chris,

I need to get the GUID of a list given a URL such as http://server/site/list/Lists/AllItems.aspx

Is there a way to do this? When I try to get the ID of a URL I get an error : "Value does not fall within the expected range"

Please help :)

-Adam

Chris O'Brien said...

Hi Adam,

To get the GUID of a list, go the list's settings page. The GUID will then be shown in the URL, you'll see something like:

/_layouts/listedit.aspx?List=%7B72201421%2D14DC%2D4E71%2DA553%2D65BA1AA22881%7D

You can then perform the following replacements:

%7B with left brace ({)
%7D with right brace (})
%2D with dash (-)

You'll then have the unencoded form of the list's GUID.

HTH,

Chris.

fromonesource said...

Thanks Chris but I need to do this programmatically. Is there a way?

Chris O'Brien said...

Ah OK - then in that case, get a reference to the list using code like:

SPList list = site.AllWebs[sWebName].Lists[sListName];

You'll need a reference to the SPSite object ("site" in the code above) and also the web and list names. Once you have the SPList object, just call SPList.ID to get the ID - no unencoding is required when getting the ID in code.

HTH,

Chris.

Anonymous said...

Can we deloy a content type with a feature where we only have the site column names but not the guids of the site columns.

and another question is the id for a content type how do we generate that, is it a guid with no '-' 's in the middle?

Chris O'Brien said...

Hi,

Unfortunately you'll need the IDs of the columns in order for the content type to reference them properly - no way around this AFAIK.

In terms of how content type IDs work, the basic steps are covered above in the article (see 'The basics are' above), but the MSDN documentation on content type IDs has more details.

HTH,

Chris.

Madhawa said...

Hi Chris, thx for the great article.
I have a small question.

Can we deploy a Content Type as a feature with some extended settings like "Workflow settings" and "Information management policy settings"?

Please explain me how to that. Thx

raj said...

Hi Chris,Iam just trying to deploy a Site Collection Feature.(its a content type which was inheriting from another framework). I broke down the inheritance by changing the first few characters in the GUID to look like 0x0101 ie. for Document content type. When i try to deploy it says File Not Found error. Can you pls. throw some light?
Raj

Anonymous said...

Dear Chris,

I saw your response to Andris concerning the update of content types. I am trying to update a page layout, which requires the addition of new fields to the content type. A completely new one, which needs to be added to the site columns xml and another field that already exists as a site column.

I was trying to do this by updating the original xml files and upgrading the solution and deactivating/activating the feature, which I now know to be completely wrong having read this article from Microsoft.

If I understand correctly my only options are Site Settings and the API. Am I right to assume I can't create a new feature with a new xml containing just the updates to the content type with the ID referencing the parent?

I don't really understand where to put any code as this cannot be done via the UI.

Any assistance greatfully received.

Chris O'Brien said...

@Anonymous,

Yes, you're correct in that the UI and code are the only options for updating existing content types. Creating a new Feature to attempt to update artifacts initially deployed from another Feature is something to avoid.

In terms of where to put the code, it doesn't matter so much. Some options could be:

- create a new Feature which has a Feature receiver containing the code to update the content type
- some other place such as a console application which you will run on the server

HTH,

Chris.

Chris O'Brien said...

@Madhawa,

Sorry for the delay in replying, I was on holiday when you posted.

Yes, is it possible apply some extended settings - I know IM policy can be done, not sure about workflow. This area is not yet well-documented AFAIK. They key is using the XmlDocuments element in the content type schema - see http://msdn.microsoft.com/en-us/sharepoint/ms549876.aspx for some info. You'll probably also need to do some extra searching to find examples using the full schema.

HTH,

Chris.

fromonesource said...

The XmlDocuments schema can be seen in the database in your content database in ContentTypes::Definition. If you have an existing content type with a IM policy you can see it here. You'll need to decode the base64 strings to use.

I have tried manually inserting this into an elements.xml file and deploying a content type with expiration policy as a feature but have been unsuccessful so far. If anyone finds the trick, please post.

Chris O'Brien said...

@Raj,

Sorry for the delay in replying, I was on holiday when you posted.

I suspect the error you are seeing is caused by something other than the content type ID you are using. Is everything correct in terms of filepaths/filenames in your Feature files/solution manifest (if deploying via solution)?

Suggest testing with the original content type ID - you can always delete this content type before anything uses it.

Cheers,

Chris.

bentini said...

Hi Chris,
I'm using your approach on a colaboration site. I want to use the content type that has the lookup field in a list definition.
The problem is that in the schema.xml one has to describe the content type fields and, in case of a lookup, we have to put the list ID...how can I set it if I don't now which is the list ID the site column is referring?
Any ideia?

Thanks!

Chris O'Brien said...

Bentini,

To do this you must write code - you can then retrieve the list ID and set the lookup. I have some code on Codeplex which does this, take a look at http://www.codeplex.com/SP2007LookupFields

HTH,

Chris.

Jamie McAllister said...

Chris,

I noticed your comments about not being able to update a Content Type via a Feature. I found the attached commentary on that interesting, and hopefully so will you. Here's the link;
http://blog.sharepoint.ch/2008_06_01_archive.html

Ben N said...

This is a great article. One thing I am wondering about, though....

I have a column that I want to use across multiple site collections, The column would be a choice column, and I want to have consistency in the set of choices across columns. This is why I am looking at deploying this through features.

I want the choice options to be easy to manage, so that a non-IT person can update the list of options as things change.

So your notion of a lookup list makes sense. But could I have it lookup in one central list that may or may not exist in the same site collection where the site column will be used?

Chris O'Brien said...

@Ben N,

Yes, this is a fairly common requirement. Using the method/code I outline in Sample code - creating lookup columns as a Feature, you can specify the web and list you want to look up.

The cross-site lookup field from SharePoint Solutions addresses this in the same way.

Unfortunately you're out of luck if you need to lookup a list in a different site collection - you'd need to do something custom to deal with this.

HTH,

Chris.