This is a place to record my journey with SharePoint. I realized that I was learning a lot of stuff that other people would probably like to know about (and that I would like to refer back to... I'm getting kinda old and my memory is fading.)

Wednesday, May 21, 2008

Creating a SharePoint Web with the API

I have been working on a application that provides a repeatable way to create sites.  This application is very similar to the SharePoint Test Data Population Tool

One of the first tasks was to create new web applications.  A quick look at the API and I thought well this is simple enough.

First instantiate a new instance of SPWebApplicationBuilder.  Set some properties on my object and then call create.  Use the SPWebApplication.Provision() method and I am done.

Well that is not exactly true.  What I discovered was that the Provision method only creates the web application on the local server.  This is great for a single web front end, but for a more typical setup more work is required.

So I dug into the Administrative screens using Reflector.  I discovered that the SharePoint team is instantiating a timer job that will provision the newly created web on the other web front ends.

if (SPFarm.Local.TimerService.Instances.Count > 1)
{
SPWebApplicationProvisioningJobDefinition definition = new SPWebApplicationProvisioningJobDefinition(application, this.ApplicationPoolSection.ResetIis);
definition.Schedule = new SPOneTimeSchedule(DateTime.Now);
definition.Update();
}

So I thought okay this is simple enough. Then I discovered that SPWebApplicationProvisioningJobDefinition as marked as internal. Agghhhh!!!!


This pattern of finding a useful SharePoint routine only to have it hidden with the Internal keyword happens way too often.  I am sure that usually there is a good reason to hide away the functionality, but I could see no logical reason to hide SPWebApplicationProvisioningJobDefinition. 


So after throwing a few darts at the SharePoint Development Team dartboard (No I do not actually have one, but it could be a great gift SharePoint developers) I sat down and created a timer job definition that does basically the same thing as SPWebApplicationProvisioningJobDefinition.


This was the first timer job I had ever written so I made a *few* mistakes. 


First I failed to understand a basic concept of timer jobs.  That is any classes inheriting from SPJobDefinition need to be deployed to the GAC on each SharePoint server.  If they are not then the job will not be created correctly.  The frustrating thing I encountered was the fact that no runtime error was raised.  So it looked like the job definition was created correctly, but it was not.


Second I failed to put a default constructor on my job definition class.  This resulted in a runtime error with the job definition.  The runtime error indicated that the class failed to serialize properly.


Third I failed to decorate my member variables with the [Persisted] attribute.   This resulted in me losing my variable values.


Fourth I did not realize that I have to manually delete the job definitions when they fail.  To delete the timer job definition bring up the SharePoint Central Administration web site.  On the Operations tab choose Timer job definitions (under the Global Configuration header).  Next click the timer job definition in the list and then click the Delete button.


Fifth I failed to understand that job definitions must be uniquely named.  This was a problem for me because my application would create one timer job per Web Application.  Sometimes a new web application job would be created before the other job was finished.  To work around the issue I included a timestamp in the name.  I am not sure if this was smart, but it works.


Sixth I failed to understand that the OWSTimer.exe service must be restarted when deploying a new version of my timer job class library.  The OWSTimer.exe service is what manages the timer jobs.  It actually instantiates and executes the timer job classes.  Since my timer job class was deployed to the GAC it meant I had to restart the OWSTimer.exe (on each SharePoint server ughhh!!!). 


In the end I accomplished my goal, but it would have been a lot easier if the SharePoint development team had not marked SPWebApplicationProvisioningJobDefinition as internal. 


Sunday, May 18, 2008

What is going on in MOSS Land

It has been a while since my last post.  I have been heads down working on a large project for my employer.  The project is to convert my employers existing MCMS/SharePoint 2003 solution to SharePoint 2007.  When this project is over my employer will have one of the largest SharePoint sites in the world.

Stay tuned, I am sure there will be many existing adventures to post about.

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.

Inheriting from LinkFieldValue

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. 

Storing link data in SPFieldMultiColumnValue field

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 <A> 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<string, XmlStorageItem> storageItems = new Dictionary<string, XmlStorageItem>();

/// <summary>
/// Initializes the XmlAsHtmlValue class
/// </summary>
public XmlAsHtmlValue()
{
}

/// <summary>
/// Initializes the XmlAsHtmlValue class and populates the Dictionary based on the provided XML
/// </summary>
/// <param name="HtmlValue"></param>
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));
}
}

/// <summary>
/// Returns the dictionary that is used to store properties
/// </summary>
public Dictionary<string, XmlStorageItem> StorageItems
{
get
{
return storageItems;
}
}

/// <summary>
/// Creates an XML representation of the Dictionary
/// </summary>
/// <returns></returns>
public virtual string ToXml()
{
StringBuilder returnValue = new StringBuilder();
returnValue.Append("<div id=\"root\">");

foreach (KeyValuePair<string, XmlStorageItem> item in storageItems)
{
XmlStorageItem storageItem = item.Value;
returnValue.AppendFormat("<div id=\"{0}\" class=\"{1}\">{2}</div>", storageItem.ID, storageItem.StorageItemType, storageItem.StorageItemValue);
}
returnValue.Append("</div>");

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)


Parting Thoughts

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.

