Thursday, June 07, 2007

SharePoint 2007 - watch out for FormUrls in a content type feature definition

The MSDN documentation - Custom information in content types talks about how you can define custom edit forms for a SharePoint content type. It specifies how you can define both FormUrls  as well as FormTemplates within a XMLDocument node for a content type - for more information take a look at the Content Type Definition Schema.

I already took a look at how to do this using FormTemplates but I thougth that it would be more straightforward using FormUrls - so I created a new content type - take a look at (Warning - the next snippet does not work correctly - read the whole blog post :-) ...)

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType ID="0x0100F61D86C522AD4f189680304F14134D97" Name="CustomCT"
Description="Content type for testing custom edit forms"
Version="0">
<FieldRefs>
<FieldRef ID="{0D3CDE70-DFAB-4411-B5D3-17DE7647BC4E}" Name="DemoComment"/>
</FieldRefs>
<XmlDocuments>
<XmlDocument>
<FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<Display>_layouts/mydisplayform.aspx</Display>
<Edit>_layouts/myeditform.aspx</Edit>
<New>_layouts/myuploadform.aspx</New>
</FormUrls>
</XmlDocument>
</XmlDocuments>
</ContentType>
</Elements>

Well, when I tested this - the content type appeared alright but I did not see my custom edit forms. Weird ... But wait - things get worse. When I tried to deactivate the content type using the SharePoint user interface - it did not work - no errors, I just could not deactivate it. Next, I tried to deactivate it using stsadm.exe -  this did not work either. Fortunately, stsadm.exe -o uninstallfeature -name customeditform -force did work. Afterwards I reinstalled the content type feature again - just to make sure that it wasn't a temporary glitch. Then, I tried to run the next code snippet in a console application to check all installed features

 try
{
SPSite sitecollection = new SPSite("http://moss");
SPWeb site = sitecollection.OpenWeb();

foreach (SPContentType ct in site.ContentTypes)
{
Console.WriteLine(ct.Name);
}
}
catch (SPException ex)
{
Console.WriteLine(ex.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

 


The code just stopped when it needed to enumerate the content types - when in uninstalled the CustomEditForm feature - it ran again without any problems. But wait, then I remembered that I had seen FormUrls being used in SharePoint workflows. If you take a look at the OffWfCommon feature (this is a SharePoint Server 2007 Enterprise Edition feature - you will not see it in WSS 3.0 or in MOSS standard edition)- which deploys the SharePoint workflow task content type - it does use FormUrls.

<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType ID="0x01080100C9C9515DE4E24001905074F980F93160"
Name="$Resources:WorkflowTaskIP_Name;"
Description="$Resources:WorkflowTaskIP_Description;"
Group="_Hidden"
Hidden="TRUE"
Version="0">
<FieldRefs>
</FieldRefs>
<XmlDocuments>
<XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<Display>_layouts/WrkTaskIP.aspx</Display>
<Edit>_layouts/WrkTaskIP.aspx</Edit>
</FormUrls>
</XmlDocument>
</XmlDocuments>
</ContentType>
</Elements>

If you want to build your own SharePoint workflows using ASP.NET forms you will need to define a new workflow task content type which inherits from the default SharePoint workflow task content type and here FormUrls work without any problem.


But suppose that you still want to define custom edit forms for your content type (even when it does not inherit) - well it actually seems to work when you do it using code. So you can create a Feature event receiver in which you add your own code to manipulate the FormUrls (I'm not going into details about how to create a feature event receiver - you might want to start by taking a look at SPFeatureReceiver as well as this interesting blog post -WSS v3 solution and feature framework applied to FlexListViewer webpart). Here's a demo code snippet to register a custom edit form:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWeb site = (SPWeb)properties.Feature.Parent;

foreach (SPList list in site.Lists)
{
//If list uses specific content type then update
foreach (SPContentType c in list.ContentTypes)
{
if (c.Name.ToLower() == "customeditformct")
{
c.EditFormUrl = @"/_layouts/Dolmen/myeditform.aspx";
c.Update();
}
}
}

}

Important conclusion: Only use a FormUrls section within your content type when you inherit from the SharePoint workflow task content type. You can however use the EditFormUrl property allthough I still need to test this one thoroughly. If you have some feedback or experienced something similar leave a comment.


6 comments:

tcolton said...

Thanks for a very helpful blog entry.

A couple of items to note. One downside of this method is that the URLs are updated at the time the feature is activated. If a user adds a list with the content type, the list will not have the updated URLs. The feature would have to be deactivated then activated to update the URLs.

To get the URLs to work, I had to change them from "/_layouts/..." to "_layouts/...", removing the leading slash.

Finally if the feature is scoped to the site collection (Scope="Site") properties.Feature.Parent is the site collection not the site. The following code works in this case:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite siteCollection = (SPSite)properties.Feature.Parent;

foreach (SPWeb site in siteCollection.AllWebs)
{
foreach (SPList list in site.Lists)
{
//If list uses specific content type then update
foreach (SPContentType aContentType in list.ContentTypes)
{
if (aContentType.Name == "MyContentType")
{
aContentType.EditFormUrl = @"_layouts/xyz/FormEdit.aspx";
aContentType.NewFormUrl = @"_layouts/xyz/FormNew.aspx";
aContentType.DisplayFormUrl = @"_layouts/xyz/FormDisp.aspx";
aContentType.Update();
}
}
}
}
}

Jun Meng said...
This comment has been removed by the author.
Phil Lanier said...

I got to looking at your code, and one difference I noticed between your content type definition and the custom workflow task content type definition is that the latter includes a namespace for the XmlDocument node. I followed suit and was successfully able to create a content type with custom FormUrls that inherits from the reguar ol' List content type (0x0100...) Perhaps that is the ticket?

Jun Meng said...

The problem has been solved, although the solution was ugly.

Here is the details: http://junmeng.blogspot.com/2007/12/wss-content-type-was-not-updated.html

Ray Saxe said...

Mr. Lanier is correct. I was having problems getting my custom links to show until I realized that my XmlDocument NamespaceURI was missing the "/url" at the end.

So I'm sure that if the original example was changed so that the XmlDocument element read <XmlDocument NamespaceURI= "http://schemas.microsoft.com/ sharepoint/v3/contenttype/forms/url"> instead of <XmlDocument> that it would work.

ecogtip said...

Is there a way to change individual Item Edit Link Programmatically?

I notice that this approach would affect all the items on my list.

How about if I only would like to change the EditFormURL of Item X and not the Other Items on my List.

Is this possible?