Custom View Locations for Pages, Blocks and more
Background
In a typical Episerver, or MVC Applicaton for that matter, the Views specified in a Controller are resolved by paths defined in the System.Web.Mvc.RazorViewEngine.
The paths for Views are:
- "~/Views/{1}/{0}.cshtml"
- "~/Views/Shared/{0}.cshtml"
In Episerver Page Controllers, we need to specify an Index-method with the Page-type as method parameter, like this:
public class StartPageController : PageController<StartPage>
{
public ActionResult Index(StartPage currentPage)
{
return View(currentPage);
}
}
If we take a look at the paths above, the name of the Controller (except -"Controller") will be used at "{1}", and the name of the Action is used at "{0}", meaning default View path for our Controller above will be:
- "~/Views/StartPage/Index.cshtml"
Flooding of "Index.cshtml" files
If you like me, use ReSharper and namely the "Navigation -> Search Everywhere/Go to Type" function to navigate/find your Views files, the amount of Index.cshtml files you'd end up - by default - becomes annoying. You probably already know this, but the files and folders in your Website project, will be something like this:
- Views
- StartPage
- Index.cshtml
- LandingPage
- Index.cshtml
- CasePage
- Index.cshtml
- ArticlePage
- Index.cshtml
- Etc. etc.
- StartPage
Thankfully, we can make up for that!
Creating a custom ViewEngine
Go ahead and create a new class that inherits System.Web.Mvc.RazorViewEngine. Like this:
public class EpiserverViewEngine : System.Web.Mvc.RazorViewEngine
{
}
To define your own paths for Views (Pages and Catalog-nodes) and PartialViews (Blocks, Partials), add a constructor and set the value of ViewLocationFormats and PartialViewLocationFormats. Like this:
public class EpiserverViewEngine : System.Web.Mvc.RazorViewEngine
{
public EpiserverViewEngine()
{
var partialViewLocations = new[]
{
"~/Views/Blocks/{1}.cshtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Blocks/{1}/{0}.cshtml",
"~/Views/Shared/{1}/{0}.cshtml"
};
var viewLocations = new[]
{
"~/Views/Pages/{1}.cshtml",
"~/Views/Nodes/{1}.cshtml",
"~/Views/Catalog/{1}.cshtml",
"~/Views/Pages/{1}/{0}.cshtml",
"~/Views/Nodes/{1}/{0}.cshtml",
"~/Views/Catalog/{1}/{0}.cshtml"
};
PartialViewLocationFormats = partialViewLocations;
ViewLocationFormats = viewLocations;
}
}
The above paths are the ones I use, and it gives much more flexibility.
Potential problem
I've seen other examples of this where the paths above, are all set to the ViewLocationFormats property - and that includes both Views and PartialViews. This results in Episerver/MVC potentially resolving PartialViews as regular Views. And as we know, a PartialView should not be rendered with a Layout, which also includes model inheriting being a requirement.
I was trying to render a ContentArea in which I had inserted a Block of mine, and that resulted in an exception with the message:
The model item passed into the dictionary is of type 'EPiServer.Core.ContentArea', but this dictionary requires a model item of type 'Namespace.IPageViewModel<T>'
It took me quite some time debugging why this became a problem suddenly. When it finally struck me, that the PartialView belonging to my Block was being resolved as a regular View. So talk about a eureka-experience!
Register your ViewEngine
To register your new EpiserverViewEngine, create an InitializationModule - like this:
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ViewEngineInitializationModule : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
System.Web.Mvc.ViewEngines.Engines.Add(new EpiserverViewEngine());
}
public void Uninitialize(InitializationEngine context) {}
}
You are now ready to use it. You can leave your StartPageController (example above), and use paths like these instead:
- Views/
- Pages/
- StartPage.cshtml
- LandingPage.cshtml
- CasePage.cshtml
- ArticlePage/ (Folder to bundle stuff together)
- Index.cshtml
- _MyArticlePagePartial.cshtml
- Blocks/
- NewsletterBlock.cshtml
- TeaserBlock.cshtml
- Pages/
Feel free to use this, modify it and happy coding!
/Kristian
Comments