Friday, May 2, 2008

Problems with XMLNS and SPWebConfigModification

Recently I posted an article that discussed the SPWebConfigModification.  I meant to add some information about problems with XML Namespaces and SPWebConfigModification.

One of the first problems my team encountered with SPWebConfigModification was failures when working with configuration settings that overrode the default xml namespace (xmlns).

The problem arose when we tried to apply the webconfig modifications needed to for the Enterprise Library.  Below is a sample entry from the configuration settings needed for Enterprise Library.  Notice how the default xmlns is being overridden.

<enterpriselibrary.configurationSettings applicationName="NewsApplication" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.microsoft.com/practices/enterpriselibrary/08-31-2004/configuration" />



That one statement invalidates the XPath expressions in the SPWebConfigModification Name and Path properties.  This means that SPWebConfigModification cannot successfully find the entry when it scans the web.config file.  So every time the web.config file is modified by SPWebConfigModification the entry gets duplicated (which causes the web.config file to be invalid).



A little research into the way .Net Framework handles XML namespaces turned up the XmlNameSpaceManager class.  This class is used to define namespaces at runtime. 



Unfortunately SPWebConfigModification does not expose this class when defining the XPath expressions.  If it did it would be possible to work with XML namespaces.



By the way, the team was able to work around the enterprise library configuration problem by moving the EntLib configuration settings into separate files.



Thursday, May 1, 2008

Creating a Feature to update the Web.Config

I just wrapped up a feature that will update the web.config with custom settings. I did it by creating a feature receiver that loads web.config modifications from an xml configuration file and then applies them using the SPWebConfigModification class.

The entire process is straight forward. There are few subtle nuisances to SPWebConfigModification that make it tricky.

Creating New Web.Config Changes

Below is some sample code from my Feature. The ApplyWebConfigChange is responsible for creating an entry to the web.config.

private void ApplyWebConfigChange(SPWebApplication webApp, string ConfigurationChangeIdentifier, string ConfigurationName, string ConfigurationXPath, string ConfigurationValue)
{

Collection&lt;SPWebConfigModification&gt; modsCollection = webApp.WebConfigModifications;

SPWebConfigModification configMod = LookupModificationEntry(modsCollection, ConfigurationChangeIdentifier, ConfigurationName, ConfigurationXPath);
if (configMod == null)
{
configMod = new SPWebConfigModification(ConfigurationName, ConfigurationXPath);
configMod.Owner = ConfigurationChangeIdentifier;
configMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modsCollection.Add(configMod);
}

configMod.Value = ConfigurationValue;
webApp.Farm.Services.GetValue&lt;SPWebService&gt;().ApplyWebConfigModifications();
webApp.Update();
}






The first step is to reference the collection that contains a list of SPWebConfigModification objects. This collection is accessed via the SPWebApplication.WebConfigModifications property.



The next step is to see if the change already exists in the WebConfigModifications collections. If it does then all I want to do is update it. If it is not there then I want to create it.



When creating new entries it is extremely important to make sure the SPWebConfigModification.Name is unique within the SPWebConfigModification.Path. If the name is not unique within the context of the path then you could end up with duplicate entries and you will not be able to remove the entry with SPWebConfigModification.







Tip: Get very comfortable with XPath since both the Path property and Name property have to be valid XPath syntax.







Make sure the Owner property is set to a unique value for your change. Later I will provide a quick and easy way to remove changes from SPWebConfigModification collection. My technique assumes that the Owner is unique for each modification.










Tip: Always set Type property to SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode. The two other options "EnsureAttribute" and "EnsureSection" will permanently change the web.config (as in you can never remove the items, even if you manually remove them they will return)







The last step is to call the SPWebService.ApplyWebConfigModifications and SPWebApplication.Update. Since my web application is deployed across multiple web front ends I need to call the ApplyWebConfigModifications. This routine will make sure the web.config change is made on all web front ends. If I only had one web front end then I could have just called SPWebApplication.Update.



Removing Web.Config Changes




Removing entries is really simple if you can assume that the Owner property is unique per change. In the routine below you can see that I will go through each item in the SPWebConfigModification. When I find an entry with the Owner set to a certain value I will remove it.




private void RemoveWebConfigChange(SPWebApplication webApp, string ConfigurationChangeIdentifier)
{

Collection<SPWebConfigModification> modsCollection = webApp.WebConfigModifications;

SPWebConfigModification deleteModification = null;

foreach (SPWebConfigModification mod in modsCollection)
{
if (mod.Owner == ConfigurationChangeIdentifier)
{
deleteModification = mod;
break;
}
}

if (deleteModification != null)
{
modsCollection.Remove(deleteModification);
webApp.Farm.Services.GetValue&lt;SPWebService&gt;().ApplyWebConfigModifications();
webApp.Update();
}
}






Here is an article that covers some more problems with SPWebConfigModification in more detail.



I learned a lot of great information about SPWebConfigModification here.






Subscribe to my Blog

Blog Archive

About Me

My Photo
Jeff Dalton
I have been in IT for the past 16 years working with many different technologies and in many different roles. Today my main focus is on moving a CMS 2002 / SharePoint 2003 solution to SharePoint 2007.
View my complete profile