Sunday, February 10, 2008

Content Type Feature with Document Template

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.

22 comments:

ASP .NET said...

in the session "Create a new Document Library", how do i get the template file deployed in the Form folder? I went through the steps but I didn't see the template file show up.

Jason Apergis said...

ASP.net,

If I understand your question correctly the template will be associated to the content type. The template will be accessible if you go to site settings and look at the content type definition. I personally like this approach because then it is centrally located and instead of scattered across document libaries.

Jason

ASP .NET said...

Thanks Jason for your quick response. I agree the first approach is better but just wanted to confirm if I missed anything in the second one, since the template file doesn't come up when I followed the steps.

ASP .NET said...

in addition, the reason why we want a custom document library is we want those custom fields automatically show up when the document library is created. however, adding a content type to a standard document library would require the user to add the new columns to the view in order to see.

Jason Apergis said...

Well in my little solution I did in my custom document library I modified the view to display the custom columns I have deployed through the site level content type.

ASP .NET said...

Great! I finally got it right. I have one more question, when I create a RDA Opportunity Document Library in SharePoint, I was still asked to "Select a document template to determine the default for all new files created in this document library" and had to pick one in the Document Template drop down list, is there a way to avoid this (not showing this option)?

ASP .NET said...

here comes another question, which attribute controls the location of the template file? e.g., if I want StatementOfWork.docs to be in the Form folder, not a subfolder like Form\Statement of Work\?

Jason Apergis said...

I believe you are getting that dropdown from within Word. That seems to be default behaviour in other SharePoint environments I have used. I have not tried off hand to turn it off as I wanted it to show because I have multiple content types in the document library.

I believe that is correct. Typically I used the MS out of the box list templates in the 12 hive as examples. They tended to put the templates down in a sub folder. Yes - if you change our the TargetName that will change the location.

ASP .NET said...

Thanks Jason. Do you know how to deploy multiple document template files for one content type? (not through SharePoint UI)

ASP .NET said...

just did some testing and it seems I can add multiple templates by keep adding the File element in the Module.

ASP .NET said...

another question is how to do this with localization?
the Url attribute is expressed as Url="_cts/ContentTypeName", but if my ContentTypeName is expressed as
Name = "$Resources:Document;" then what the Url for the content type should be?

ASP .NET said...

just realised that the Url attribute in the Module element is not important and doesn't have to match the content type name.

ASP .NET said...

Jason, I'm still wanting to know if we can put the template file directly in the Forms directory for a content type; rather than in a subfolder. anything in the Module element is responsible for this? do you know what the Path attribute is?
thanks.

Jason Apergis said...

My two cents is what I did does replicate standard operating procedure of SharePoint were to do it. So if I for example were to manually add two content types to a document library which had their own templates a subfolder would be created. For wrong or for right I would try to keep to the way SharePoint does this. It is most likely done this way because there is no guarantee that a template name may be the same between content types. So they are being safe.

If you really really want to do what you are suggesting in my code above try changing the TargetName for the ContentTypeRef in the document library definition. I have not tried that before but it may work.

Jason

Julius said...

Did I miss something or is the excerpt from the ContentTypes.xml missing in your code snippets?

Very usefull post!

Jason Apergis said...

Julius,

Thanks for the feedback.

Yep - I am missing the ContentType.xml. At this point the code is on some on vpc I am not going to find at this point.

However I recently wrote a content type Feature I can post if that would be helpful?

Thanks,
Jason

Julius said...

That would be great! I am currently working on a wsp-solution including a feature with a large number of content types with document templates and a working example will save me some pains.

Jason Apergis said...

Julius,

It is your lucky day. I found the VPC with the code from over a year ago - on my first try! I have posted here.

I will still try to create another general posting.

If you go to the 12 hive. In the Features folder there is a "fields" and "ctypes" folders. The are the base content types for SharePoint. There tons of good examples in there.

Jason

Ralph said...

Hi,

J provisioned site definition without any problems.
When creating new Statement of Work document from New menu MS Word opens well but it opens with Opportunity Qual content type instead of Statement of Work content type (Office button-Prepare-Properties).
When I create custom document content type by UI and next add it to document libray, creating new document from New menu works fine.
What should feature look like to create documents with correct content type?

Hjalmar said...

awesome post

Kourosh said...

Hi,
Is it possible to do all these steps programmatically (C#)?
thanks

Jason Apergis said...

Kourosh,

Yes you can do this in C# as well, pretty commonplace. I would say I wrote this almost three years ago. Visual Studio 2010 and SharePoint 2010 have made this much easier.

Jason