Best Practices Development

How to Use Inversion of Control with Sitecore Glass Mapper

­­­­­­­­­­­­Sitecore Glass Mapper has lately become the backbone of my Sitecore solutions. It saves time, reduces overhead, simplifies maintenance, and outright makes my life easy. Although the Glass Mapper’s functionality provides solutions for about 90% of all development use cases, there are still some things I hope it did a bit differently. If you are planning to setup a multi-site implementation on Sitecore with extensive data sharing this article is for you!

Inversion of Control and Dependency Injection with Sitecore Glass Mapper

Dependency  Injection is at the core of Sitecore. Every time we add the full class path with the assembly name, the DI is used to access the required functionality in that type. It is no secret that any sophisticated and well architected solution could benefit from the use of DI and IoC; however, the Glass Mapper by default makes it a bit harder to implement.

The culprit is in the item extension methods like GlassCast<T>(), GetItem<T>(), GetCurrentItem<T>() …etc. which can accept an interface as well as a generic class for the T variable, which is absolutely fine, for most cases. There are even some discussions online on whether an interface or a class should be used for T, and the Glass Mapper tutorials offer an entire section describing the use of Interfaces to map Sitecore item fields.

The problem I have run into is that the Glass Mapper does not try resoling the Interface to a type first using its DI container before wrapping it in the proxy class. Although what might seem like a small and unimportant feature at a first glance, it can become a roadblock in complex multi-site instances that share a lot of data.

Sitecore Glass Mapper and IoC Limitation

It is important to understand the issue at hand, so, in order to better demonstrate the dilemma, let’s think of a hypothetical situation in which we have two websites sharing the same list of events in Sitecore. Following the conventional content structure, there are two website items for each of the site, and a single global folder called Events, an Item Bucket of event items.

In our solution we define an IEvent interface as follows:

 

public interface IEvent{
     string Title{get;}
     string Description{get;}
     DateTime StartDate{get;}
     string Representative{get;}
}

 

The requirements are such that these events can be shown on both websites, however, each website would like to have their own representative listed in the event details to contact for questions (let’s assume that a full name would suffice for this). To meet this requirements we create an Event data template in Sitecore with the Title, Description, StartDate, RepresentativeA, and RepresentativeB fields.

Being exemplar architects and developers, let’s also assume that we have been using the IoC throughout the solution for two websites, tying classes to interfaces based on certain conditions. Looking at the requirements we recognize a valid need for another IoC implementation, in which the IEvent interface would be shared by both websites, however, resolve to different types depending on the website calling it. WebsiteA would resolve the Representative property to RepresentativeA field, and WebsiteB- to RepresentativeB.

 

[SitecoreType(AutoMap = true)]
public class EventA : IEvent {
     public virtual string Title{get;set;}
     public virtual string Description{get;set;}
     public virtual DateTime StartDate{get;set;}
     [SitecoreField("RepresentativeA")]
     public virtual string Representative{get;set;}
}

...

[SitecoreType(AutoMap = true)]
public class EventB : IEvent {
     public virtual string Title{get;set;}
     public virtual string Description{get;set;}
     public virtual DateTime StartDate{get;set;}
     [SitecoreField("RepresentativeB")]
     public virtual string Representative{get;set;}
}


 

These types will be name-registered with the Windsor container as follows –

 

container.Register(Component.For<IEvent>().ImplementedBy<EventA>().LifeStyle.Transient.Named("WebsiteA"));
container.Register(Component.For<IEvent>().ImplementedBy<EventB>().LifeStyle.Transient.Named("WebsiteB"));

 

In our hypothetical EventProvider we would create logic to resolve the Interface using a DI container, however, we run into an issue of not being able to pass the runtime generated type to any of the Sitecore Glass Mapper methods, as they require a generic (compile-time) type.

How would you proceed in this case? Force the IEvent have two properties for each representative, and force each website to use their own bringing confusion and unnecessary overhead in maintenance? Write custom getters and setters for the Representative property? – all these approaches seem to break the Separation of Concerns principle, thus, out the window they go.

This is, of course, a very oversimplified example; more complex cases may include getting data from different data sources, or making complex calculations depending on the resolved model.

Using Reflection to Allow IoC with Sitecore Glass Mapper

When all fails – turn to Reflection! Ha!

Although I am not a fan of using Reflection, since it breaks some enforced safety principles of .NET, it is there for cases like ours, so let’s get cracking!

Unfortunately, the Sitecore Glass Mapper’s methods are not virtual, so I had to use the extension pattern to extend Sitecore.Data.Items.Item types with a brand new set of methods that instead of the generic type T can take a System.Type. Let’s take a look at the newly born RuntimeGlassCast() method.

 

public static object RuntimeGlassCast(this Item item, Type type, bool isLazy =false, bool inferType =false)
{
     var method =typeof (ItemExtensions).GetMethod("GlassCast", new[] {typeof(Item), typeof(bool), typeof(bool)});
     var hydratedType = method ==null?null : method.MakeGenericMethod(type).Invoke(null, newobject[] { item, isLazy, inferType });
     return hydratedType;
}

 

As you can see, we are still keeping the optional isLazy and inferType flags, but now the first parameter is of System.Type type! The method itself is pretty straight-forward – with some reflection magic, we first find the static GlassCast method of the ItemExtensions class with the right signature, make it generic, and finally invoke it using the passed in parameters. Voila! Now we can safely resolve our interface, and later pass the resolved type to the RuntimeGlassCast, and the returned object can be used as the original interface type.

Going back to our hypothetical scenario with events, we can continue on in the EventPrvider as follows:

 

var eventViewModelType =Services.Resolve<IEvent>(Sitecore.Context.Site.Name).GetType();
var eventViewModel = eventItem.RuntimeGlassCast(eventViewModelType);
return eventViewModel as IEvent;

 

Following the same pattern other runtime sudo Sitecore Glass Mapper methods can be created with multiple signatures (RuntimeGetItem(), RuntimeGetCurrentItem()…etc.).

Inversion of Control Benefits

There are many websites and blog dedicated to explaining what Dependency Injection and Inversion of Control are and how modern web solutions can benefit from them. Sitecore solutions are no different! As developers and architects, we should strive to create easily maintainable loosely coupled solutions. Properly structuring the interfaces and resolving them to proper types gives developers a lot of flexibility in terms of loose coupling, unit testing, and DI.

The described above mapper wrapper methods allow full flexibility in terms of the IoC in Sitecore implementation, since the resolution can be handled by your code, scoring you some of those hard to get “top developer” points!


One comments on “How to Use Inversion of Control with Sitecore Glass Mapper
  1. Michael Edwards on said:

    Hi

    Glad to see you are getting value out of Glass! Glass does support multiple Glass contexts, a Glass context contains all the configuration for the types to load. A context can then be linked to a site in the Sitecore config, it isn’t a well know feature, but I will try and do a blog post on how to do this written soon.

    I have noted down your comments on virtual methods and I will look to adding some in a future release. Thanks for the feedback and if you have any more ideas I would love to hear them.

    Mike

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.