Custom XSLT Functions in SharePoint
Wouldn’t it be good if SharePoint would allow the registration of custom functions that could be used in XSLT stylesheets? Well, as it happens, it does!
Some of the classes that allow XSLT processing – DataFormWebPart, ContentByQueryWebPart - have a virtual method called ModifyXsltArgumentList that can be used for registering custom functions. Sadly, XsltListViewWebPart, XlstListFormWebPart, among others, are sealed, and thus do not allow this.
Let’s write a class that inherits from DataFormWebPart (you could do the same for ContentByQueryWebPart) and register our own functions:
1: public class CustomDataFormWebPart : DataFormWebPart
2: {
3: protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
4: {
5: argList.AddExtensionObject("urn:MyFunctions", new MyFunctions());
6: base.ModifyXsltArgumentList(argList);
7: }
8: }
Let’s forget about the MyFunctions class and pretend that it has a few public instance methods that we want to expose to SharePoint. Mind you, because this class will be instantiated in a SharePoint context, it will have access to SPContext.Current, and from here, to the whole of SharePoint!
In a XSLT script, we need something like this in order to access our extension functions:
1: <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:dsp="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal" xmlns:my="urn:MyFunctions">
2: ...
3: </xsl:stylesheet>
Did you notice xmlns:my=”urn:MyFunctions”? This should be identical to the namespace that you used inside ModifyXsltArgumentList.
And to call one of the functions:
1: <xsl:value-of select="my:Function()"/>
Again, you need to use the same prefix, my in this case. You can add parameters, as long as they are of base types (strings, number, date/time, booleans, and so on), and the same goes for the result type.
I hear you say: “OK, but now I have to replace all declarations of DataFormWebPart for this new CustomDataFormWebPart!”. Not really, dear reader, ASP.NET offers a great solution for this: tag mapping. In a nutshell, we can ask ASP.NET to replace all instances of a control for another class that inherits from the control’s class.
Now, we could this in several ways, but the best one is to have a feature targeting a WebApplication with a feature receiver that performs a Web.config modification:
1: public override void FeatureActivated(SPFeatureReceiverProperties properties)
2: {
3: var tagMappingDataFormWebPartModification = new SPWebConfigModification();
4: tagMappingDataFormWebPartModification.Path = "configuration/system.web/pages/tagMapping";
5: tagMappingDataFormWebPartModification.Name = "tagMappingDataFormWebPartModification";
6: tagMappingDataFormWebPartModification.Sequence = 0;
7: tagMappingDataFormWebPartModification.Owner = "WebConfigModifications";
8: tagMappingDataFormWebPartModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
9: tagMappingDataFormWebPartModification.Value = String.Format("<add tagType=\"{0}\" mappedTagType=\"{1}\"/>", typeof(DataFormWebPart).FullName, typeof(CustomDataFormWebPart).AssemblyQualifiedName);
10:
11: var webApp = properties.Feature.Parent as SPWebApplication;
12: webApp.WebConfigModifications.Add(tagMappingDataFormWebPartModification);
13: webApp.Update();
14: webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
15: }
When this feature is activated, it adds a line in the Web.config file, tagMapping section, that tells ASP.NET to replace DataFormWebPart for CustomDataFormWebPart in all markup declarations. Of course, it won’t affect code: new DataFormWebPart() will still return a DataFormWebPart!
All is not done, yet. We still need to mark our CustomDataFormWebPart control as safe for declaration in web pages. We do that by adding an entry to the SafeControls section of the Web.config. For a change, I’m going to do this in the SharePoint Solution Manifest, Package.xml:
1: <Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="e860c419-b399-497d-a313-545f994753c8" SharePointProductVersion="15.0">
2: <Assemblies>
3: <Assembly Location="$SharePoint.Project.AssemblyFileName$" DeploymentTarget="GlobalAssemblyCache">
4: <SafeControls>
5: <SafeControl Assembly="$SharePoint.Project.AssemblyFullName$" Namespace="MyNamespace" TypeName="*" Safe="True" />
6: </SafeControls>
7: </Assembly>
8: </Assemblies>
9: <FeatureManifests>
10: <FeatureManifest Location="MyFeature\Feature.xml" />
11: </FeatureManifests>
12: </Solution>
Notice the $SharePoint.Project.AssemblyFileName$ and $SharePoint.Project.AssemblyFullName$, these are two examples of tokens that will be replaced by Visual Studio when it deploys the solution.
And that’s it! Your custom XSLT extension functions are ready to use!