Download the source code for this project here
One of the clever features of WSS 3.0 and MOSS is the ability to create custom field types. These allow you to determine exactly how information is displayed and edited within your SharePoint site.
There are a number of excellent articles out there covering how to implement this functionality, such as Nick Sevens article. However, I’ve found that in order to build configurable applications in SharePoint, storing XML and other XML-based data in fields is extremely powerful. It’s my aim in this post to explain how to create a custom field to store XML and how to use this as a base type for other XML-based data such as XSLT or XHTML.
In the interests of brevity I’ve made the assumption that the reader is already familiar with the process of developing and deploying code and configuration information to SharePoint in order to focus on the actual code itself.
For background info on creating a custom field control, check out MSDN.
I’m a big fan of Carsten Keutmann’s WSPBuilder and use this as my packaging tool of choice. For this demo I’ve created a new WSPBuilder project (in Visual Studio 2008), the added a new Custom Field Type item.
Since I wanted the ability to attach a schema for verification and a stylesheet for rendering, I’ve created an XmlBaseFieldType from which the XmlFieldType is derived. I’ve done this because the XmlFieldType has two properties of type XmlBaseFieldType which will be used to stored XSD and XSLT content.
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
<FieldType>
<Field Name="TypeName">XmlBaseFieldType</Field>
<Field Name="ParentType"></Field>
<Field Name="InternalType">Note</Field>
<Field Name="SQLType">ntext</Field>
<Field Name="TypeDisplayName">Xml Base Field</Field>
<Field Name="TypeShortDescription">A base type for fields containing XML data</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="ShowOnListCreate">FALSE</Field>
<Field Name="Sortable">FALSE</Field>
<Field Name="AllowBaseTypeRendering">FALSE</Field>
<Field Name="Filterable">FALSE</Field>
<Field Name="FieldTypeClass">XmlFieldType.SPFieldXmlBase, XMLFIeldType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a4c58a242a4b085</Field>
<RenderPattern Name="HeaderPattern">
<Property Select="DisplayName" HTMLEncode="TRUE"/>
</RenderPattern>
</FieldType>
<FieldType>
<Field Name="TypeName">XmlFieldType</Field>
<Field Name="ParentType">XmlBaseFieldType</Field>
<Field Name="TypeDisplayName">Xml Field</Field>
<Field Name="TypeShortDescription">Xml Data</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="ShowOnListCreate">TRUE</Field>
<Field Name="Sortable">FALSE</Field>
<Field Name="AllowBaseTypeRendering">TRUE</Field>
<Field Name="Filterable">FALSE</Field>
<Field Name="FieldTypeClass">XmlFieldType.SPFieldXml, XMLFieldType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a4c58a242a4b085</Field>
<PropertySchema>
<Fields>
<Field Hidden="FALSE" Name="ValidationSchemaXml"
DisplayName="Schema Xml"
Type="XmlBaseFieldType">
</Field>
<Field Hidden="FALSE" Name="RenderXml"
DisplayName="Rendering Xslt"
Type="XmlBaseFieldType">
</Field>
</Fields>
<Fields></Fields>
</PropertySchema>
</FieldType>
</FieldTypes>
Step 2 – Create Field Classes
The class (or code behind) for the XmlBaseFieldType is very simple. The main reason for it’s existence is to allow us to hook up custom rendering controls. Note that in order to remain consistent with the naming of other fields I’ve called this class SPFieldXmlBase.
One of the things to note is that when inheriting from SPField, It’s necessary to override FieldValueType since by default this returns null and causes problems elsewhere.
Also, since we’ll be accepting XML data, its necessary to override DefaultValue because the base implementation returns the .InnerText property of the Default node which would strip out our brackets etc.
public class SPFieldXmlBase : SPField
{
public SPFieldXmlBase(SPFieldCollection fields, string fieldName)
: base(fields, fieldName)
{
}
public SPFieldXmlBase(SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName)
{
}
public override BaseFieldControl FieldRenderingControl
{
get
{
BaseFieldControl fieldControl = new BaseXmlField(this);
fieldControl.FieldName = InternalName;
return fieldControl;
}
}
public override Type FieldValueType
{
get
{
return typeof(XmlDocument);
}
}
public override string DefaultValue
{
get
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(this.SchemaXml);
XmlNode nodeInFieldSchema =doc.SelectSingleNode("Field/Default");
if (nodeInFieldSchema != null)
{
return nodeInFieldSchema.InnerXml;
}
return null;
}
set
{
base.DefaultValue = value;
}
}
}
For the actual implementation of the XML field, I’ve created another class that will act as the code behind the XmlFieldType. Again, in order to keep some commonality with existing field classes, I’ve called this class SPFieldXml.
The main difference between this class and the base class is the inclusion of two additional properties: RenderXml and ValidationSechemaXml. These will be used later to implement custom rendering and data validation.
public class SPFieldXml : SPFieldXmlBase
{
private const string RENDER_XML = "RenderXml";
private const string SCHEMA_XML = "ValidationSchemaXml";
public SPFieldXml(SPFieldCollection fields, string fieldName)
: base(fields, fieldName)
{
}
public SPFieldXml(SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName)
{
}
public override BaseFieldControl FieldRenderingControl
{
get
{
BaseFieldControl fieldControl = new XmlField(this);
fieldControl.FieldName = InternalName;
return fieldControl;
}
}
public string ValidationSchemaXml
{
get
{
return this.GetCustomProperty(SCHEMA_XML) + "";
}
set
{
this.SetCustomProperty(SCHEMA_XML, value);
}
}
public string RenderXml
{
get
{
return this.GetCustomProperty(RENDER_XML) + "";
}
set
{
this.SetCustomProperty(RENDER_XML, value);
}
}
}
Step 3 – Create Rendering Controls
We'll create two rendering controls, one for the base field, that will allow us to define a custom edit interface for Xml type data, and another for the actual implementation of the Xml Field, that will take this a step further by validating the input against a schema and making use of a stylesheet to transform the output for display.
Again, it’s worth pointing out that the reason we have a base Xml field is because the parameters for the actual implementation of the Xml field are Xml based. The base implementation will allow us to define a useful edit control for these parameters.
One gotcha that I came up against was, when inheriting from BaseFieldControl, It’s necessary to override the Value property. The base implementation doesn’t store the value.
public class BaseXmlField : BaseFieldControl
{
private SPFieldXmlBase field;
private TextBox textBox;
protected BaseXmlField()
{
}
public BaseXmlField(SPFieldXmlBase parentField)
{
this.field = parentField;
this.textBox = new TextBox();
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}
protected override void CreateChildControls()
{
base.CreateChildControls();
textBox = new TextBox();
this.Controls.Add(textBox);
}
public virtual string Text
{
get
{
this.EnsureChildControls();
if (this.textBox == null)
{
return null;
}
return this.textBox.Text;
}
set
{
this.EnsureChildControls();
if (this.textBox != null)
{
this.textBox.Text = value;
}
}
}
public override object Value
{
get
{
return this.Text;
}
set
{
if (base.Field != null)
{
this.Text = base.Field.GetFieldValueForEdit(value);
}
}
}
}
For now the implementation for the XmlFieldType renderer is as basic as possible. We’ll flesh this out with some useful functionality later.
public class XmlField : BaseXmlField
{
private SPFieldXml field;
public XmlField(SPFieldXml parentField)
{
this.field = parentField;
}
}
Conclusion
That’s about it for now. By following these steps you should have two new field types. The new XML Field can be added to any list and will accept two parameters, one for schema and the other for an XSLT template.
For now these fields don’t do anything exciting, they’re very similar to a standard text field. However, in the next part of this article we’ll look at adding a custom edit interface before progressing onto implementing custom rendering using XSLT and finally making use of XSLT to define a custom edit panel that can be used to capture and edit the XML data stored in the field.
Link to Part 2 of this article
CodeProject