Upgrading from Glass.Mapper.Sc v4 to Glass.Mapper.Sc v5

Glass

Version 5 of Glass.Mapper.Sc was released last year.  If you are working with Sitecore 9.1, there isn’t formal support for it with Glass.Mapper.Sc v4.  So, I decided it was time to upgrade.  I also discovered that tutorials and documentation from the Glass team are now a paid training course, which made the learning curve a little more challenging, although it still wasn’t a big deal.  I’m going to outline my steps and the references I found.

Getting Started

Shu Jackson created a pretty kick butt overview of Glass and covers what has changed in version 5.  Shu Jackson and Courtney Dean also created an excellent video that covers just about everything.

Circular References & Model Depth

bloaters

The code base that I am working with has some gnarly model classes that need to be redesigned.  Always easier said than done.  The Glass.Mapper.Sc API changed enough where some of the tricks involving Lazy Loading that allowed these classes to work in the past, no longer worked.  Some classes implemented complex fallback logic and exposed self references via a model property.  That code needed to be removed and, in some cases, refactored as a method.  Ideally, that code will be moved to a service class and out of the model.

Removing Attributes SitecoreNode and SitecoreQuery

Some of the model glasses also relied on the use of Glass.Mapper.Sc model attributes, particularly SitecoreNode and SitecoreQuery.  Some of the queries implemented through these attributes no longer worked as they did previously, most notably, any use of relative and lazy.  I removed these model properties and re-implemented them as methods, removing the use of the attributes.


[SitecoreQuery("Primary/*", IsLazy = true, IsRelative = true)]
public virtual IEnumerable PrimarySubMenu { get; set; }

public virtual IEnumerable PrimarySubMenu
{
    get
    {
        GetItemsByQueryOptions builder = new GetItemsByQueryOptions();
        builder.Query = new Query("Primary/*");
        builder.RelativeItem = this.Item;
        builder.Lazy = Glass.Mapper.LazyLoading.Enabled;
        return _requestContext.SitecoreService.GetItems(builder);
    }
}

Item is the model base class reference to the Sitecore Item.


[SitecoreItem]
public virtual Item Item { get; set; }

The web application started to work after these changes were applied, but the project was still compiling with a dizzying number of warnings.

Removing GlassController

Most of my controller classes inherited from GlassController.


