Friday 5 September 2008

Creating Custom Meeting Workspaces Site Definitions

My college was working on a project that needed to create a custom Meeting Workspace site definition.

The simple answer would be to create a copy of the site definition and work from there
However as quite often SharePoint is not quite so simple.

The problem comes in the form of 3 web parts embedded in the MasterPage. These web parts check to see that the webTemplateId of the current SPWeb object is equal to 2 (which is the ID of the MPS templates In the webtemp.xml). These web parts then fail to render breaking the site.

He had located the problem but was unable to find a acceptable workaround.
1) He could modify the OotB MPS Site Definition (This is NOT acceptable practice)
2) He could use a feature stapler to modify the Site Definition on creation(This would apply to all new Meeting Workspace across the Farm and not really practical given the project)
3) he could remove the Web Parts and replace them with custom ones (the classic developer answer but never a practical one)

None of these option were really viable for his project.

So I asked him if he had thought of using a SPWebProvisioningProvider class.
With a little testing we found that the SPWebProvisioningProvider does not change the WebTemplateID as the Site Definition used during the provisioning of the site is still the OotB MPS Definition and the Provisioner runs code After the site had been created.

He can now create a Customised Meeting Workspace site definition.
the SPWebProvisioningProvider class he is using is quite small as all it does is activate and deactivate a few features, this allows for easy modification of the features rather than having to recompile the provisioner should the client wish to change anything.

Just something that popped up today :)

Creating and attaching Information Management Policies

I haven't posted anything for a while and here is something I meant to post
Sorry for the formatting issues with the code Hope this Overview helps.

Background information about information Policies
Polices are defined in xml markup
A skeleton Policy described in xml.

<p:Policy xmlns:p="office.server.policy" id="" local="true">
<p:Name></p:Name>
<p:Description></p:Description>
<p:Statement></p:Statement>
<p:PolicyItems>
<p:PolicyItem featureId="">
<p:Name></p:Name>
<p:Description></p:Description>
<p:CustomData>
<data>
</data>
</p:CustomData>
</p:PolicyItem>
</p:PolicyItems>
</p:Policy>

Note the Policy id attribute takes a string that uniquely identifies this policy
Example “Microsoft.Office.RecordsManagement.Policy”

Each policy contains a number of Policy Items
A Microsoft.Office.RecordsManagement.PolicyFeatures.Expiration Policy Item defined in xml.

<p:PolicyItem featureId="Microsoft.Office.RecordsManagement.PolicyFeatures.Expiration">
<p:Name>Expiration</p:Name>
<p:Description>
Automatic scheduling of content for processing, and expiry of content
that has reached its due date.
</p:Description>
<p:CustomData>
<data>
<formula id="Microsoft.Office.RecordsManagement.PolicyFeatures.Expiration.Formula.BuiltIn">
<number>0</number>
<property>Created</property>
<period>days</period>
</formula>
<action type="workflow" id="" />
</data>
</p:CustomData>
</p:PolicyItem>

These Policy items are used to configure the Policy Features for this Policy.
See Stegeman Blog for more information on how to create your own custom Policy Features.

Managing Information Management Policies via Code
Creating a Information Management Policy
The Policy needs to be defined in xml markup. This definition can then be passed to the
Policy.ValidateManifest () method that will validate the XML against the Policy Xml schema.
Once the policy has been validated it can be added to the policy collection using the PolicyCollection.Add () method

Example

string xmlManifest = generatePolicyXML(policyId,
Name,
string.Empty,
string.Empty,
FieldName,
WorkflowAssociationId);

Policy.ValidateManifest(xmlManifest);

PolicyCollection.Add(site, xmlManifest);

In this example I am generating the XML Manifest using a method and passing it various parameters once we have the xmlManifest string populated I pass it for validation to the Policy.ValidateManifest (string) method (Note this will through an exception that explains why it is unable to validate the XML) and then I add it to the Site via the PolicyCollection.Add (SPSite, string)

Retrieving a Information management Policy for a Site
To retrieve an Information Management Policy for a site we need to first get the Policy Catalog for that site and then retrieve the Policy from the catalog. We retrieve the Policy Catalog by creating a new PolicyCatalog object passing the SPSite object to the Constructor. We can then access the PolicyList collection of the new PolicyCatalog object to retrieve the Policy.

Example

using (SPSite site = new SPSite(siteId))
{
PolicyCatalog policyCatalog = new PolicyCatalog(site);
Policy returnPolicy = policyCatalog.PolicyList[policyId];

if (returnPolicy != null)
{
//Code to run aganst the new Policy Object …
}
}

In this example I am creating the SPSite object from a Guid that is the Id for the site. Once I have the site object I pass it to the PolicyCatalog Constructor to create a new PolicyCatalog object that relates to the site. Once I have the PolicyCatalog for the site I can use its PolicyList Collection to retrieve a Policy using the Policy’s Id. Note that if a Policy does not exist with the supplied Policy ID then the Collection returns null so I check the returned Policy is not null before running code against it.

