ASP.NET MVC Templates for EPiServer 7 CMS
In this article I will walk through the most important technical aspects of the new MVC templates for EPiServer 7 CMS.
Technical Summary
- Built on EPiServer 7
- Built on ASP.NET MVC 4 and Razor
- Same import package for content as the WebForms templates
- Based on Bootstrap by Twitter, using nearly the same CSS files as the WebForms version
- Uses ASP.NET bundling and minification
Note that while the templates uses Razor as view engine EPiServer 7 CMS also supports the WebForms view engine.
Download
The MVC templates are distributed as a zipped file. > Download
Alloy – Same but Different
Like the Web Forms templates the MVC templates is a sample site for the fictive technology company Alloy. The MVC version has the same features as the WebForms-based Alloy site and the same content.
While the two template packages have completely separate code bases, they share the same import package and almost identical CSS and language files.
For this reason I will not cover how the site is designed or how it works as Ted Nyberg has already written an article about that. Instead, I will focus on the key design decisions made when developing the MVC templates and provide an overview of key components in the templates.
Design Philosophy
EPiServer 7 CMS is the first version of the CMS that supports template development with ASP.NET MVC. This means that there has yet to develop “best practices” in the EPiServer community when it comes to MVC development. It also means that ASP.NET MVC may be unchartered territory for some developers.
Therefore the focus when building the MVC templates was on illustrating how to build a site with EPiServer using ASP.NET MVC rather than showing best practices for general ASP.NET MVC development. The focus of the templates is to show an example of how to build an EPiServer site with good characteristics both in terms of maintainability and performance. They also show an example of how conventions in ASP.NET MVC can be tuned to, hopefully, suit a site like Alloy.
In order to accomplish this, a number of developers working with ongoing development projects on EPiServer 7 CMS and MVC were consulted. These discussions, prototyping, and the unique characteristics of the Alloy site were distilled into a number of design decisions geared towards making it easy to extend the Alloy site with new content types and functionality.
Models
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 and instead focus on the implementation.
View Models in the MVC Templates
All non-partial views and layouts in the MVC templates uses a model of type IPageViewModel<T> where T has to be a descendent of PageData. The IPageViewModel interface defines a CurrentPage property as well a couple of properties geared toward layouts and framework components. This way all views, including layout files, can rely on some common characteristics of the view model.
To free controllers from having to populate these common properties on 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 is of type IPageViewModel, sets the common properties required by the sites framework.
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 will not 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.
An example of a controller which implements IModifyLayout is the controller for the preview page, PreviewController. It modifies the view model in order to instruct the view to hide the site’s header and footer.
View Locations and a Default Controller
Based on my own experience of developing sites with EPiServer 7 CMS and ASP.NET MVC, as well as input from other developers, it seems that we often end up with controllers that do nothing but return a ViewResult. While this is not the case for all sites, and not for all page types in Alloy either, the extra step of adding a controller when all we want is a new page type and view may seem unnecessary.
With this in mind, and considering that ASP.NET MVC is largely built around conventions, the MVC templates feature a controller named DefaultPageController. This controller can handle all page types and simply returns a view result with a view location set to /Views/<page_type_name>/Index.cshtml.
This allows us to easily add views for page types by following the above convention for view locations. For page types that require a controller all we need to do is create one which is more specific in terms of what page type it handles.
A Custom View Engine
It is possible to create partial renderers, templates for pages and blocks in content areas and the like, in a number of ways when using ASP.NET MVC. The three main ways are to create a controller, to create a partial view in the /Views/Shared folder whose name matches the type name of the content type or to create a partial view which is registered at startup using a class that implements the IViewTemplateModelRegistrator interface.
The MVC templates use the two latter approaches extensively. Partly because the first approach, using a controller, is quite costly in terms of performance, and partly because a controller is not really needed for many of the blocks in Alloy. As a result of this, the /Views/Shared folder contains a lot of views, which makes it difficult to find a specific one. Therefore, the templates feature a custom view engine, SiteViewEngine, which adds two additional folders in which partial views for CMS content can be stored – /Views/Shared/PagePartials and /Views/Shared/Blocks.
Content Area Rendering
The WebForms templates feature a customized rendering of content areas. Please view Ted’s article for an extensive explanation of how it works.
As this functionality is at the very heart of the Alloy website, the MVC templates also feature the same customized content area rendering. While I will not go through the rather intricate implementation, I thought I would point out the involved components so that you can have a look for yourself.
- The ContentArea.cshtml display template (located in /Views/Shared/DisplayTemplates) overrides the default display template for content areas and renders them using…
- The HtmlHelpers.RenderBalancedContentArea method checks if the content area should be rendered using a “size tag”. If it should not, it delegates the rendering to EPiServer’s standard method. If it should, it delegates the rendering to an instance of…
- The BalancedContentAreaRenderer class is responsible for the actual rendering of content areas. However, to figure out how to divide content in an area into rows it uses an instance of…
- The ContentAreaRowBalancer class is where the actual logic for how content areas should be divided into rows, and how wide each content should be, resides.
Error Handling
The MVC templates contain a bonus feature in the form of the ErrorHandlingContentRenderer class. This class is registered as the default type for the IContentRenderer interface during the site’s initialization. It wraps the EPiServer 7 CMS’ default implementation and extends it to provide error handling while rendering partial content.
This has the effect that an exception, of a number of non-critical types, thrown while rendering a block or page in a content area will not crash the entire page. Instead it will simply hide the failing partial content. If the HTTP request is made by an editor, it will instead render an error message which can easily be reported to a developer.
Note that the error handling is only in place if the site is in release mode (debug=”false” in web.config). In debug mode the request is probably made by a developer and then the standard error handling is used instead, allowing us developers to more easily see the error message and stack trace.
Beyond making the site more robust, this functionality also illustrates the flexibility of EPiServer 7 CMS’ API.
Navigation Components
When building websites with EPiServer 7 CMS and WebForms, we can utilize a number of web controls such as MenuList and PageTree for building navigations. Currently the framework does not feature such components for ASP.NET MVC.
The templates feature a single extension method for the HtmlHelper class named MenuList which is used to build all three navigations components on the Alloy site – the top menu, the sub navigation and the breadcrumbs.
While there are many possible ways to build each of those navigation elements for an MVC site, the MenuList method is especially designed for flexibility through the use of Razor helpers.
The method requires a root page and a Razor helper as argument. It fetches the children of the root page and filter them based on whether they should be visible to the current visitor and whether they should be displayed in navigation or not. Finally it invokes the Razor helper once for each page.
This produces something similar to template controls in WebForms. As Razor helpers are essentially C# code it can be used for many different things, especially since the Razor helpers can recursively invoke themselves.
Dependency Resolving
ASP.NET MVC is designed for testability and flexibility and EPiServer 7 CMS features the abstractions necessary for us to utilize that. Therefore almost all communication with the API is kept out of the views and placed in separate classes and, to a lesser extent, in the controllers.
To enable us to switch out components without modifying controllers and to make the code practical to write unit tests for, all dependencies in controllers are injected through their constructors. In ASP.NET MVC dependencies for controllers and other components are resolved using a DependencyResolver. The templates feature such a class, StructureMapDependencyResolver, which is set as the default dependency resolver through the DependencyResolverInitialization initialization module.
The dependency resolver wraps the IoC container used by the CMS it self, meaning that we do not have to register implementations for the CMS’ API. Custom classes in the templates generally do not have corresponding interfaces but instead have virtual methods. This means that we do not have to register them either while maintaining the ability to write unit tests for code that relies on such types.
Configuration Management and Static Resources
The WebForms version of the Alloy templates feature an example of how configuration files can be managed for an EPiServer site as well as post build events for minifying and combining CSS and JavaScript files. See Ted’s article for an in depth description of these features.
The MVC templates feature the same configuration handling. Static resources are however not handled through build steps. Instead the resource bundling features in System.Web.Optimization is used.
The CSS and script bundles are configured in an initialization module named BundleConfig. They are then outputted as links in the “master” layout file, _Root.cshtml.
Recommended Reading
- Ted Nyberg’s Article about the Alloy Site
- An Introduction to Razor Helpers used for the menus and breadcrumbs
- Bundling and Minification using System.Web.Optimization