2

Determine model from view with Sitecore MVC

Today at Lightmaker myself and James Dartnell (@j4ddie) were looking into Sitecore MVC, specifically how Sitecore determines what model to use for a rendering view. Currently Sitecore allows you to create a Model item where you state what class and assembly to be used, then you associate this Model item to a Rendering view item. However for us this created a dependency between the code and the Sitecore items, if we changed what model is to be used on a view we would also need to change it in the Sitecore item.

With our problem at hand we set about finding a way to programmatically determine what model needs to be used for a view. Our first port of call was to check out how Sitecore mvc.getModel pipeline functions, which is what tries to determine what model to use. For the most part the pipelines are quite simple in that they read the Model parameter set on the Rendering view item, then retrieves the corresponding Model item which in turn reads the value of the Model Type field.

We felt that we could leverage the mvc.getModel pipeline ourselves and insert our own step in which we would programmatically pass the type set on the view (@model in .cshtml file) to Sitecore for processing. We outlined the following steps that we felt were required to do this;

  1. Determine physical path of view
  2. Retrieve view as an object
  3. Determine class set with @model in view
  4. Pass the determined class to Sitecore so that it can continue its normal process

Initially we thought as the Sitecore pipelines were reading rendering.Properties[“Model”] that we could just use “Path” instead. To our surprise this didn’t work and for some reason the value of the Path field is not present in the properties. Eventually we found that we could retrieve the path by casting the Rendering.Renderer property to a ViewRenderer class which exposes the ViewPath property.
Now that we had our path we needed to retrieve the view as an object, we found that we could use the BuildManager.GetCompiledType property and pass it the path of the view. This returned us an object of type System.Type where we then ran some checks to ensure that we had actually found a view.

After ensuring we had actually retrieved a view as an object we were then able to leverage the fact that views implement ViewPage<T> where T represents what is set in @model. We then check to see if the type returned is object and if it is then we know @model hasn’t been set.

Finally we work out the fully qualified name and assembly that is then passed to Sitecore’s ModelLocator.GetModel method and Sitecore continues on from there! I’ve dropped the pipeline processor we wrote below as a code snippet, enjoy!

using Sitecore.Mvc.Pipelines.Response.GetModel;
using Sitecore.Mvc.Presentation;
using System.Web.Compilation;

public class GetModelFromView : GetModelProcessor
{
	protected virtual object GetFromViewPath(Rendering rendering, GetModelArgs args)
	{
		var path = rendering.Renderer is ViewRenderer ? ((ViewRenderer)rendering.Renderer).ViewPath : rendering.ToString().Replace("View: ", string.Empty);

		if (string.IsNullOrWhiteSpace(path))
		{
			return null;
		}

		// Retrieve the compiled view
		var compiledViewType = BuildManager.GetCompiledType(path);
		var baseType = compiledViewType.BaseType;

		// Check to see if the view has been found and that it is a generic type
		if (baseType == null || !baseType.IsGenericType)
		{
			return null;
		}

		var modelType = baseType.GetGenericArguments()[0];

		// Check to see if no model has been set
		if (modelType == typeof(object))
		{
			return null;
		}

		var fullyQualifiedName = modelType.FullName + ", " + modelType.Assembly.GetName().Name;

		return args.ModelLocator.GetModel(fullyQualifiedName, true);
	}

	public override void Process(GetModelArgs args)
	{
		if (args.Result == null)
		{
			args.Result = GetFromViewPath(args.Rendering, args);
		}
	}
}

Jason Bert