November Happy Hour will be moved to Thursday December 5th.

Views: 37071
Number of votes: 8
Average rating:

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

Comments

Per Magne Skuseth
Per Magne Skuseth Jan 23, 2013 02:32 PM

Very nice! Works great.
Note: I had to create a new connectionstring.config file as the build script did not create one. Every other config file got copied after build though.

Jan 24, 2013 10:09 AM

If you don't have an 64 bit OS, you want to change the configuration files as they refer to the folder program files (x86) which only normally exists on Windows 64 bit.

David Sandeberg
David Sandeberg Jan 25, 2013 02:10 PM

Great to have a reference project for mvc as well! Thanks for putting in the effort Joel :)

I had some trouble locating the module refered to in bullet 9 of the installation instructions but eventually found it in the install directory for EPiServer:
C:\Program Files (x86)\EPiServer\CMS\7.0.586.1\Install\Modules\Alloy\util\setup\alloy.episerverdata

Santosh Achanta
Santosh Achanta Jan 29, 2013 01:45 AM

Good one David, you made it easy for others by specifying location of the package to import :-) But when you browse admin url of the site you must be asked to install alloy content if you set the startpageid to '0'? Hence you do not need to locate the data file manually and import.

Pooja Dhingra
Pooja Dhingra Jan 29, 2013 06:27 AM

Hi,

I am not able to use the MVC Alloy Sample in Episerver 7. Can anyone please help and tell me the steps how to make it work???

Thanks in advance,
Pooja

Jan 29, 2013 07:41 AM

Pooja, Hope this helps:

1. Install EPiServer 7.
2. Create an empty CMS database using Deployment Center.
3. Extract the AlloyMVC.zip.
4. Open Visual Studio as an administrator.
5. In Visual Studio, open the Alloy\Alloy.csproj from the extracted content.
6. Create the connectionstring.config and point it to the database created in step 2.
7. Start the site (F5). (You will receive a message about a missing page ID.)
8. In EPiServer.config in the site root, change pageStartId to 0.
9. Go to admin mode (//localhost:37010/episerver/cms/admin) and import the package.

corb
corb Jan 29, 2013 07:01 PM

I've followed the steps to the T multiple times and I keep getting

The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.

Requested URL: /Util/login.aspx

Joel Abrahamsson
Joel Abrahamsson Jan 30, 2013 07:51 AM

Hi Corbin,

Are you running a 32 bit version of Windows? If so, as Toni wrote above, you need to update the config files to reflect that EPiServer is installed in c:\Program Files rather than c:\Program Files (x86).

Debasish Banerjee
Debasish Banerjee Feb 4, 2013 11:14 AM

Very good example...this is helpful for ppl who are new to razor syntax as well (like me)!

David Sandeberg
David Sandeberg Feb 22, 2013 05:34 PM

Hi Joel!

Iv'e been trying to add XForms on a block but I can't seem to get it working properly. I see you have an XForms block in the template package. Have you gotten that to work?

I've looked at this blogpost
http://www.eyecatch.no/blog/2013/01/using-xforms-and-mvc-in-an-episerver-7-block/
and read this thread
http://world.episerver.com/Modules/Forum/Pages/Thread.aspx?id=64533&epslanguage=en

I can't get the form to post back to the current page since i have a custom route registered which makes the action on the form resolve using my custom route and not to the url of the current page.

I can however post to a standard mvc controller and save the data on to the form in but then the context of the current page is lost.

Would really appreciate some pointers on this one :)

Cheers,
David

giaophong
giaophong Mar 8, 2013 05:51 AM

Great example

mattias_jonsson_cycom
mattias_jonsson_cycom Apr 22, 2013 11:53 AM

I get a problem when trying to change the connection string config-file. I says "The file has been modified outside of the source editor. Do you want to reload it?". The message is strange cause I have not modified the file some where else, and does not have it open elsewhere. I just open the project directly after unzipping it. When I press YES, the config-file reverts back to empty connectionString="". If I press No, it asks me the same question again, when pressing F5, and then I get compilation error that the connectionstring does not exist. So my question is if any one can give me some help to solve this. I also tried to delete the file connectionStrings.config, create a new and point it to the right connection string, but get same error.

Any suggestions are highly appreciated.

Ha a great day!

Friendly greetings from Mattias!

Eric
Eric May 15, 2013 03:13 PM

delete

Eric
Eric May 15, 2013 03:13 PM

When downloading this zip. Make sure you unblock it before you unzip it. Otherwise you will have problem with access denied on files, and that might look like missing files or 404 but if you let ie diagnose the problem it will say access denied.

 poojari.santosh@gmail.com
poojari.santosh@gmail.com Jun 3, 2013 02:08 PM

I followed the above setups to setup mvc but no success . I got following error when I hit f5
Error 1 The imported project "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk. C:\Users\sapo\Desktop\AlloyMVC\AlloyMVC\Alloy\[MSBuild]\CommonConfigTransforms.xml 3 3 Alloy
Please help.

 poojari.santosh@gmail.com
poojari.santosh@gmail.com Jun 3, 2013 02:30 PM

I installed VS2012 and the import was referencing VS2010.. So now I changed the configuration in import to visualstudio\v11.0 and it worked fine.

mkontula
mkontula Jun 11, 2013 06:23 PM

In order to get intellisense to work with razor views cluttered with html helpers, make sure you add targetFramework="4.0" to your web.config under system.web/compilation. Otherwise VS will show you red error lines under every helper and no intellisense. Strangely enough, the code still compiles and functions as it should.

Palle Mertz
Palle Mertz Jun 17, 2013 09:57 AM

I love the templates, but i do have 2 wishes that could make the templates even better:

Support for dependency injection in Filters (i know you can do injection but filters are only newed up once using GlobalFilters.Filters.Add()). I did however fix this myself with an implementation of IFilterProvider, that creates the filters i want on the fly (from the structuremap container of course).

But one thing I did not manage to figure out is to make the TemplateCoordinator fully dependency injectable (i know you can use the servicelocator but i am a perfectionist and want to inject via constructors). Any suggestions?

Jonathan Roberts
Jonathan Roberts Jul 14, 2015 03:56 PM

HI,

How do you get the currentpage PageId?

Peter Healy
Peter Healy May 27, 2016 11:35 AM

There is no Web.config file?

Please login to comment.