My first exposure to Sitecore and Glass.Mapper was when I was brought on to a Sitecore MVC implementation about 80% complete. The Sitecore lead had implemented Glass and was using it quite extensively. It was not only being used as an ORM, mapping data from Sitecore Items to the Business Model POCOs, but it was also being used to query data from Sitecore. Properties on the POCOs were using attributes to configure Sitecore field types and field names, as well as using Sitecore Query to populate child collections, no matter their relation to the current item or location in the content tree. For example:
[SitecoreType(AutoMap = true, TemplateId = SitecoreTemplateConstants.PageTemplates.ApplicationLandingPageTemplateId)] public class ApplicationLandingPage : PageBase { public const string Slide_Show = "Slide Show"; public const string Application_Landing_Header = "Application Landing Header"; [SitecoreField(FieldName = Slide_Show, FieldType = SitecoreFieldType.TreelistEx)] public virtual SlideShow SlideShow { get; set; } [SitecoreField(FieldName = Application_Landing_Header, FieldType = SitecoreFieldType.SingleLineText)] public virtual string ApplicationLandingHeader { get; set; } [SitecoreQuery(".//*[@@templateid='" + SitecoreTemplateConstants.PageTemplates.ApplicationCategoryPageTemplateId + "']", IsRelative = true)] public virtual IEnumerable<ApplicationCategoryPage> ApplicationCategories { get; set; } }
SO MANY TEMPLATE ID CONSTANTS TO MANAGE!
Most of the Renderings were View Renderings with only a handful of Controller Renderings, so they were very tightly coupled to the Glass models.
Being a big proponent of using design patterns, separation of concerns and writing testable code, this approach didn’t sit well with me. Unfortunately, in this particular project, it was far too late to refactor the entire model. At least for the initial launch. We simply documented it as an area for improvement in the next phase as well as all future projects.
Next up
On the heels of this project, another kicked off and I turned to Glass.Mapper again, only this time, I was able to do things MY way!
The models continued using attributes to configure field types and field names, but no longer did we populate properties in the model directly with Sitecore Query attributes. I let the “middle-tier” of the application handle that using Sitecore’s API. Now, our models looked more like this:
public class HeaderedTextContentModel : ContentBase { [SitecoreField(FieldName = "Header", FieldType = SitecoreFieldType.SingleLineText)] public virtual string Header { get; set; } [SitecoreField(FieldName = "Header Rank", FieldType = SitecoreFieldType.Droplink)] public virtual AbbreviatedText HeaderRank { get; set; } }
Better, but the attribute mapping still bugged me. It’s more of a personal style thing, but I prefer POCOs as “clean” as possible. I don’t want the model to know (or care) anything about its configuration.
Finally (well, for now at least)
In v4, Mike and the gang introduced property mapping through the use of Fluent Configuration Maps. This offered a more fluent-API-style mapping configuration. This is the way I had done this type of property mapping in the past with Entity Framework, so it felt more comfortable. It was also about this time that I switched from using concrete model classes to using interfaces for my models.
Side note: using only interfaces of the model in the web project also helps to enforce the separation of concerns I referenced earlier. In my view, there is never a time where the web application should be instantiating Sitecore content items. This should always be done in the “middle tier” with the rest of the business logic.
Now, my models look more like this:
public interface IHeaderedContentModel : IContentBase { string MegaHeader { get; } string LargeHeader { get; } string MediumHeader { get; } string SmallHeader { get; } string Content { get; } Image Image { get; } IEnumerable<ILinkContentModel> Links { get; set; } bool ShowImageAboveHeaderVersusBelow { get; } string HeaderAlignment { get; } string ImageAlignment { get; } string ContentAlignment { get; } }
The configuration is done separately.
public class HeaderedContentModelMap : SitecoreGlassMap&lt;IHeaderedContentModel&gt; { public override void Configure() { Map(config => { ImportMap<IContentBase>(); config.AutoMap(); config.Field(f => f.MegaHeader).FieldName("Mega Header"); config.Field(f => f.LargeHeader).FieldName("Large Header"); config.Field(f => f.MediumHeader).FieldName("Medium Header"); config.Field(f => f.SmallHeader).FieldName("Small Header"); config.Field(f => f.Content).FieldName("Content"); config.Field(f => f.Image).FieldName("Image"); config.Field(f => f.ShowImageAboveHeaderVersusBelow).FieldName("Show Image Above Header Versus Below"); config.Field(f => f.HeaderAlignment).FieldName("Header Alignment"); config.Field(f => f.ImageAlignment).FieldName("Image Alignment"); config.Field(f => f.ContentAlignment).FieldName("Content Alignment"); }); } }
Now, my model is nice and clean!
I always separate my model into a separate assembly (ProjectName.Model.Cms) so it can be shared amongst all the projects/application tiers. And for now, I have all the config maps in separate assembly as well (ProjectName.Model.Cms.Configuration), but this will probably get collapsed back in to the web application. I don’t really see an advantage of keeping the configuration separate from the web application.
Finally, to have all of the config maps loaded, I use a bit of reflection in the GlsasMapperScCustom.cs class:
public static void AddMaps(IConfigFactory<IGlassMap> mapsConfigFactory) { Assembly modelMapAssembly = AppDomain.CurrentDomain.GetAssemblies().First(x => x.FullName.IndexOf("ProjectName.Model.Cms.Configuration", StringComparison.OrdinalIgnoreCase) >= 0); if (modelMapAssembly != null) { Type glassmapType = typeof(IGlassMap); var maps = modelMapAssembly.GetTypes().Where(x => glassmapType.IsAssignableFrom(x)); if (maps != null) { foreach (var map in maps) mapsConfigFactory.Add(() => Activator.CreateInstance(map) as IGlassMap); } } }
Happy Sitecore trails, my friends!