Perhaps the most discussed and debated question of all when it comes to EPiServer and ASP.NET MVC development is “What is the view model?” Here's my thoughts on the subject and the reasoning behind the path chosen in the Alloy templates.
In my article about the MVC templates for EPiServer 7 I wrote
"Perhaps the most discussed question of all when it comes to EPiServer and MVC development is 'What is the model?' This is a multifaceted question, which could easily make this article very long. Hence, I will leave the background discussion for a later article or blog post"
In this article I'll follow up on that with both the background reasoning for what types of models are passed to views from controllers in the MVC templates, as well as general thoughs about (view) models when working with EPiServer CMS and ASP.NET MVC.
To use view models or not to use view models
Should we use a separate view model or simply pass PageData/BlockData to the view? In general ASP.NET MVC development it’s a well established best practice, at least in larger projects, to use separate view models. There are four commonly used arguments for this approach:
- By creating a model to the needs of the view we can keep the view simple and free from complex logic.
- By creating a view model we can create unit tests for the logic that takes domain models or other data as arguments and returns the data needed by the view.
- By using separate view models we can change the domain models without having to change the view models and vice versa.
- Model binding in ASP.NET MVC is vulnerable to mass assignment attacks and using a separate view model can prevent that.
All of the above arguments are of course valid but also context specific.
For instance, a page type is often already designed with the needs of the view in mind. We’re after all using a content management system in which the domain objects represent content intended to be displayed using templates.
That is, in many, but not all, scenarios a view, no matter if it’s MVC or Web Forms, simply renders properties from a PageData or BlockData object. In such cases there’s also no logic to test meaning that the second argument doesn’t make much sense in this context.
Using view models to be able to change domain models without having to update views, or vice versa, is an attractive idea from an architectural standpoint. It does come at a steep price however, as it requires a lot of code for mapping CMS content objects to view models.
While that can be handled by mapping frameworks such as AutoMapper we’d still be forced to write more code whenever we want to add a new property to a content type that should be displayed in the view, as we’d then have to update the page or block type as well as the view model.
Also, designing for future changes which may happen is a case of Big Design Up Front. Often the code that hasn’t been written yet is most easy to change.
Finally, while mass assignment is a very real threat when using ASP.NET MVC the model binding of CMS content is handled by EPiServer and EPiServer sites rarely have code in which non-trusted users are allowed to create or modify pages or blocks.
For these reasons the MVC template does allow them selves to use pages and blocks as view models when doing so offers the path of least resistance.
There is however another aspect of the underlying “What is the model” question – how to handle data needed by the overall layout rather than the specific view.
Let’s review one of the pages from the Alloy site below.
It’s pretty obvious that the headline, text and form is unique for this page and therefor something that the rendering view should handle. But what about the header and footer?
There’s clearly quite a lot of data being displayed there but hardly any of it is related to the specific page. As for the breadcrumb and sub navigation those are both tied to the current pages location in the page tree but we can probably all agree that we wouldn’t like each page type specific controller or view to have to care about them.
While handling such “framework” components is straightforward using master pages and user controls in Web Forms there’s no clear-cut solution when using ASP.NET MVC. There are however a number of possible solutions which each can be grouped into one of two general patterns:
- Use a base class for view models with properties needed by the sites framework. Such properties can then be rendered in layout files, optionally using partial views.
- Use child actions in layouts to render framework components such as navigations. Each child action, invoked by the Action or RenderAction HTML helpers, can then figure out the current page by looking at the request context.
Coming from Web Forms development where user controls is frequently used the second approach is attractive. By using child actions we can compose each page by using a number of stand alone components without any dependencies between them.
However, each child action will spawn a full MVC cycle, which is costly performance wise. Each such action would also have to figure out the current page context, which is rather tedious and possibly error prone.
The first approach requires that all actions responsible for pages populate common properties of the view model. While that can be handled by using a base class for controllers with such actions we would end up with, possibly complex, inheritance hierarchies not only for view models but also for controllers.
View models in the MVC templates
Based on the reasoning about the necessity, or lack of such, for separate view models and the need for almost the exact same context in layouts and navigation components used in most views the solution in the MVC templates is to pass the current page object to the view along side data needed by framework components. This is done using a base class, or rather an interface to utilize generic variance.
All non-partial views and layouts in the MVC templates uses a model of type IPageViewModel<T>. The type parameter T has to be a descendent of PageData.
The interface defines a CurrentPage property of type T as well a couple of properties intended to provide context to layouts and framework components such as navigation elements.
The idea behind this solution is that all views, and especially their layout files, can rely on some common characteristics of the view model.
Populating context properties
To free controllers from having to populate these common properties of the view model an action filter named PageViewContextActionFilter is registered globally at start up. This filter inspects the view model which is about to be passed to the view and, given that it’s of type IPageViewModel, sets the common properties required by the sites framework.
By using this approach each action for a page can use it’s own specific view model as long as it implements IPageViewModel without having to care about populating common properties.
At the same time the logic for populating such common properties have been moved out from the sites controllers into a specific class responsible for framework components, which can be modified and tested without having to deal with controllers.
Should a specific controller or action want to influence the common properties of the view model it can do so by populating them on the view model as the filter then won’t modify them. Alternatively a controller can implement an interface named IModifyLayout that tells the filter to pass the Layout property of the view model to the controller after having populated it with default values.
Using an action filter instead of populating all properties of the view model in controllers makes the code less obvious and harder to follow. Clearly a bad thing. However, considering the alternative we'd either have to duplicate a lot of code or rely on inhertitance which would have almost the same negative effects while also tying controllers to a possibly complex inheritance hiearchy.
By taking the action filter route each controller can go on it's merry way focusing only on the specifics needed for the page type it handles.
Models for partial views
Partial views doesn't follow the pattern described above. Instead they either use a completely custom view model or the current content object is passed directly to the view.
The reason for not using a similar approach as for non-partial views is that the partial views don't have layouts and typically don't need to know about a broader context.
While the approach used in the MVC templates isn’t the most obvious and takes a while to wrap one’s head around it makes it easy to create new controller, models and views. As such I find it allows rapid development while maintaining decent separation of concerns.
When working with a view one has access to both view specific properties as well as a broader context. While this may seem like it's violating "best practices" I think it's good to keep in mind that best practices is dependent on context. And in the case of many EPiServer sites the context is that a page type very much is a view model in it self.
I've also used the same approach as in the MVC templates in a few other projects since and found it working well. Software design and development is always about context though. I can imagine sites where completely view-specific view models may makes sense. Similarly I can imagine sites where simply passing the current PageData object to the view would be an attractive solution.
Therefor I personally think that we can use the view model approach used in the MVC templates in most projects as a start. As with many other things we should however always look at the specific context of the project and think about how we can deliver most customer value and flexibility for evolving customer needs.