Attaching a Information Management Policy to a Content Type
To attach a Site Information Management Policy to a Content Type, we first need to fetch the site policy and then attach it to the Content Type using the Policy.CreatePolicy () passing it the Policy and Content Type.

Example

using (SPWeb web = site.OpenWeb())
{
SPContentType contentType = web.ContentTypes[contentTypeId];
if (contentType != null)
{
Policy.CreatePolicy(contentType, policy);
}
}

In this example I fetch the SPWeb Object and use it to fetch the Content Type with the supplied Content Type ID. Once I have the Content Type I check that that the returned value is not null and then I pass it to the Policy.CreatePolicy (SPContentType, Policy). Note a content type can only have one policy attached at any time and it may be best to check that the content type does not have a policy attached by testing that the Policy.GetPolicy (SPContentType) method returns null and not a Policy object else the Policy.CreatePolicy () method will through an exception.

Configuring the Expiration PolicyFeature

Creating an Expiration PolicyFeature Formula
The Expiration PolicyFeature has a custom data node that defines the when the item will expire. This is stored in the formula node.

Example

<formula id="Microsoft.Office.RecordsManagement.PolicyFeatures.
Expiration.Formula.BuiltIn">
<number>0</number>
<property>ERMExpiryDate</property>
<period>days</period>
</formula>


The Formula id Points the out of the box, built in formula
Number refers to the number of units to append to the date
Property refers to the Field in the item to retrieve the date from
Period refers to the unit that the number represents.
There are 3 period options: days, months, years.

Creating an Expiration PolicyFeature Action that calls a Workflow
The Expiration PolicyFeature has a custom data node that defines what action should be taken once the Item has expired. We want to start off a workflow so our XML looks like this

Example

<action type="workflow" id="feebb1e3-68f0-40ce-8dc0-e297814680ab" />


I define the type of action to be workflow and set the id to point the ParentAssociationId of the Content Types Workflow Association.
Setting this ID is one of the resigns that have not been able to get around in CAML.
This Guid is neither the Workflow Associations Id nor the Workflow Template ID but the Parent Association Id of the Workflow association.

Resources
MSDN
Introduction to Information Management Policy
Policy Schema Overview
Private Blogs
MOSS Custom policies part 1 - Creating a custom information management policy

Wednesday 16 January 2008

SPFieldCollection indexer (Field[string]) Vs SPFieldCollection. GetField(string)

The Question can be asked don’t they do the same thing and reading the SDK doesn’t clear that up (see http://msdn2.microsoft.com/en-us/library/microsoft.sharepoint.spfieldcollection.aspx )
So a bit of work with Reflector and the difference becomes apparent

The Indexer calls the internal method GetFieldByDisplayName(strDisplayName, true)
Which in turn accesses the Display Name Hashtable using the parameter as the key value DisplayNameDict[strDisplayName];
(Note the second Pramater of GetFieldByDisplayName is a bool which sets if the method will throw any exceptions)
GetField(string) calls GetField(strName, true) which in turn will call GetFieldByInternalName(strName, false) which accesses the Internal Name Hashtable using the parameter as the key value InternalNameDict[strName] if this fails to return a value it accesses the InternalNameWithPrefixDict Hashtable. If GetFieldByInternalName fails to return a value GetField will make a call to GetFieldByDisplayName(strName, false)

In practice how does this help
The Indexer only works with Display Names while the GetField() will look for the internal name first but if it can’t find a field it will then try the parameter as the display name. This matches up nicely with the ContainsField(string) method as this method will return true if the string is found as ether a display name or a internal name. Using this we have a nice way of avoiding unnecessary exception handling when accessing List data

Eg
if (item.Fields.ContainsField(_fieldName))
{
holderstring = item[_fieldName].ToString();
}

Not checking to see if the field exists can cause a System.NullReferenceException - Object reference not set to an instance of an object on the ToString() method

Note there is also a GetFieldByInternalName(string) which will only call GetFieldByInternalName(strName, false)

Been a while

It has been a while (6 months) since my last post and in that time I have acquired ownership of some rather messy projects (Please I am begging people don’t modify out of the box files even if you think you know what you are doing because most likely YOU DONT …. ok maybe Application.master and the like but then again only if absolutely necessary and you and your client understand what Microsoft has to say http://blogs.msdn.com/sharepoint/archive/2007/10/30/kb-944105-how-to-customize-application-pages-in-the-layouts-folder-in-wss-3-0-and-moss-2007.aspx ) and passed my MCTS - WSS 3.0 Application Development gotten engaged and learnt a lot about WSS and MOSS and to be honest ASP.NET

I will try and Blog all of what I have discovered and where I went badly wrong over the next few months and return what I can to the SharePoint community.
Hope to post more as time goes on :)

PS I would like to see more internet facing WSS 3/Moss sites on the web they are easy to develop and even easier to maintain for the non technical minded.