Back-End Development

Customizing the model within Sitecore MVC

Custom Tiles@1x.jpg

sclogovertpan_jpg_300In Sitecore, the MVC framework provides a “default” model type that does some very necessary work.

To support the view, Sitecore’s default model type Sitecore.Mvc.Presentation.RenderingModel provides you access to

  • The “current” item – passed via a datasource or the context item if no datasource is used
  • The PageContext item (which may be different if a datasource is used)
  • A reference to the Rendering used by the model

In some cases, this is very sufficient – but there are occasions where a developer might want to add a property to the model to remove code blocks that would otherwise have to be placed within the view.

Sitecore does allow you to create a custom model class – as long as you implement Sitecore.Mvc.Presentation.IRenderingModel, and support the above 3 properties, you can create a model from scratch.

Rather than writing a custom model from scratch every time I needed one, I decided to use Generics to accomplish the task.

Step 1 – Writing a CustomItem class

If you are familiar with Sitecore, you have certainly seen the Sitecore.Data.Items.CustomItem class.  This abstract type is a good foundation to use to write a custom domain item or DTO based on a Sitecore Item type in your project.  Lots of Sitecore custom modules such as the CustomItemGenerator use this base type when creating custom item classes.

Writing a custom item is easy – simply support a constructor that takes a Sitecore.Data.Item as an argument, and you’re ready to go.  Here’s a sample CustomItem to support a Breadcrumb view:

[sourcecode type=”c#”]

using System;
using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using MyStuff.Web.Extensions;

namespace MyProject.Web.Models.Items.Navigation
{

public class BreadcrumbItem : CustomItem
{
/// <summary>
/// Constructor to build a NavigableItem from a Sitecore Item
/// </summary>
/// <param name="item"></param>

protected BreadcrumbItem(Item item) : base(item)
{
CrumbStack = new Stack<Item>();
Item cur = item;

while (cur != null && !cur.IsDerived(new ID(TemplateIds.WebsiteFolder)))
{
CrumbStack.Push(cur);
cur = cur.Parent;
}
}
public Stack<Item> CrumbStack { get; private set; }
}
}

[/sourcecode]

Step 2 – Creating a Model base type that handles generics

First, the interface:

[sourcecode type=”c#”]

using Sitecore.Data.Items;
using Sitecore.Mvc.Presentation;

namespace MyProject.Web.Models
{
public interface ICustomItemRenderingModel<out T> : IRenderingModel where T : CustomItem
{
T CustomItem { get; }
Item Item { get; }
Item PageItem { get; }
Rendering Rendering { get; }
}
}

[/sourcecode]

Covid 19
COVID-19: Digital Insights For Enterprise Action

Access Perficient’s latest insights into how you can leverage digital technologies to not only respond to the pandemic, but drive your operations forward and deliver experiences your customers need.

Get Informed

The important part of this is the <out T> modifier – in C# 4, this allows you to use covariance … in simple terms, this allows you to use a derived type instead of a specific type as the generic type.  In this case, T can be of any type that is derived from CustomItem.

Now the implementation

[sourcecode type=”c#”]

using System;
using System.Reflection;
using Sitecore.Data.Items;
using Sitecore.Mvc.Presentation;

namespace MyProject.Web.Models
{
public class CustomItemRenderingModel&amp;lt;T&amp;gt; : ICustomItemRenderingModel&amp;lt;T&amp;gt; where T : CustomItem
{
private Rendering _rendering;
private T _customItem;
private Item _pageItem;
private Item _item;

public void Initialize(Rendering rendering)
{
_rendering = rendering;
}

public T CustomItem
{
get {
if (_customItem != null) return _customItem;
// find the constructor and call it
Type type = typeof(T);
ConstructorInfo constructor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance,
null,
new[] { typeof(Item) },
null);
if (constructor == null) return null;
_customItem = (T)constructor.Invoke(new object[] { _rendering.Item });
return _customItem;
}
}

public Item PageItem
{
get { return _pageItem ?? (_pageItem = PageContext.Current.Item); }
}

public Item Item
{
get { return _item ?? (_item = _rendering.Item); }
}

public virtual Rendering Rendering
{
get { return _rendering; }
}
}
}

[/sourcecode]

Step 3 – Using the Generic Type

This really should be the easy part, but not so much.  To use your new CustomItem / Generic model, you simply add the model statement to the view

@model MyProject.Web.Models.CustomItemRenderingModel<MyProject.Web.Models.Navigation.BreadcrumbItem>

and then of course in Sitecore create a Model item in the Layouts folder that declares the type.  Note that you have to use the alternate syntax to declare the type in your model item:

MyProject.Web.Models.CustomItemRenderingModel`1[MyProject.Web.Models.Items.Navigation.BreadcrumbItem],MyAssembly.Web

Step 4 – Replacing the Model Locator within Sitecore to support Generics

As of Sitecore 6.6 update 5, this step is required because Sitecore will fail to create the Model with the Generics declaration.  Once you have narrowed it down a bit, you have to do 2 things to support models with generics:

First, create your own model locator and override the method object GetModelFromTypeName(string typeName, string model, bool throwOnTypeCreationError)

[sourcecode type=”c#”]

using System;
using System.Text.RegularExpressions;
using Sitecore.Mvc.Helpers;
using Sitecore.Mvc.Presentation;

namespace MyProject.Web.Models
{
public class CustomModelLocator : ModelLocator
{
private const string GenericsPattern = @"`d[.+]";
protected override object GetModelFromTypeName(string typeName, string model, bool throwOnTypeCreationError)
{
Type type = Regex.IsMatch(typeName, GenericsPattern, RegexOptions.Singleline) ? Type.GetType(typeName) : TypeHelper.GetType(typeName);

if (type == null)
{
if (!throwOnTypeCreationError) return null;
throw new InvalidOperationException(Sitecore.StringExtensions.StringExtensions.FormatWith("Could not locate type ‘{0}’. Model reference: ‘{1}’", (object)typeName, (object)model));
}
object @object = TypeHelper.CreateObject(type, new object[0]);
if (@object != null || !throwOnTypeCreationError)
return @object;
throw new InvalidOperationException(Sitecore.StringExtensions.StringExtensions.FormatWith("Could not create a model object of type ‘{0}’. Model reference: ‘{1}’", (object)typeName, (object)model));
}
}
}

[/sourcecode]

You can tell Sitecore to use your model locator by adding this method to the <initialize> pipeline

[sourcecode type=”c#”]

using MyProject.Web.Models;
using Sitecore;
using Sitecore.Mvc.Configuration;
using Sitecore.Pipelines;

namespace MyProject.Web.Pipelines
{
[UsedImplicitly]
public class SetModelLocator
{
public virtual void Process(PipelineArgs args)
{
MvcSettings.RegisterObject&amp;lt;Sitecore.Mvc.Presentation.ModelLocator&amp;gt;(() =&amp;gt; new CustomModelLocator());
}
}
}

[/sourcecode]

Just patch that method into the pipeline (I did it as the first processor in the pipeline) and now you’re all set.

About the Author

As a Sitecore MVP, Brian spends most of his time consulting and architecting software solutions for enterprise-level Sitecore projects.

More from this Author

Thoughts on “Customizing the model within Sitecore MVC”

  1. I followed all the steps but still sitecore is not able to locate the generic model.

    Please explain Step3 in detail

    MyProject.Web.Models.CustomItemRenderingModel`1[MyProject.Web.Models.Items.Navigation.BreadcrumbItem],MyAssembly.Web

    Tell me the parameters I have to mention for step 3.

Leave a Reply

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

Subscribe to the Weekly Blog Digest:

Sign Up