Hi all! Today I’m going to show example of how to write clean and understandable code when using feature upgrade. Of course, solution which we are using is not perfect, but it rather solid, easy to use, easy to read, easy to extend. If you have any suggestions, improvements, or event better and cooler solution – you are welcome in comments :). Ok, lets start.
You are already know, that with sharepoint 2010 you can upgrade your features by adding this sample xml:
<UpgradeActions>
<VersionRange BeginVersion="0.0.0.0" EndVersion="0.0.0.09">
<CustomUpgradeAction Name="SomeActionName">
<Parameters>
<Parameter Name="TitlePrefix">Upgrade Time:</Parameter>
</Parameters>
</CustomUpgradeAction>
</VersionRange>
</UpgradeActions>
CustomUpgradeAction is not the only allowed tag inside VersionRange, but I’m going to focus on it, because with this tag we can specify actions, which invokes by code. To use above action you need to specify upgrade actions receiver and override FeatureUpgrading method. Very straightforward and simple implementation of this method for our above example can looks like this one:
public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters)
{
switch (upgradeActionName)
{
case "SomeActionName":
var web = properties.Feature.Parent as SPWeb;
var list = web.Lists["TestUpgradeList"];
var item = list.Items.Add();
item["Title"] = parameters["TitlePrefix"] + DateTime.Now.ToShortTimeString();
item.Update();
break;
case "AnotherAction":
//do other stuff
break;
}
}
Pretty cool, but what if you have 20-30 different actions? Or even more. Some of them may be really complicated. Your code become bigger and bigger, harder to read, maintain and understand. And one day you have to think about how to refactor it.
Possible solution is to use separate class for every upgrade action, pass full upgrade action class name as upgradeActionName parameter. In FeatureUpgrading you need to construct class object by its name using reflection (factory pattern helps us in this task) and execute action method finally. Enough theory, here is an implementation.
First of all we need to modify feature xml file to pass full class name as upgrade action Name:
<UpgradeActions>
<VersionRange BeginVersion="0.0.0.0" EndVersion="0.0.0.09">
<CustomUpgradeAction Name="UpgradeFeatureVS.Upgrade.TestAction, $SharePoint.Project.AssemblyFullName$">
<Parameters>
<Parameter Name="TitlePrefix">Upgrade Time:</Parameter>
</Parameters>
</CustomUpgradeAction>
</VersionRange>
</UpgradeActions>
NOTE: as you can see, I use visual studio replaceable parameter to automatically generate assembly name. It means that my class TestAction is in sharepoint project, if it’s not true for you, you need to specify full four-part assembly name instead of replaceable parameter.
Now we have full class name as name of the CustomUpgradeAction. How FeatureUpgrading is changed:
public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters)
{
var action = UpgradeActionsFactory.Resolve(upgradeActionName);
action.Execute(properties, parameters);
}
UpgradeActionsFactory implementation:
public static class UpgradeActionsFactory
{
public static BaseAction Resolve(string typeName)
{
if(string.IsNullOrEmpty(typeName))
{
throw new ArgumentException("Type name can not be null", "typeName");
}
var type = Type.GetType(typeName);
if(type == null)
{
throw new Exception(string.Format("Can not resolve type from '{0}'", typeName));
}
return (BaseAction)Activator.CreateInstance(type);
}
}
and BaseAction:
public abstract class BaseAction
{
private IDictionary<string, string> parameters;
public virtual void Execute(SPFeatureReceiverProperties properties, IDictionary<string, string> actionParameters)
{
try
{
parameters = actionParameters;
Action(properties);
}
catch (Exception ex)
{
//perform some logging here
throw;
}
}
protected string GetParameter(string parameterName)
{
if(!parameters.ContainsKey(parameterName))
{
throw new ArgumentException("Upgrade Action: parameter not found", parameterName);
}
return parameters[parameterName];
}
protected abstract void Action(SPFeatureReceiverProperties properties);
}
Now, to implement some action, you need to derive your class from BaseAction and implement Action method. I specified UpgradeFeatureVS.Upgrade.TestAction class in xml schema, so this is implementation:
public class TestAction : BaseAction
{
protected override void Action(SPFeatureReceiverProperties properties)
{
var web = properties.Feature.Parent as SPWeb;
var list = web.Lists["TestUpgradeList"];
var item = list.Items.Add();
item["Title"] = GetParameter("TitlePrefix") + DateTime.Now.ToShortTimeString();
item.Update();
}
}
Pros of using this approach:
- code become more readable
- easy to extend and to add new actions
- you can write reusable actions specifying different parameters
Cons of using this approach:
- one class for each action. Upgrade classes count may grow highly, but you can place it in different folders as an option
And the last note about preceding code. Consider Execute method in BaseAction class – it wraps with try –catch and in catch block exception is re-thrown. This is notable thing. Depending on how do you call feature Upgrade method, your feature version will may be changed or not. Look at msdn: Upgrade method accepts bool force parameter. If you pass true, feature will be upgraded despite the possible exceptions (upgraded means feature version will be changed, and if this exceptions is not catastrophic that stops upgrade at all, msdn is not clear in this question), if you pass false and at least one exception will be thrown by your code, upgrade will fail and feature version won’t changed. Some of the upgrade actions may be executed, but version remains old (and no rollback of course). If you want to always change feature version after upgrade(despite the exceptions in your code), you need to remove exception re-thrown in Execute method, sometimes this not desired behavior, but it depends on requirements. If you leave exception re-thrown, you can still control upgrade passing true or false as force parameter.
Demo project can be downloaded here (11.26 kb). To test it you need to install initial solution using default deployment configuration, then switch to UpgradeFeature (that I described previously), increase feature version and click deploy. Item should be added in the list.
That’s it, and as usual good luck in spdevelopment!