public class MenuController : GlassController
    {

The classes had GlassController calls like:


MenuParameters parameters = GetRenderingParameters();

and


var somedata = GetDataSourceItem();

It’s a pretty easy conversion to Glass Mapper 5.


    public class MenuController : Controller
    {
        private readonly IMvcContext _mvcContext;

        public MenuController()
        {
            _mvcContext = new MvcContext();
        }
        ...

MenuParameters parameters = _mvcContext.GetRenderingParameters();
var somedata = _mvcContext.GetDataSourceItem();

Removing GlassView


@using Glass.Mapper.Sc.Web.Mvc
@using Someproject.Models.Data

@inherits GlassView<SomeModelClass>


@if (Model != null)
{
    <li class="class-of-business-item">

        @if (Model.ArtworkImage != null)
        {
            @Html.Raw(RenderImage<SomeModelClass>(Model, x => x.ArtworkImage, new { Class = "class-of-business-image" }, isEditable: true))
        }
        <li class="class-of-business-content">
            @Editable(Model, x => x.PageTitle)
            @Editable(Model, x => x.Body)
        </li>
    </li>
}

@using Glass.Mapper.Sc.Web.Mvc
@using Someproject.Models.Data

@model SomeModelClass

@if (Model != null)
{
    <li class="class-of-business-item">

        @if (Model.ArtworkImage != null)
        {
            @Html.Raw(Html.Glass().RenderImage<SomeModelClass>(Model, x => x.ArtworkImage, new { Class = "class-of-business-image" }, isEditable: true))
        }
        <li class="class-of-business-content">
            @Html.Glass().Editable(Model, x => x.PageTitle)
            @Html.Glass().Editable(Model, x => x.Body)
        </li>
    </li>
}

Simple Examples

Here are a few simple code snippets:

Create a Reference


SitecoreService _sitecoreService = new SitecoreService(Sitecore.Context.Database);
IRequestContext_requestContext = new RequestContext(_sitecoreService);

Relative Query

Set the RelativeItem property. It does what you expect.


GetItemsByQueryOptions builder = new GetItemsByQueryOptions();
builder.Query = new Query("Primary/*");
builder.RelativeItem = this.Item;
return _requestContext.SitecoreService.GetItems(builder);

Query Item(s) By Path


GetItemByPathOptions builder = new GetItemByPathOptions();
builder.Path = “/sitecore-path/…”;
return _requestContext.SitecoreService.GetItem(builder);

...

GetItemsByQueryOptions builder = new GetItemsByQueryOptions();
builder.Query = new Query("/sitecore-path/…/*");
return _requestContext.SitecoreService.GetItems(builder);

Query Item By Id

GetItemByIdOptions builder = new GetItemByIdOptions();
builder.Id = Guid.Parse(“some-guid-string”);
return _requestContext.SitecoreService.GetItem(builder);

Query Item By Item

Replace any Glass Casting with the following:

return _requestContext.SitecoreService.GetItem(SomeItem);

Other Useful Properties

Request a specific language version of an item:

builder.Language = LanguageManager.DefaultLanguage;

Set Glass caching:

builder.Cache = Glass.Mapper.Configuration.Cache.Enabled;

Set Lazy Loading:

builder.Lazy = Glass.Mapper.LazyLoading.Enabled;

Rendering Experience Editor Compatible Links Using Glass Mapper and Dynamically Built Properties

I encountered a problem when reviewing some legacy code using Glass Mapper v4.3.  Glass Mapper no longer supports dynamically build properties, while in Sitecore Experience Editor “Edit” mode.

View renderings had expressions like this:

using (BeginRenderLink(Model.GetListHeader(sList), x => x, isEditable: true))
{
   @Html.Raw(Model.GetListHeader(sList).Text);
}

The supporting Model class had the following:

[SitecoreField("List1_Header")]
public virtual Link List1Header { get; set; }

[SitecoreField("List2_Header")]
public virtual Link List2Header { get; set; }

[SitecoreField("List3_Header")]
public virtual Link List3Header { get; set; }

[SitecoreField("List4_Header")]
public virtual Link List4Header { get; set; }

public virtual Link GetListHeader(string sRenderingParameter)
{
   Link oReturn = null;
   int iList = 0;

   iList = Int32.Parse(sRenderingParameter);

   switch (iList)
   {
      case 1:
         oReturn = List1Header;
         break;
      case 2:
         oReturn = List2Header;
         break;
      case 3:
         oReturn = List3Header;
         break;
      case 4:
         oReturn = List4Header;
         break;
      default:
         break;
   }
   return oReturn;
}

While in Experience Editor Edit mode, the following exception was thrown:

Expression doesn't evaluate to a member x
...
at Glass.Mapper.Utilities.GetTargetObjectOfLamba[T](Expression`1 field, T model, MemberExpression& memberExpression) at Glass.Mapper.Sc.GlassHtml.MakeEditable[T](Expression`1 field, Expression`1 standardOutput, T model, Object parameters, Context context, Database database, TextWriter writer)

I did some digging on the web and found a few other references to this issue:

https://stackoverflow.com/questions/18698668/exception-in-glass-mapper-for-sitecore-in-pageeditor-mode

https://stackoverflow.com/questions/44059614/can-i-use-glassmappers-editable-on-a-reflected-property

Glass Mapper checks to make sure the lambda expression is a MemberExpression, and throws an exception when it’s not.  This is necessary due to updates in newer versions of Glass Mapper.

Here is what I did to work around this issue.

Remove the Dynamic Property References?

I could refactor the View rendering, adding an endless number of if statements, effectively pulling the model code into the View rendering, so that the property is statically referenced. That would create an even bigger mess.  This wasn’t going to work for me.  No thank you.

unhappy

Building a Dynamic MemberExpression in a Helper Method

After doing some reading about lambda expressions and coming across this question on Stack Overflow, I added the following method to the model class:

public virtual Expression<Func<T, object>> GetListHeaderExp(string sRenderingParameter)
{
   Expression<Func<T, object>> oReturn = null;
   int iList = 0;

   iList = Int32.Parse(sRenderingParameter);
   PropertyInfo propertyInfo = null;

   switch (iList)
   {
      case 1:
         propertyInfo = typeof(GenericPage).GetProperty("List1Header");
         break;
      case 2:
         propertyInfo = typeof(GenericPage).GetProperty("List2Header");
         break;
      case 3:
         propertyInfo = typeof(GenericPage).GetProperty("List3Header");
         break;
      case 4:
         propertyInfo = typeof(GenericPage).GetProperty("List4Header");
         break;
      default:
         break;
   }

   var entityParam = Expression.Parameter(typeof(GenericPage), "e");
   Expression columnExpr = Expression.Property(entityParam, propertyInfo);

   if (propertyInfo.PropertyType != typeof(Link))
      columnExpr = Expression.Convert(columnExpr, typeof(Link));

   oReturn = Expression.Lambda<Func<T, object>>(columnExpr, entityParam);

   return oReturn;
}

Call the MemberExpression Builder Method from the View

I then refactored the call in the View rendering to:

using (BeginRenderLink(Model, Model.GetListHeaderExp(sList), isEditable: true))
{
   @Html.Raw(Model.GetListHeader(sList).Text);
}

Conclusion

That did the trick.  I can now edit the link using the Sitecore Experience Editor, while keeping the dynamic property reference.

I’m hoping to write about my experience migrating an enterprise Sitecore installation from 7.2 to 8.2.  Hopefully, I’ll get to that soon.

Upgrading to Glass Mapper v4.3

Glass Mapper is hands down the best ORM available for Sitecore Development.  I have been using it for five years and love it.  It helps you map Sitecore templates and fields to .NET classes, objects and properties.

I recently decided to upgrade to v4.3 from v4.0.1.8.  Upgrading from v3 to v4 was a little bit hairy, but upgrading to v4.3 was pretty easy.

Update from Nuget

First, I loaded the new version of Glass Mapper from Nuget.  I opened the Package Manager Console and ran:

Install-Package Glass.Mapper.Sc -Version 4.3.4.197

Installing the Nuget package wipes out App_Start/GlassMapperScCustom.cs class, so I needed to re-add my custom data mappers. My GlassMapperScCustom class looked like this after I was done.

#region GlassMapperScCustom generated code
using Glass.Mapper.Configuration;
using Glass.Mapper.IoC;
using Glass.Mapper.Maps;
using Glass.Mapper.Sc.IoC;
using IDependencyResolver = Glass.Mapper.Sc.IoC.IDependencyResolver;
using Glass.Mapper.Configuration.Attributes;
using SomeProject.CustomDataHandler;

namespace SomeProject.App_Start
{
    public static  class GlassMapperScCustom
    {
        public static IDependencyResolver CreateResolver()
        {
           var config = new Glass.Mapper.Sc.Config();

	   var dependencyResolver = new DependencyResolver(config);
           // add any changes to the standard resolver here

           dependencyResolver.DataMapperFactory.Insert(0, () => new LinkListDataHandler());
           dependencyResolver.DataMapperFactory.Insert(0, () => new CustomInternalLinkDataHandler());

           return dependencyResolver;
        }

        public static IConfigurationLoader[] GlassLoaders()
        {
           var attributes = new AttributeConfigurationLoader("SomeProject");
           return new IConfigurationLoader[] { attributes };
        }
    
        public static void PostLoad()
        {
           var dbs = Sitecore.Configuration.Factory.GetDatabases();
           foreach (var db in dbs)
           {
               var provider = db.GetDataProviders().FirstOrDefault(x => x is GlassDataProvider) as GlassDataProvider;
               if (provider != null)
               {
                   using (new SecurityDisabler())
                   {
                       provider.Initialise(db);
                   }
               }
           }
        }
        public static void AddMaps(IConfigFactory< IGlassMap >  mapsConfigFactory)
        {
        }
    }
}
#endregion

Ambiguous Reference

I re-built my solution and ran into some compiler errors.

ambiguous reference error

Easy problem to resolve: a simple conflict between a Glass abstraction of the Sitecore Diagnostics log and the Sitecore Diagnostics log.  I considered removing the references to Sitecore.Diagnostics and using the Glass method, but the Glass.Mapper.Sc.Log class requires an instance to be used.  So, I simply globally changed my Log references in code to Sitecore.Diagnostics.Log.

Model Depth Check

The solution now compiled successfully.  When I tested the application, I encountered:

Server Error in ‘/’ Application.

Model too deep. Potential lazy loading loop. Type requested: SomeProject.Models.IGlassBase SomeProject.Models.IGlassBase
SomeProject.Models.IGlassBase
SomeProject.Models.IGlassBase
SomeProject.Models.IGlassBase
SomeProject.Models.IGlassBase
SomeProject.Models.IGlassBase
SomeProject.Models.Pages.HomePage
SomeProject.Models.Parts.DesktopMenu

After a quick Google search, I re-read the Glass Mapper 4.3 Release Notes.  The depth check mechanism added for the Cachable Changes was breaking the code.  This was another easy fix.  I refactored the CreateResolver method as per the release notes:

public static IDependencyResolver CreateResolver()
{
   var config = new Glass.Mapper.Sc.Config();

   // Needed to avoid Model too deep exception
   config.EnableLazyLoadingForCachableModels = true;
   
   var dependencyResolver = new DependencyResolver(config);
   // add any changes to the standard resolver here

   dependencyResolver.DataMapperFactory.Insert(0, () => new LinkListDataHandler());
   dependencyResolver.DataMapperFactory.Insert(0, () => new CustomInternalLinkDataHandler());

   var factory = dependencyResolver.ObjectConstructionFactory as AbstractConfigFactory < AbstractObjectConstructionTask >;
   factory.Remove< ModelDepthCheck >();

   return dependencyResolver;
}

Naughty GlassCast

The next issue I worked through was the depreciation of the GlassCast method. Mike Edwards, the “Godfather of Glass Mapper”, did an incredible job explaining why GlassCast can lead to performance problems. I had an embarrassing amount of GlassCast method calls sprinkled throughout the code base.

model = MiscContentFactory.GetRelatedContent (Sitecore.Context.Item.Parent.GlassCast< CustomModelClass >().Id.ToString(), parameters.NumberOfItems);

This was straight forward. I removed all references to GlassCast. I chose to replace GlassCast with use of the Cast method from the SitecoreService object.

ISitecoreService service = new SitecoreService(Sitecore.Context.Database);
model = MiscContentFactory.GetRelatedContent (service.Cast< CustomModelClass >(Sitecore.Context.Item.Parent).Id.ToString(), parameters.NumberOfItems);

Embedded Markup in RenderLink Contents Parameter

The next implementation issue was instances of RenderLink being used with embedded markup in the contents attribute.

@RenderLink(x => home.SomeLinkProperty, new System.Collections.Specialized.NameValueCollection() { { "class", "print-logo" } }, contents: "< span class=\"icon-openenvelop\" >< / span > < span class=\"print-logo\">" + home. SomeLinkProperty.Text + " < / span >")

I could not find a way to make this work.  I ended up converting every use of the method RenderLink to BeginRenderLink.

@using (BeginRenderLink(x => home.SomeLinkProperty, new System.Collections.Specialized.NameValueCollection() { { "class", "print-logo" } }, isEditable: true))
{
   < span class="icon-openenvelop" > < /span >< span class="print-logo" > @home. SomeLinkProperty.Text < / span >
}

I suppose I could have created a helper class to work around this to avoid all the rework, but I felt this was clearer long term solution.

Empty Model Values for View Renderings

The last issue that I encountered was View Renderings with blank Model class values.

blank view model value in Sitecore View Rendering

This caused the following error to appear on various pages:

The model item passed into the dictionary is of type ‘Sitecore.Mvc.Presentation.RenderingModel’, but this dictionary requires a model item of type ‘X’

Sloppy development practices left the Model values blank for some View Renderings, even though the View Renderings get bound to specific Model classes in the View.  I reviewed all of the View Renderings and set the Model values that were blank to the appropriate custom model class.

Sitecore View Rendering with valid model value

Conclusion

Upgrading to Glass Mapper v4.3 was fairly straightforward and well worth the effort.