Generation of a Composite Model Using Reflection and Sitecore Serialization

Context

In my previous post, I’ve described the benefits of composition pattern over inheritance in code generation and mentioned that it simplifies restoration of models from reflection. From my point, this is a huge plus as it enables the creation of developer friendly modular architecture in Sitecore.

I do not think that someone still has problems with a creation of an update package with Sitecore config transforms, but it is not really possible to install an update package in your Visual Studio solution. Of cause, you can install it in Sitecore, grab all libraries from bin folder and copy to your source, then serialize installed items into your project, generate your models based on “everything” you have in Sitecore.

To me, it doesn’t sound like a good approach, moreover, there are many of solution like NuGet, npm, bower that drastically simplify package management and installation for development. Also, when referencing items from packages, you don’t expect to modify DLL coming with them, so I do not think that you should have to serialize Sitecore items to use them in your model generation.

Most likely you already have information about your templates in compiled into your module DLLs. Below you would see how you could use it.

Approach

Module Preparation

Reflection in .NET gives you an ability to discover information about classes defined in DLLs through its metadata. You could define custom attributes that would mark your classes with template IDs and properties with fields IDs, or if you using GlassMapper you could use attributes defined in it.

[SitecoreType(TemplateId="b820da23-c7f6-4e04-b159-7d804a84a3d8")]
public partial class BasePage : GlassBase, IBasePage
{
   ...
   [SitecoreField("Title")]
   public virtual string Title {get; set;}
   ...

Once you attributes are set, you can compile it and pack your module. Details of packing are not relevant as of now, but for the sake of simplicity, let’s assume that you will just reference this DLL from a folder (in real case scenario it should be done via NuGet)

Reference Module

For code generation, you cannot reference an assembly as you would do this standard C# project. As T4 templates are used, we need to pass a location of the assembly to T4 template somehow.

The best place for that would be a project file, however, once you I’d dug into TDS, that I’m using to generate models, I found out that project file was not available in an item.tt. So I ended up adding project information into a template as a string variable (in a separate post I will show how this could be done automatically).

Once I got the project file location, I could read this file and get custom properties stored there.

Search for this reference

In the project file, I’ve added few elements like CodeGenerationLocations and CodeGenerationFilters. In those elements, I store paths where DLLs might be located separated by pipe and mask for files filtering.


<!--Set paths saparated by pipe-->
<CodeGenerationLocations>d:\sln\project\Debug\bin\|d:\sln\packages\</CodeGenerationLocations>
<!--Set DLL filter separated by pipe. e.g.: Sample1.*.dll|Sample2.*.dll-->
<CodeGenerationFilters>sln.*.dll</CodeGenerationFilters>

Using those variables I iterate over files and get required DLLs.

Identify model files

Once assembly is loaded from a file, it is possible to get all classes or interfaces defined in this assembly and filter required for your base on attributes e.g.


foreach (var type in allTypes)
{
  if (type.IsInterface &&
    type.GetCustomAttributes()
      .Any(
        attr =>
          attr.GetType()
            .FullName.Contains(
              "Glass.Mapper.Sc.Configuration.Attributes.SitecoreTypeAttribute")))
  {
    // create dictionary using types metadata
  }
}

Once type information is gathered, it is possible to use in in template generation.

Generate models and restore base templates

To generate code, you need to wrap code above to a singleton implementation that you call on each item and header templates. Singleton pattern, in this case, will ensure that you initialize code once and not slowing down the generation.


string projectPath = @"path to .scproj file";
CodeGeneration.Manager.Init(projectPath);

The following code pattern will generate a class header, constants for a template and fields declared on the template as well as field properties itself.


<# if (!template.Name.IsInterfaceWord()){ #>
    ///
<summary>
    /// <#= template.Name.AsClassName() #>
    /// <para>Path: <#= template.Path #></para>
    /// <para>ID: <#= template.ID.ToString() #></para>
    /// </summary>

    [SitecoreType(TemplateId="<#= template.ID.ToString() #>")]
    public partial class <#= template.Name.AsClassName() #>  : GlassBase, <#=template.Name.AsInterfaceName()#>
    {

        public const string TemplateIdString = "<#= template.ID.ToString() #>";
        public static readonly ID TemplateId = new ID(TemplateIdString);
        public const string TemplateName = "<#= template.Name #>";
<#   foreach(SitecoreField field in template.GetFieldsForTemplate(false)){#>
        public static readonly ID <#= field.GetPropertyName() #>FieldId = new ID("<#=field.ID.ToString()#>");
        public const string <#= field.GetPropertyName() #>FieldName = "<#=field.Name#>";

<#   }#>
<#   foreach(SitecoreField field in template.GetFieldsForTemplate(false)){#>
    ///
<summary>
    /// The <#=field.Name#> field.
    /// <para>Field Type: <#=field.Type#></para>
    /// <para>Field ID: <#=field.ID.ToString()#></para>
<#     if(!string.IsNullOrEmpty(field.Data)) { #>
    /// <para>Custom Data: <#=field.Data#></para>
<#     }#>
    /// </summary>

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("<#=Tool#>", "<#=ToolVersion#>")]
    [SitecoreField("<#=field.Name#>")]
    public virtual <#=field.GetGlassFieldType()#> <#= field.GetPropertyName() #>  {get; set;}

<#   }#>

After that, we generate properties from base templates known in this TDS project. Those properties are marked with SitecoreSelf attribute, that map the same item as mapped to the model to a property, while the property is actually a type of another autogenerated model.


<#  foreach(var baseTemplate in template.BaseTemplates) { #>
    ///
<summary>
    /// The <#=baseTemplate.Name#> composition field.
    /// </summary>

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("<#=Tool#>", "<#=ToolVersion#>")]
    [SitecoreSelf]
        public virtual <#= baseTemplate.GetNamespace(DefaultNamespace)#>.<#=baseTemplate.Name.AsClassName()#> <#= baseTemplate.Name.AsClassName() #>Comp  {get; set;}

<#  }#>

The final section will use extension method that will load templates that do not exist in current TDS project from a previously prepared dictionary and generate properties base on that.


<#  foreach(var tuple in Model.BaseTemplates()) { #>
    ///
<summary>
    /// The <#=tuple.Item2#> composition field.
    /// <para>Generated from referenced assembly meta information.</para>
    /// </summary>

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("<#=Tool#>", "<#=ToolVersion#>")]
    [SitecoreSelf]
    public virtual <#=tuple.Item1#> <#=tuple.Item2#>Comp  {get; set;}

<#  }#>
  }
<#  }#>

Summary

The approach above allows you to generate code in you project using only meta-information from existing precompiled DLLs with included autogenerated models, constants for templates and fields. It helps to be compliant with an open/close SOLID principle creating modules, by eliminating the need to carry serialized Sitecore or TDS items in packages and preventing changes to a functionality of this package.


Follow me on twitter @true_shoorik. Would be glad to discuss ideas above in comments.

Generation of a Composite Model Using Reflection and Sitecore Serialization

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s