SharePoint Design Patterns: Entry 2.5
In the previous entry, we looked at how we can model a SharePoint list item using a more domain specific model to simplify programmatic access to the list item thus reducing otherwise error prone data access code and making the overall framework easier to use. Again: the idea is to promote reuse and decrease complexity through domain specific code that abstracts the underlying SharePoint object models, making it easier for a team to build functionality on top of this framework.
One interesting point is that if you’re already building your fields and content types using features XML, the work required to generate the domain specific wrappers can be simplified dramatically using automation. In a sense, a content type is basically a class (this is generally how I map them in my domain design); why double your effort and write both the content types and the classes?
So how do we go about this?
(Side note: this isn’t so much a “design pattern” as it is an “implementation pattern”)
As an example, here is a simple XML file which defines a set of fields and content types which use those fields:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;"><?xml </span><span style="color: #ff0000;">version</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"1.0" </span><span style="color: #ff0000;">encoding</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"utf-8" </span><span style="color: #800080;">?</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #808000;">Elements </span><span style="color: #ff0000;">xmlns</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.microsoft.com/sharepoint/"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">Field </span><span style="color: #800080;">DisplayName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Model Code"</span> <span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Model_Code"</span> <span style="color: #800080;">StaticName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Model_Code"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000001}"</span> <span style="color: #800080;">Type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Integer"</span> <span style="color: #800080;">SourceID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.someusedcarinventory.com"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Columns"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">Field </span><span style="color: #800080;">DisplayName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"VIN"</span> <span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"VIN"</span> <span style="color: #800080;">StaticName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"VIN"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000002}"</span> <span style="color: #800080;">Type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Text"</span> <span style="color: #800080;">SourceID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.someusedcarinventory.com"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Columns"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">Field </span><span style="color: #800080;">DisplayName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Make"</span> <span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Make"</span> <span style="color: #800080;">StaticName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Make"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-00000000003}"</span> <span style="color: #800080;">Type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Text"</span> <span style="color: #800080;">SourceID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.someusedcarinventory.com"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Columns"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">ContentType </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Vehicle"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"0x0100FC000000000000000000000000000001"</span> <span style="color: #800080;">Description</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Used car inventory"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Content Types" </span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRefs</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"ContentType" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Title" </span> <span style="color: #800080;">Required</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"TRUE" </span><span style="color: #800080;">ShowInNewForm</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"TRUE" </span><span style="color: #800080;">ShowInEditForm</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"TRUE" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000001}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Model_Code"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000002}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"VIN"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000003}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Make"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">FieldRefs</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">ContentType</span><span style="color: #0000ff;">></span><span style="color: #000000;"> </span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">Field </span><span style="color: #800080;">DisplayName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership Code"</span> <span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership_Code"</span> <span style="color: #800080;">StaticName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership_Code"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-00000000004}"</span> <span style="color: #800080;">Type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Integer"</span> <span style="color: #800080;">SourceID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.someusedcarinventory.com"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Columns"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">Field </span><span style="color: #800080;">DisplayName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership Fax Number"</span> <span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership_Fax_Number"</span> <span style="color: #800080;">StaticName</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership_Fax_Number"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-00000000005}"</span> <span style="color: #800080;">Type</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Text"</span> <span style="color: #800080;">SourceID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.someusedcarinventory.com"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Columns"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">ContentType </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership"</span> <span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"0x0100FC000000000000000000000000000002"</span> <span style="color: #800080;">Description</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealerships"</span> <span style="color: #800080;">Group</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"My Custom Content Types" </span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRefs</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"ContentType" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Title" </span> <span style="color: #800080;">Required</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"TRUE" </span><span style="color: #800080;">ShowInNewForm</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"TRUE" </span><span style="color: #800080;">ShowInEditForm</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"TRUE" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000004}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership_Code"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">FieldRef </span><span style="color: #800080;">ID</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000005}" </span><span style="color: #800080;">Name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"Dealership_Fax_Number"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">FieldRefs</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">ContentType</span><span style="color: #0000ff;">></span><span style="color: #000000;"> </span> <span style="color: #0000ff;"></</span><span style="color: #808000;">Elements</span><span style="color: #0000ff;">></span></span> |
When approaching this problem, I considered three ways of handling the class file generation:
- Use an object model and StringTemplate to create .cs files. This invovled writing POCO classes (or generating them from the schema) which I could deserialize the XML to and then passing those objects to a template instance. This seemed like too much work, given that I really didn’t feel like maintaining all of that code as well. Plus, while StringTemplate isn’t – by any sense of the imagination – hard, it is a non-standard syntax that someone would have to learn to maintain and/or extend the conversion.
- Use an XDocument and CodeDom to create .cs files. This seemed like even more work! While it’s framework supported, I feel like this solution would be hard to extend and maintain for most developers.
- Use an XSL transform to create .cs files. This seemed to be the most natural solution given that the source file is already in XML format and a the target content structure is far from complex (the basic class file is fairly simple). Plus, while XSLT isn’t trivial, it’s not that hard either (and the syntax is “standard”).
One of the cool features of XSL 2.0 is the xsl:result-document element which allows you to create multiple documents from one source document. Only one problem: .NET’s XSLT engine doesn’t implement XSLT 2.0! What a bummer; it seemed like if I wanted to get this to work and generate multiple output files, it was going to take some work in code or find an XSLT 2.0 capable processor.
Enter Saxon, which provides an XSLT 2.0 processor for .NET. The following code takes the XML above and uses the xsl:result-document to create two class files, one for each content type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;">using </span><span style="color: #008080;">System</span><span style="color: #000000;">;</span> <span style="color: #0000ff;">using </span><span style="color: #008080;">System</span><span style="color: #000000;">.</span><span style="color: #008080;">IO</span><span style="color: #000000;">;</span> <span style="color: #0000ff;">using </span><span style="color: #000000;">Saxon.Api;</span></span> <span style="font-family: Lucida Console;"><span style="color: #0000ff;">namespace </span><span style="color: #000000;">FeatureToClass</span> <span style="color: #000000;">{</span> <span style="color: #0000ff;">internal class </span><span style="color: #000000;">Program</span> <span style="color: #000000;">{</span> <span style="color: #0000ff;">private static void </span><span style="color: #000000;">Main(</span><span style="color: #0000ff;">string</span><span style="color: #000000;">[] args)</span> <span style="color: #000000;">{</span> <span style="color: #808000;">Uri </span><span style="color: #000000;">xmlFile = </span><span style="color: #0000ff;">new </span><span style="color: #808000;">Uri</span><span style="color: #000000;">(</span> <span style="color: #000000;">@</span><span style="color: #ff00ff;">"C:\Users\Charles\Desktop\elements.xml"</span><span style="color: #000000;">);</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">// Create a Processor instance. </span> <span style="color: #000000;">Processor p = </span><span style="color: #0000ff;">new </span><span style="color: #000000;">Processor();</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">// Load the source document. </span> <span style="color: #000000;">XdmNode node = p.NewDocumentBuilder().Build(xmlFile);</span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">using </span><span style="color: #000000;">(</span><span style="color: #808000;">Stream </span><span style="color: #000000;">stream = </span><span style="color: #808000;">File</span><span style="color: #000000;">.OpenRead(</span><span style="color: #ff00ff;">"core-transform.xslt"</span><span style="color: #000000;">))</span> <span style="color: #000000;">{</span> <span style="color: #008000;">// Create a transformer for the stylesheet. </span> <span style="color: #000000;">XsltTransformer transformer = </span> <span style="color: #000000;">p.NewXsltCompiler().Compile(stream).Load();</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">// Set the root node of the source document</span> <span style="color: #008000;">// to be the initial context node. </span> <span style="color: #000000;">transformer.InitialContextNode = node;</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">// BaseOutputUri is only necessary for xsl:result-document. </span> <span style="color: #000000;">transformer.BaseOutputUri = xmlFile;</span></span> <span style="font-family: Lucida Console;"> <span style="color: #000000;">transformer.SetParameter(</span> <span style="color: #0000ff;">new </span><span style="color: #000000;">QName(</span><span style="color: #ff00ff;">"ct"</span><span style="color: #000000;">, </span><span style="color: #ff00ff;">"http://www.customtransform.com"</span><span style="color: #000000;">, </span><span style="color: #ff00ff;">"namespace"</span><span style="color: #000000;">), </span> <span style="color: #0000ff;">new </span><span style="color: #000000;">XdmAtomicValue(</span><span style="color: #ff00ff;">"My.Custom.Package"</span><span style="color: #000000;">));</span></span> <span style="font-family: Lucida Console;"> <span style="color: #008000;">// Create a serializer. </span> <span style="color: #000000;">Serializer serializer = </span><span style="color: #0000ff;">new </span><span style="color: #000000;">Serializer();</span> <span style="color: #000000;">transformer.</span><span style="color: #808000;">Run</span><span style="color: #000000;">(serializer);</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span></span> |
The code above is a simple console program that takes a (hardcoded) path to a source XML file (a SharePoint elements.xml file) and (hardcoded) namespace and loads an XSL file to transform the XML to C# class files.
Here’s the transform (it’s a bit messy for output formatting reasons, so you’re best off copying it into an XML aware text editor to get a better view):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;"><?xml </span><span style="color: #ff0000;">version</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"1.0" </span><span style="color: #ff0000;">encoding</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"UTF-8"</span><span style="color: #800080;">?</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><!DOCTYPE </span><span style="color: #800080;">stylesheet </span><span style="color: #000000;">[</span> <span style="color: #000000;"><</span><span style="color: #800080;">!ENTITY space </span><span style="color: #000000;">"<</span><span style="color: #800080;">xsl:text</span><span style="color: #0000ff;">></span><span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:text</span><span style="color: #0000ff;">></span><span style="color: #000000;">"></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">!ENTITY </span><span style="color: #800080;">cr </span><span style="color: #000000;">"<</span><span style="color: #800080;">xsl:text</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:text</span><span style="color: #0000ff;">></span><span style="color: #000000;">"></span> <span style="color: #000000;">]></span></span> <span style="font-family: Lucida Console;"><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:stylesheet</span> <span style="color: #800080;">xmlns:xsl</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://www.w3.org/1999/XSL/Transform"</span> <span style="color: #800080;">xmlns:sp</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://schemas.microsoft.com/sharepoint/"</span> <span style="color: #800080;">xmlns:xs</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://www.w3.org/2001/XMLSchema"</span> <span style="color: #800080;">xmlns:functx</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://www.functx.com"</span> <span style="color: #800080;">xmlns:ct</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://www.customtransform.com"</span> <span style="color: #ff0000;">version</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"2.0"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:param </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"ct:namespace"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:output </span><span style="color: #800080;">method</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"text"</span><span style="color: #0000ff;">/></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:template </span><span style="color: #800080;">match</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"/"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:for-each </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"//sp:ContentType"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:variable </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"classname" </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"replace(@Name, ' ', '')"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:variable </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"filename" </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"concat($classname,'.cs')" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$filename" </span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #008000;"><!-- Creating --></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:result-document </span><span style="color: #800080;">href</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"{$filename}"</span><span style="color: #0000ff;">></span> <span style="color: #000000;">using System;</span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> namespace </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$ct:namespace"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> {</span> <span style="color: #000000;"> public partial class </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$classname"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> {</span> <span style="color: #000000;"> private string _contentTypeId;</span> <span style="color: #000000;"> </span> <span style="color: #000000;"> public string ContentTypeId {</span> <span style="color: #000000;"> get { return _contentTypeId; }</span> <span style="color: #000000;"> set { _contentTypeId = value; }</span> <span style="color: #000000;"> }</span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> public </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$classname"</span><span style="color: #0000ff;">/></span><span style="color: #000000;">()</span> <span style="color: #000000;"> {</span> <span style="color: #000000;"> _contentType = "</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">'@Name'</span><span style="color: #0000ff;">/></span><span style="color: #000000;">";</span> <span style="color: #000000;"> _contentTypeId = "</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">'@ID'</span><span style="color: #0000ff;">/></span><span style="color: #000000;">";</span> <span style="color: #000000;"> }</span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:apply-templates</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> }</span> <span style="color: #000000;">}</span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:result-document</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:for-each</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:template</span><span style="color: #0000ff;">></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #008000;"><!--///</span> <span style="color: #008000;"> Templates</span> <span style="color: #008000;"> ///--></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:template </span><span style="color: #800080;">match</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"sp:FieldRef"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:variable </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"fieldname" </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"functx:lower-first(replace(@Name, '_', ''))"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:variable </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"fieldid" </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"@ID"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:variable </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"dotnettype"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:choose</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:when </span><span style="color: #800080;">test</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"//sp:Field[(@ID = $fieldid) and (@Type = 'Integer')]"</span><span style="color: #0000ff;">></span><span style="color: #000000;">int</span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:when</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:otherwise</span><span style="color: #0000ff;">></span><span style="color: #000000;">string</span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:otherwise</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:choose</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:variable</span><span style="color: #0000ff;">></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> private </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$dotnettype"</span><span style="color: #0000ff;">/></span><span style="color: #000000;"> _</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$fieldname" </span><span style="color: #0000ff;">/></span><span style="color: #000000;">;</span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:call-template </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"attribute"</span><span style="color: #0000ff;">><</span><span style="color: #808000;">xsl:with-param </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"fieldid" </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$fieldid"</span><span style="color: #0000ff;">/></</span><span style="color: #808000;">xsl:call-template</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> public </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$dotnettype"</span><span style="color: #0000ff;">/></span><span style="color: #000000;">&space;</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"replace(@Name, '_', '')"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> {</span> <span style="color: #000000;"> get { return _</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$fieldname" </span><span style="color: #0000ff;">/></span><span style="color: #000000;">; }</span> <span style="color: #000000;"> set { _</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"$fieldname" </span><span style="color: #0000ff;">/></span><span style="color: #000000;"> = value; }</span> <span style="color: #000000;"> }</span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:template</span><span style="color: #0000ff;">></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:template </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"attribute"</span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:param </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"fieldid"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:if </span><span style="color: #800080;">test</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"exists(//sp:Field[@ID = $fieldid]/@ID)"</span><span style="color: #0000ff;">></span><span style="color: #000000;">[Caml(Id="</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">'//sp:Field[@ID = $fieldid]/@ID'</span><span style="color: #0000ff;">/></span><span style="color: #000000;">", StaticName="</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">'//sp:Field[@ID = $fieldid]/@StaticName'</span><span style="color: #0000ff;">/></span><span style="color: #000000;">", Type="</span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:value-of </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">'//sp:Field[@ID = $fieldid]/@Type'</span><span style="color: #0000ff;">/></span><span style="color: #000000;">")]</span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:if</span><span style="color: #0000ff;">></</span><span style="color: #808000;">xsl:template</span><span style="color: #0000ff;">></span></span> <span style="font-family: Lucida Console;"><span style="color: #000000;"> </span><span style="color: #008000;"><!--///</span> <span style="color: #008000;"> Custom functions</span> <span style="color: #008000;"> ///--></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:function</span> <span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"functx:lower-first" </span><span style="color: #800080;">as</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"xs:string"</span> <span style="color: #800080;">xmlns:functx</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"http://www.functx.com" </span><span style="color: #0000ff;">></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:param </span><span style="color: #ff0000;">name</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"arg" </span><span style="color: #800080;">as</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"xs:string"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"><</span><span style="color: #808000;">xsl:sequence </span><span style="color: #800080;">select</span><span style="color: #0000ff;">=</span><span style="color: #ff00ff;">"concat(lower-case(substring($arg,1,1)), substring($arg,2))"</span><span style="color: #0000ff;">/></span> <span style="color: #000000;"> </span><span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:function</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #808000;">xsl:stylesheet</span><span style="color: #0000ff;">></span></span> |
I borrowed one function from the FunctX library to create the camelCased field names. The XSL probably isn’t nearly as clean or optimized as it should be (my XSL is admittedly a bit rusty), but it gets the job done. Here’s one of the two classes (and class files) which get generated at the source directory of the input XML file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<span style="font-family: Lucida Console;"><span style="color: #0000ff;">using </span><span style="color: #008080;">System</span><span style="color: #000000;">;</span></span> <span style="font-family: Lucida Console;"><span style="color: #0000ff;">namespace </span><span style="color: #000000;">My.Custom.</span><span style="color: #808000;">Package </span> <span style="color: #000000;">{</span> <span style="color: #0000ff;">public </span><span style="color: #000000;">partial </span><span style="color: #0000ff;">class </span><span style="color: #000000;">Dealership</span> <span style="color: #000000;">{</span> <span style="color: #0000ff;">private string </span><span style="color: #000000;">_contentTypeId;</span> <span style="color: #0000ff;">public string </span><span style="color: #000000;">ContentTypeId {</span> <span style="color: #000000;">get { </span><span style="color: #0000ff;">return </span><span style="color: #000000;">_contentTypeId; }</span> <span style="color: #000000;">set { _contentTypeId = value; }</span> <span style="color: #000000;">} </span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">public </span><span style="color: #000000;">Dealership()</span> <span style="color: #000000;">{</span> <span style="color: #000000;">_contentType = </span><span style="color: #ff00ff;">"Dealership"</span><span style="color: #000000;">;</span> <span style="color: #000000;">_contentTypeId = </span><span style="color: #ff00ff;">"0x0100FC000000000000000000000000000002"</span><span style="color: #000000;">;</span> <span style="color: #000000;">}</span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">private string </span><span style="color: #000000;">_contentType;</span> <span style="color: #0000ff;">public string </span><span style="color: #000000;">ContentType</span> <span style="color: #000000;">{</span> <span style="color: #000000;">get { </span><span style="color: #0000ff;">return </span><span style="color: #000000;">_contentType; }</span> <span style="color: #000000;">set { _contentType = value; }</span> <span style="color: #000000;">} </span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">private string </span><span style="color: #000000;">_title;</span> <span style="color: #0000ff;">public string </span><span style="color: #000000;">Title</span> <span style="color: #000000;">{</span> <span style="color: #000000;">get { </span><span style="color: #0000ff;">return </span><span style="color: #000000;">_title; }</span> <span style="color: #000000;">set { _title = value; }</span> <span style="color: #000000;">}</span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">private int </span><span style="color: #000000;">_dealershipCode;</span> <span style="color: #000000;">[Caml(Id=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000003}"</span><span style="color: #000000;">, </span> <span style="color: #000000;">StaticName=</span><span style="color: #ff00ff;">"Dealership_Code"</span><span style="color: #000000;">, </span><span style="color: #808000;">Type</span><span style="color: #000000;">=</span><span style="color: #ff00ff;">"Integer"</span><span style="color: #000000;">)]</span> <span style="color: #0000ff;">public int </span><span style="color: #000000;">DealershipCode</span> <span style="color: #000000;">{</span> <span style="color: #000000;">get { </span><span style="color: #0000ff;">return </span><span style="color: #000000;">_dealershipCode; }</span> <span style="color: #000000;">set { _dealershipCode = value; }</span> <span style="color: #000000;">}</span></span> <span style="font-family: Lucida Console;"> <span style="color: #0000ff;">private string </span><span style="color: #000000;">_dealershipFaxNumber;</span> <span style="color: #000000;">[Caml(Id=</span><span style="color: #ff00ff;">"{F0000000-0000-0000-0000-000000000004}"</span><span style="color: #000000;">, </span> <span style="color: #000000;">StaticName=</span><span style="color: #ff00ff;">"Dealership_Fax_Number"</span><span style="color: #000000;">, </span><span style="color: #808000;">Type</span><span style="color: #000000;">=</span><span style="color: #ff00ff;">"Text"</span><span style="color: #000000;">)]</span> <span style="color: #0000ff;">public string </span><span style="color: #000000;">DealershipFaxNumber</span> <span style="color: #000000;">{</span> <span style="color: #000000;">get { </span><span style="color: #0000ff;">return </span><span style="color: #000000;">_dealershipFaxNumber; }</span> <span style="color: #000000;">set { _dealershipFaxNumber = value; }</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span> <span style="color: #000000;">}</span></span> |
Awesome! It’s amazing how little code was required to get this basic scenario working.
Now we have a single source which defines our SharePoint artifacts and our code artifacts; I love it. Write your fields and content types in your feature and you get class files for free! You’ll note that I’ve added a simple CamlAttribute where applicable. This will prove handy when it comes time to automate construction of the object instance from a SharePoint list item, which we’ll look at next time (for the time being, feel free to modify the XSL and remove the line for it or write an implementation of CamlAttribute).
Again, to reiterate: the goal is make it easy to build applications on top of a SharePoint deployment by adding a layer of domain specific APIs and objects so that a team can be productive while reducing duplication and the ramp up time required to understand the business domain.
Other points for improvement and enhancement (look for these in a future installment):
- Parameterize the program.
- Consider making it a Visual Studio add-in or a custom tool.
- Make it go the other way; in other words: generate the content type and field XML from class files (which would be cool, too).
But even as it is, it’s incredibly useful. On the next installment, we’ll see how we can build more intelligence into the model and make it more useful.
2 Responses
[…] As a quick summary, it's common knowledge (well, amongst SharePoint developers at least) that you can associate event receivers with a list template type. However, an interviewer recently brought to light that one can also associate an event receiver directly with a content type. This is immensely useful for anyone building custom solutions on SharePoint, especially if you make heavy usage of content types in your design. […]
[…] Design Patterns for SharePoint Part 2.5 […]