How To Create a View Model For Your Layout View In Episerver CMS

If you are familiar with MVC then the concept of abstracting your presentation files from your business logic should be pretty self-explanatory.  If you’re not sure about this, then I recommend reading up on MVC a bit.  Most people are very familiar with creating an Episerver page, wrapping the result in a view model in the controller and passing it on to the view to use.

On a lot of projects I’ve come across, people may follow this principle but when it comes to the layout file, the head and the footer, then for some people this principle goes out the window.  On all of my projects, I will create a base Layout View Model.  The model will contain all the properties needed for the header, footer, breadcrumb etc.. In this guide I’m going to cover how to hook into the MVC pipeline to inject the model for all page requests, and then how to use the View Model in your layout.

Result Filters

The first part of this puzzle is to create a view model, I’m going to use a very basic example:

public class LayoutViewModel
{
public string Header { get; set; }
public string Footer { get; set; }
}

In a production environment, this model will be huge but this will do to explain the principle.

Now we have a global view model, we need to somehow populate it on every page request and attach it into the content. I’ll try and talk through the process when someone tries to load a page, the request is sent to Episerver for the page type that matches the request.  Episerver populates the page type with the appropriate data and pushes it into the MVC pipeline. MVC will then look for a controller that matches the request.  The controller is defined using the Episerver PageControler class.

When MVC finds the controller, the page is passed into it via an action method argument.  Depending on how you structure your project, the page is usually added into a view model and then the view model is returned to the pipeline. Using the standard MVC naming convention, the view will be called (based on the names).  In the view, the master layout to use will be defined, otherwise MVC will use the default master view defined in the _ViewStart.html file.

The problem with this scenario, like Episerver, how do you hook into the pipeline to inject your own object? In EpiServ er’s case they want to inject the page object being requested fully populated with data.  In our case, we want to populate a layout view model with all the data required to render the header and footer.

MVC allows us to do this using Result filters (Or IResultFilters). A ‘result filter’ allows us to interact the MVC pipeline and run custom logic before and after a view is executed.  In our example, we want to run some code before our Layout is called.  Populate a view model and then add it to a ViewBag or similar method to attach an object to the page request. We can then use the model in _Layout.cshtml to make everything type-safe.

Creating The Results Filter

public class PageContextActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
var model = filterContext.Controller.ViewData.Model;
var layoutModel = model as IPageViewModel<PageData>;
if (layoutModel != null)
{
var layout = new LayoutViewModel();
layoutModel.Layout = layout;
}
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}

The first line ‘Controller.ViewData.Model’ is an MVC thing that gets the currently requested model in the MVC pipeline.  We will use this model to first, determine if the request is for an Episerver page (if it’s for a block preview, for example, ignore it).

The next line is checking if the model is of type IPageViewModel.  This type is a custom thing that I will create in a minute.  At the moment, all you need to know is that in order to get this to work we need to create a base model that all pages will use.

If the current model inherits from our base page, then create a new layout view model and add it to the model.  In more plain English, if the request is from a page we care about, set the layouts view model on it.

Global Base Page

First, let’s create the interface that will glue everything together:

public interface IPageViewModel<out T> where T : PageData
{
LayoutViewModel Layout { get; set; }
T CurrentPage { get; }
}

This inherits from PageData as the request will always be an Episerver page.  We have a property to store our custom view model, we have a property to store the current page object.

The next step is to create a base View Model.  In order to get this to work, every page will now need to return a view model that inherits from this:

public class BaseViewModel<T> : IPageViewModel<T> where T : PageData
{
public BaseViewModel(T currentPage)
{
CurrentPage = currentPage;
}
public T CurrentPage { get; private set; }
public LayoutViewModel Layout { get; set; }
}

That’s it really for the custom code.  The rest is standard Episerver stuff.  Lets say we have a page type defined called ‘StartPage’.  In order to render the ‘StartPage’ we would have a controller defined as so:

public class StartPageController : PageController<StartPage>
{
public ActionResult Index(StartPage currentPage)
{
return View("Index", new StartPageViewModel(currentPage));
}
}

The code for ‘StartPageViewModel will look like this:

public class StartPageViewModel : BaseViewModel<StartPage>
{
private readonly StartPage _currentPage;
public StartPageViewModel(StartPage currentPage)
: base(currentPage)
{
}
}

The last part is updating _Layout.cshtml view to include our View Model:

@model IPageViewModel<BasePage>
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body id="page-top">
@Model.Layout.Header
@RenderBody()
</body>
</html>

That’s it! You now have all the code you need to get this up and running.  This example is very basic.  The problem with it is that the Header string will never be populated.  When you start building your Episerver websites, you will either create a page or block to store your header and footer data, or you will include the fields on the homepage in separate tabs.  In your view model, you will then call the homepage, for example, read in and set all the properties.

Conclusion

Let’s recap. Having a View Model for our layout is a good thing to keep with the abstraction approach that is at the heart of MVC.  In order to implement this, we need to hook into the MVC pipeline before our views are called and inject. We do this using a Result Filter.

The way the new world will work is that in every page controller, we now have to return a view model that inherits from a custom interface that we define. The interface provides the mechanism to store the new layout view model.

In the results filter, we checked if the current page request/model inherits from our custom interface. If it does, we populate the layout view model data and attach it to the request.  If it doesn’t, we ignore it and let the request load as usual.

Finally, in our view we define a model for it to use, in this case, the one we just created and we now have access to a full typed safe model with all our global page settings included.

One word of caution with this approach, every page request will need to return a view model otherwise the page will error.  This makes sense it you expect a page to render a header or the footer and the data for it isn’t there.

If you want to render a page without the model you can simply create a new layout file and then call it from the view, rather than use the default.

CODE SAMPLE

Trying to get lots of code in a blog post up and running can be a pain. If you want to download a working example you can get full source code to my sample site here, http://jondjones.com/jondjones-episerver-blog-sample-site/

Jon D Jones

Software Architect, Programmer and Technologist Jon Jones is founder and CEO of London-based tech firm Digital Prompt. He has been working in the field for nearly a decade, specializing in new technologies and technical solution research in the web business. A passionate blogger by heart , speaker & consultant from England.. always on the hunt for the next challenge

More Posts

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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