Saturday, April 14, 2012

Adventures in extending the Publishing.Fields.LinkValue class

Recently I was working on a SharePoint application that required me to extend the out-of-box Publishing Link field.  In the end I found an acceptable solution, but it took a little while to get there.

The first thing I tried was creating a new link field value class that extended the LinkFieldValue class.  The LinkFieldValue class is not sealed so I thought I would be able to work with it.

LinkFieldValue inherits from the HtmlTagValue class.  HtmlTagValue is more or less a dictionary that provides a structured way to create an HTML tag.  Unfortunately HtmlTagValue has all of the routines I needed marked as Internal.  So this meant that my idea of inheriting from LinkFieldValue was dead.

The Publishing LinkFieldValue class inherits from the Microsoft.SharePoint.Publishing.Fields.HtmlTagValue class.  If you look at the guts of LinkFieldValue you will see that HtmlTagValue does all the heavy lifting.  The LinkFieldValue properties call back into the HtmlTagValue.Item property. 

HtmlTagValue class has two public routines (everything else is Internal or private). So that pretty much ended my idea of extending the LinkFieldValue class. 

Next I decided to store the link data in a SPFieldMultiColumnValue field.  I have created plenty of these in the past so I knew exactly what to do.

Everything was great until I ran a test for Link fix-up.

In case you did not know WSS has built-in functionality that supports fixing site collection links that get broken because the WSS object (image, document, publishing page) is moved. 

Turns out WSS link fix-up only works for certain field types (and Text is not one of them).  It also requires that the link be stored as an anchor tag.  So that pretty much ended my idea of using the SPFieldMultiColumnValue field.

Using XML to store the Link data

Finally I decided to store the link using the same technique as the Summary Link control.  The Summary Link control provides a way of storing a variable number of links in a publishing field.  It does this by serializing the data into XHTML and storing it in a rich text field.

First I created a base field class that would act as a parent for any field that I wanted to store as XHTML. 

public class XmlAsHtmlField : SPFieldMultiLineText
{
public XmlAsHtmlField(SPFieldCollection fields, string fieldName) : base(fields, fieldName) { }
public XmlAsHtmlField(SPFieldCollection fields, string typeName, string displayName) : base(fields, typeName, displayName)
{
base.RichText = true;
base.SetRichTextMode(SPRichTextMode.FullHtml);
}
}

Next I created a XmlAsHtmlFieldValue class that would act as a base class for all field values that use the XmlAsHtmlField class.  The base field value class implements a dictionary that provides a mechanism for all child classes to store their property values.  The class assumes that any properties stored in the dictionary translate their value into XHTML.


public class XmlAsHtmlValue
{
private Dictionary storageItems = new Dictionary();

///


/// Initializes the XmlAsHtmlValue class
///

public XmlAsHtmlValue()
{
}

///


/// Initializes the XmlAsHtmlValue class and populates the Dictionary based on the provided XML
///

///
public XmlAsHtmlValue(string HtmlValue)
{
HtmlValue = Utility.FixQuotesInXML(HtmlValue);

System.Xml.XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(HtmlValue);
XmlNodeList storageItemNodes = xmlDocument.SelectNodes("/div/div");
foreach (XmlNode storageItemNode in storageItemNodes)
{
XmlAttribute classValue = storageItemNode.Attributes["class"];
string id = storageItemNode.Attributes["id"].Value;
XmlStorageItemType itemType = (XmlStorageItemType)System.Enum.Parse(typeof(XmlStorageItemType), classValue.Value);
storageItems.Add(id, new XmlStorageItem(itemType, storageItemNode.InnerXml, id));
}
}

///


/// Returns the dictionary that is used to store properties
///

public Dictionary StorageItems
{
get
{
return storageItems;
}
}

///


/// Creates an XML representation of the Dictionary
///

///
public virtual string ToXml()
{
StringBuilder returnValue = new StringBuilder();
returnValue.Append("
");

foreach (KeyValuePair item in storageItems)
{
XmlStorageItem storageItem = item.Value;
returnValue.AppendFormat("

{2}
", storageItem.ID, storageItem.StorageItemType, storageItem.StorageItemValue);
}
returnValue.Append("
");

return returnValue.ToString();
}

public override string ToString()
{
return this.ToXml();
}

}

One really ugly problem I ran into was the fact that Microsoft will strip out double and single quotes from the HTML.  I am not sure why this is done, but I had to create a routine to put the quotes back in.

Next I created an extended link value class that used the out-of-box link field value plus another class I created with the new link properties.  I stored both of the classes in the XMLAsHtmlValue class.

Because I used the out-of-box Link Value class I ran into some problems when I created unit tests.  When I tried to generate the tests in Visual Studio.Net 2008 I would receive the following error.

Could not resolve member reference: Microsoft.SharePoint.ISPConversionProcessor::PostProcess

To fix the problem I added the following DLL as a reference in my project (Microsoft.HtmlTrans.Interface, it is located in the GAC)


In the end I had something that would allow me to create fairly extensible field value classes.  I am not sure how well this solution scales (a topic for a future posting) and I am not how easy this solution will be to upgrade with future versions of SharePoint.


View the original article here

No comments:

Post a Comment