July 25, 2008 - cleaned up code snippets...
May 5, 2009 - added ContentTypes.xml which was missing
I was presented with the challenge of trying to figure out how to deploy document contents types and then deploy them to document libraries. This should be simple given I have deployed content types as a feature, created my custom list and site definitions, etc. However, I had only worked with item types and introducing a document template was more complex and not well documented.
In this scenario I had a requirement where I needed to create a new site definition in which I needed to attach the document library content types to. I knew there were several different approaches and I wanted to investigate each. The goal is to create content types that I would later associated K2 BlackPearl processes to.
I also have the belief that I should be able to do everything through CAML. I take the same approach with SharePoint as I do with a database; I treat the CAML as a database schema. A best practice in my opinion is if you cannot script it out the deployment of any customization you are doing to SharePoint and make it repeatable you will have long-term issues with moving solutions from environment to environment. Knowing that I wanted to make sure I could do everything through Features and Site Definitions.
Creating the Content Types
Creating Content Types through a feature is not something too terribly difficult and pretty well documented. If you go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\ctypes\ctypeswss.xml you will find all of the out of the box Content Types that come with SharePoint 2007. All you need to is take and an ID for something like Document "0x0101", concatenate "00", then generate a new GUID and concatenate it to the end resulting in something like this 0x010100B334687E3D2F41f98A38B2FAA0B99088005CC82C39718847d8BEAC042213B66BEF.
Simple enough but now I needed to deploy a document template with the content types. The information on doing this was pretty sparse and I did find the following blog entry which seemed to be the only really thing viable out there. I found some information about managing publishing site templates but that did not apply.
This blog entry explained how to deploy a content type with a document template. You would then have the ability to go to a document library through the SharePoint UI and add it as a content type and it will work. However it will not work if you were to associate the content type to a custom document library definition and then use that definition in a custom site definition. I spent hours messing around this, reading the MSDN documentation, creating templates and opened up the manifest in the .stp, and even using the SharePoint Site Generator tool. Nothing would help but I finally got to work. In finally found this blog which pointed me in the right direction.
First here is the feature. Note there are ElementFiles which have all of the document templates that will be set at the document templates.
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="07E36FBD-08F6-4370-B1E0-42194CA76BA0"
Title="RDA Opportunity Content Types"
Description="These are the content types that will be used by K2 BlackPearl for Opportunity Workflows."
Version="1.0.0.0"
Scope="Site">
<ElementManifests>
<ElementManifest Location="Columns.xml"/>
<ElementManifest Location="ContentTypes.xml"/>
<ElementFile Location="OpportunityQual.docx"/>
<ElementFile Location="StatementOfWork.docx"/>
</ElementManifests>
</Feature>
Next are the contents of Columns.xml, again pretty standard stuff.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field ID="{3370B9EC-BC03-42bd-9A1E-B87D3FEF5C7E}"
Name="CompanyName"
Group="RDA Workflow Opportunity Columns"
Type="Text"
DisplayName="Company Name"
StaticName="CompanyName"
ReadOnly="FALSE"
Hidden="FALSE"
Required="FALSE"
/>
<Field ID="{0CB803C5-FFA9-40b8-8BC3-3D2A7BC71C93}"
Name="CompanyID"
Group="RDA Workflow Opportunity Columns"
Type="Text"
DisplayName="Company ID"
StaticName="CompanyID"
ReadOnly="FALSE"
Hidden="FALSE"
Required="FALSE"
/>
<Field ID="{6AE2C1E0-2F0E-4adc-857E-8EF8BD720A6D}"
Name="OpportunityName"
Group="RDA Workflow Opportunity Columns"
Type="Text"
DisplayName="Opportunity Name"
StaticName="OpportunityName"
ReadOnly="FALSE"
Hidden="FALSE"
Required="FALSE"
/>
<Field ID="{6268BDA9-87DE-4997-B902-77C75F868BB9}"
Name="OpportunityID"
Group="RDA Workflow Opportunity Columns"
Type="Text"
DisplayName="Opportunity ID"
StaticName="OpportunityID"
ReadOnly="FALSE"
Hidden="FALSE"
Required="FALSE"
/>
</Elements>
May 5, 2009
The following is the ContentTypes.xml.
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType ID="0x010100B334687E3D2F41f98A38B2FAA0B99088"
Name="RDA Opportunity Document"
Group="RDA Workflow Opportunity Types"
Description="Content Types to be used Opportunity Workflow"
Version="0">
<FieldRefs>
<FieldRef ID="{3370B9EC-BC03-42bd-9A1E-B87D3FEF5C7E}" Name="CompanyName"/>
<FieldRef ID="{0CB803C5-FFA9-40b8-8BC3-3D2A7BC71C93}" Name="CompanyID"/>
<FieldRef ID="{6AE2C1E0-2F0E-4adc-857E-8EF8BD720A6D}" Name="OpportunityName"/>
<FieldRef ID="{6268BDA9-87DE-4997-B902-77C75F868BB9}" Name="OpportunityID"/>
</FieldRefs>
</ContentType>
<ContentType ID="0x010100B334687E3D2F41f98A38B2FAA0B99088005CC82C39718847d8BEAC042213B66BEF"
Name="Opportunity Qual"
Group="RDA Workflow Opportunity Types"
Version="0">
<DocumentTemplate TargetName="OpportunityQual.docx" />
<FieldRefs />
</ContentType>
<Module Name="Opportunity Qual"
SetupPath="Features\RDA.WSS.Opportunity.ContentTypes"
Url="_cts/Opportunity Qual"
Path=""
RootWebOnly="TRUE">
<File Url="OpportunityQual.docx" />
</Module>
<ContentType ID="0x010100B334687E3D2F41f98A38B2FAA0B990880015019A49E1C8471c952F6209251DC30E"
Name="Statement of Work"
Group="RDA Workflow Opportunity Types"
Version="0">
<DocumentTemplate TargetName="StatementOfWork.docx" />
<FieldRefs />
</ContentType>
<Module Name="Statement of Work"
SetupPath="Features\RDA.WSS.Opportunity.ContentTypes"
Url="_cts/Statement of Work"
Path=""
RootWebOnly="TRUE">
<File Url="StatementOfWork.docx" />
</Module>
</Elements>
So here are some notes and little pit falls I ran into along the way:
- You must enter a <FieldRefs /> node even if there are no fields added to the content type. What would happen is if you open the content type from the document library none of the fields would appear in MS Word.
- For the DocumentTemplate TargetName only put in the name of the file. All of the blogs out there have you enter a full path to the template. Once I took that out, everything started to work. I lost hours on this point and would not have guessed that was the solution given the error I was getting. What would happen is if the content type was associated to a custom document library definition, after the site was provisioned, you would go to click open the document and save it and the content type of the document would get lost and revert to saving the document as Document type.
Create a New Document Library
Now the next step is trying to figure out how to deploy the content type with a site definition. The simplest and well known best practice is to create your own document library. To do this go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\DocumetLibrary folder and copy out all of the files into your own folder. I modified the ID to be a new GUID and modified some other little things.
<Feature Id="FDE23094-968A-4184-9EBC-1E9830C1E7CD"
Title="RDA Opportunity Document Library"
Description="Document library with the opportunity content types attached."
Version="1.0.0.0"
Scope="Site"
Hidden="FALSE"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="ListTemplates\DocumentLibrary.xml" />
</ElementManifests>
</Feature>
Then I modified DocumentLibrary.xml change the Name and Type attributes. Note that when I changed the Name attribute I had to change the folder name "DocLib" to "OpportunityDocLib".
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListTemplate
Name="OpportunityDocLib"
Type="4001"
BaseType="1"
OnQuickLaunch="TRUE"
SecurityBits="11"
Sequence="110"
DisplayName="RDA Opportunity Document Library"
Description="RDA Opportunity Document Library"
Image="/_layouts/images/itdl.gif"
DocumentTemplate="101"/>
</Elements>
Then in the Schema.xml file made the following modifications. First, I added EnableContentTypes and AllowMultipleContentTypes.
<List xmlns="http://schemas.microsoft.com/sharepoint/"
Title="Opportunity Docs"
Direction="$Resources:Direction;"
Url="OpportunityDocs"
BaseType="1"
EnableContentTypes="TRUE"
AllowMultipleContentTypes="TRUE">
<MetaData>
<ContentTypes>
<ContentTypeRef ID="0x010100B334687E3D2F41f98A38B2FAA0B99088005CC82C39718847d8BEAC042213B66BEF">
<Folder TargetName="Forms/Opportunity Qual" />
</ContentTypeRef>
<ContentTypeRef ID="0x010100B334687E3D2F41f98A38B2FAA0B990880015019A49E1C8471c952F6209251DC30E">
<Folder TargetName="Forms/Statement of Work" />
</ContentTypeRef>
</ContentTypes>
...
Some pitfalls I ran into:
- The AllowMultipleContentTypes does not seem to be part of the WSS XSD schema and will not be shown in intillisense.
- Note there are no spaces in the URL attribute of List, for some reason when I took it out it would work even though work if the site was provisioned through the UI. If there was a space and this was provisioned in the Site Definition, I would get failures.
Second, to make the custom columns visible you need to do the following. First you need to add the columns to the Fields node. This is a well known mistake and rather perplexing thing you must do.
<Field ID="{3370B9EC-BC03-42bd-9A1E-B87D3FEF5C7E}"
Name="CompanyName"
DisplayName="Company Name"
Type="Text"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="CompanyName"/>
<Field ID="{0CB803C5-FFA9-40b8-8BC3-3D2A7BC71C93}"
Name="CompanyID"
DisplayName="Company ID"
Type="Text"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="CompanyID"/>
<Field ID="{6AE2C1E0-2F0E-4adc-857E-8EF8BD720A6D}"
Name="OpportunityName"
DisplayName="Opportunity Name"
Type="Text"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="OpportunityName"/>
<Field ID="{6268BDA9-87DE-4997-B902-77C75F868BB9}"
Name="OpportunityID"
DisplayName="Opportunity ID"
Type="Text"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="OpportunityID"/>
Next you need to dig down in the Views Node and find the appropriate ViewFields node and add in the custom columns. This will make them visible in the document library view within the SharePoint UI.
<ViewFields>
<FieldRef Name="DocIcon">
</FieldRef>
<FieldRef Name="LinkFilename">
</FieldRef>
<FieldRef Name="Modified">
</FieldRef>
<FieldRef Name="Editor">
</FieldRef>
<FieldRef Name="CompanyName">
</FieldRef>
<FieldRef Name="CompanyID">
</FieldRef>
<FieldRef Name="OpportunityName">
</FieldRef>
<FieldRef Name="OpportunityID">
</FieldRef>
</ViewFields>
Create Custom Site Definition
Note the next thing to do is create a custom site definition. Again this is something that is pretty well documented. You should never modify the Microsoft site definition but instead create your own. I will outline the steps that you must do but this is well documented:
- Go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML and create an new WEBTEMP_XXX.XML file for you site definition.
- Go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplates, select an existing template to start from. Copy all of the contents out of that into you new folder. Among other things, make sure the name of the new folder corresponds with the template name defined in the previous step.
I will stop here and suggest you read up on it. Many books out there show how to do this was well.
I did make the following modifications to my custom template to show the new document library that I created.
First, I added this under the appropriate Configuration node.
<List FeatureId="FDE23094-968A-4184-9EBC-1E9830C1E7CD"
Type="4001"
Title="Opportunity Documents"
Url="OpportunityDocuments"
QuickLaunchUrl="OpportunityDocuments/Forms/AllItems.aspx"
EnableContentTypes="TRUE" />
Pitfall - Note if the EnableContentTypes needs to be set to true here even though the list definition has it set to true. I ran into issues that when the document library is created with the site definition, the content types would again be wrong when I saved. It would only select the first content type by default. Setting this to true too resolved the problem.
Second, I added the following in the corresponding Module node for the Configuration node.
<View List="OpportunityDocuments"
BaseViewID="6"
WebPartZoneID="Left"
WebPartOrder="2" />
Then I was done. I got everything working so now when a new site is provisioned it will have an opportunity document library with the custom content types (with standard document templates) ready to go.
Other things I Tried Worth Noting
The following are things that I tried which did not completely work. The solution above was what I wanted to do but it was just getting weird errors with non-obvious solutions. I figured since everyone learns from the things they did wrong I would post up things that I tried.
List Instance Feature
I decided to instead of trying to create a custom document library definition with custom content types I would create a feature using a ListInstance. I would then activate the feature in my custom site definition. The following is the feature.xml.
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="3D4BDB9A-EB6A-40ec-B0C9-367C9EB5D44C"
Title="RDA Opportunity Library Instance"
Description="Instance of a doc lib with opportunity content types."
Version="1.0.0.0"
Scope="Web">
<ElementManifests>
<ElementManifest Location="LibraryInstance.xml"/>
</ElementManifests>
</Feature>
The following is the LibraryInstance.xml which inherits from the out of the box document library, note the FeatureID that is used.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListInstance Id="400005"
FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101"
Description="Opportunity Documents"
TemplateType="101"
Title="Opportunity Documents"
OnQuickLaunch="TRUE"
QuickLaunchUrl="OpporunityDocuments/Forms/AllItems.aspx"
Url="OpportunityDocuments">
<Data/>
</ListInstance>
<ContentTypeBinding ListUrl="OpportunityDocuments"
ContentTypeId="0x010100B334687E3D2F41f98A38B2FAA0B99088005CC82C39718847d8BEAC042213B66BEF"/>
<ContentTypeBinding ListUrl="OpportunityDocuments"
ContentTypeId="0x010100B334687E3D2F41f98A38B2FAA0B990880015019A49E1C8471c952F6209251DC30E"/>
</Elements>
I would then add the following into the WebFeatures of the Onet.xml of the custom site definition.
<!--List instance feature-->
<Feature ID="3D4BDB9A-EB6A-40ec-B0C9-367C9EB5D44C" />
Notes:
- First I actually tried putting the ContentTypeBinding into its own feature but I continued to get a File Not Found error every time.
- I also would get File Not Found errors if there was a space in the URL.
- This feature worked fine until I finally corrected the way in which I associated a content type to a document template through a feature. If I deployed a content type feature without a document template, and then added the document template manually through site settings, content type gallery this would work.
Long term, this was not the solution I wanted.
- First adding the document library as a feature in this manner would not allow me to add it to the Module View in the onet.xml of the custom site definition. I wanted the users to see the document library on the top page of the newly provisioned site as there would not be a lot documents in the library.
- Second I really wanted to keep custom document library definition as this would be consistent with the way other content types are made available. For instance, tasks, linked lists, document, issues, etc. content types each have their own list definition.
Content Binding and Site Stapling Features
Another solution I tried was feature stapling. This is a very important new capability in WSS 3.0 as it allows you to create features which will be provisioned to existing site definitions without requiring you to create a new site definition.
In my situation this did not make a whole lot of sense as I needed to associate these content types to a specific site definition; however I figured I give this a try.
I first create the following feature that would bind the documents to a document library.
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="7BC32E74-6CA9-4775-9C19-400DF718E3E2"
Title="RDA Opportunity Bind Document Library"
Description="This feature will staple opportunity documents to a site."
Version="1.0.0.0"
Scope="Web">
<ElementManifests>
<ElementManifest Location="BindDocs.xml"/>
</ElementManifests>
</Feature>
This is the BindDocs.xml for the feature. The ListUrl needs to correspond to a name of the list in which this content type is being associated to. Note below I have OpporunityDocuments as the ListUrl. There is no space and there is an assumption that there is already a document library on the site with a URL of OpportunityDocuments. This is a poor assumption and should not be done but in my case I had already added a document library with this name to my custom site definition. If this was a more general solution you would put something like Shared Documents as the ListUrl.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentTypeBinding ListUrl="OpportunityDocuments"
ContentTypeId="0x010100B334687E3D2F41f98A38B2FAA0B99088005CC82C39718847d8BEAC042213B66BEF"/>
<ContentTypeBinding ListUrl="OpportunityDocuments"
ContentTypeId="0x010100B334687E3D2F41f98A38B2FAA0B990880015019A49E1C8471c952F6209251DC30E"/>
</Elements>
Then I created the following feature to activate the ContentTypeBinding feature when my custom site definition were to be provisioned.
<Feature Id="998ACA04-668C-4491-94B1-DFFB83F68665"
Title="RDA Opportunity Site Stapling"
Description="Associates features to the RDA Opportunity."
Version="1.0.0.0"
Scope="Site"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="Elements.xml" />
</ElementManifests>
</Feature>
Here is a good blog on FeatureSiteTemplateAssociation. As you see I associate a the ContentTypeBinding feature to the custom site definition I have called Opportunity.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<FeatureSiteTemplateAssociation
Id="7BC32E74-6CA9-4775-9C19-400DF718E3E2"
TemplateName="OPPORTUNITY#0" />
</Elements>
I have used FeatureSiteTemplateAssociation successfully in the past to associate a CustomAction feature to a site definition but in this case I would get never ending File Not Found errors. I would go into the SharePoint logs and would not find any information that could solve the problem. I Googled forever and could not find anything that would solve the problem. I found this particularly frustrating as I could make a good case that someday I may have a use case where I would need to associate content types to existing site definitions but for right now this would not work for me. Regardless, the real solution I wanted was my own document library definition with my custom content types which I was finally able to achieve.