Ever tried to add a custom field control to a Custom List Form in SharePoint Designer? You’d think it’d be simple, just drag the field from the toolbox and stick it on the form. Nah! We haven’t heard much from Ernő Rubik in recent years. I’m convinced he’s at Microsoft working on the SharePoint team (and doing a bit of work in the WF team when he’s got a chance)

You’ll come across a few problems when trying to add your custom field control (or any other type of custom control) to a custom list form. As you may be aware the custom list form is implemented using the DataFormWebPart control. This web part uses Xslt to transform the xml data that represents an SPListItem into a fabulous user interface. There’s a flaw in this plan though: capturing user input – to capture user input it’s necessary to have server controls that can be picked up in the code behind. (Or you could do it with JavaScript and post it using a web service but lets not complicate things!).

The user of server controls causes two problems in XSLT, the first is easy to fix, the second will require the services of good ole’ Visual Studio. Firstly, when you drag and drop your field onto the custom list form design surface in SharePoint Designer all will seem fine. However, if you try to access the page containing your control you’ll see:

Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Windows SharePoint Services-compatible HTML editor such as Microsoft Office SharePoint Designer. If the problem persists, contact your Web server administrator

Also, if you switch to design view in SharePoint Designer you’ll find an error message similar to:


This Web Part does not have a valid XSLT stylesheet:Error: Reference to undeclared namespace prefix: 'Controls'.

Thankfully, this one is easy to fix. Switch to code view then find the <xsl:stylesheet> node. You need to add a namespace for your control, so in the case of my example, my controls were added with the prefix of Controls so I’d add a new namespace attribute as illustrated:

 

image

Switching back to design view, confirms that this has fixed the XSLT error. However if you try to view the page now you’ll get an Unknown server tag error similar to:

image

Now this is where the fun begins!

This error appears because the DataFormWebPart doesn’t have a reference to the assembly containing your custom control. I hear you say “I checked that, there’s a reference at the top of the page”. Here’s the thing thought, the reference on the page only applies to items that are on the page itself. So if you drag your custom control onto the page it’ll work perfectly but because it’s being referenced in a web part,  the web part itself needs  a reference to your custom control assembly.

So lets look at how DataFormWebPart determines which assembly references to include (from Reflector):

 protected string[] _assemblyReferences = new string[] { "<%@ Register TagPrefix=\"SharePoint\" Namespace=\"Microsoft.Sharepoint.WebControls\" Assembly=\"Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c\" %>" };
   

Distinctly not ideal in my opinion!

We need to find a way to add our assembly into this array. Sadly, there are no properties on the default implementation of DataFormWebPart to allow us to do this so the only option is to create a new web part that inherits from the DataFormWebPart and provides this additional property.

So we can create a web part as follows:

public class ExtendedDataFormWebPart : DataFormWebPart
{
    public ExtendedDataFormWebPart()
        : base()
    {
    }
    
    [Browsable(false), WebPartStorage(Storage.None), PersistenceMode(PersistenceMode.InnerProperty)]
    public string AssemblyReferences
    {
        get
        {
            List<AssemblyReference> response = new List<AssemblyReference>();
            foreach (string reference in _assemblyReferences)
            {

                AssemblyReference ar = new AssemblyReference(reference);
                response.Add(ar);
            }

            return response.ToString();
        }
        set
        {
            XDocument doc= XDocument.Parse("<root>"+value+"</root>");
            var refs = from r in doc.Descendants("AssemblyReference")
                       select new AssemblyReference
                       {
                           Prefix = r.Attribute("Prefix").Value,
                           Namespace = r.Attribute("Namespace").Value,
                           Assembly = r.Attribute("Assembly").Value
                       };


            _assemblyReferences = new string[refs.Count()];

            int i = 0;
            foreach (var ar in refs)
            {
                _assemblyReferences[i] = ar.ToString();
                i++;
            }
        }
    }


}

public sealed class AssemblyReference
{
    public AssemblyReference()
    {
    }

    public override string ToString()
    {
        return string.Format("<%@ Register TagPrefix=\"{0}\" Namespace=\"{1}\" Assembly=\"{2}\" %>", Prefix, Namespace, Assembly);
    }

    public AssemblyReference(string reference)
    {

        Match m = Regex.Match(reference, "TagPrefix=\"(\\S*)\" Namespace=\"(\\S*)\" Assembly=\"(.*)\"");

        Prefix = m.Groups[1].Value;
        Namespace = m.Groups[2].Value;
        Assembly = m.Groups[3].Value;
    }

    public string Prefix;
    public string Namespace;
    public string Assembly;
}

 

 

Then we can replace the reference to the DataFormWebPart in the page using SharePoint designer:

 <CustomWp:ExtendedDataFormWebPart runat="server" IsIncluded="True" …>
<DataSources> <!—Clipped for brevity—>
</DataSources> <AssemblyReferences> <AssemblyReference Prefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"/> <AssemblyReference Prefix="Controls" Namespace="MyCustom.Controls" Assembly="MyAssembly, Version=1.1.0.0, Culture=neutral, PublicKeyToken=183802e36edc8515"/> </AssemblyReferences> <ParameterBindings> <ParameterBinding Name="ListItemId" Location="QueryString(ID)" DefaultValue="0"/> <ParameterBinding Name="ListID" Location="None" DefaultValue="{09E30A3B-8053-4EA6-AA1E-B4315C829B5B}"/> <ParameterBinding Name="dvt_apos" Location="Postback;Connection"/> <ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/> <ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/> </ParameterBindings>

Note the new AssemblyReferences property. Simply add an AssemblyReference element for each custom assembly that you want to include. The Prefix should match up with the namespace that you declared above. Also, note that if you’re using SharePoint field controls you need to add the WebControls reference.

 

Another mystery unravelled!