Reusing views between areas in ASP.NET MVC
Posted 20 Apr 2012
ASP.NET MVC Area's are great for organizing functionality that logically goes together. However, the default view location logic is somewhat limited.
If I want to use a view from one area in a controller for another, I can specify it's path explicitly. This works fine. However, when the view uses EditorTemplates or DisplayTemplates in the corresponding subfolders, the view engine will not be able to locate these because it will try to resolve these independently from the view that is being rendered based on the executing controller and area.
What's going wrong?
The ViewEngine is in charge of resolving views and templates based on the controller name, the view name and the area name. By default the WebFormViewEngine considers the following paths:
~/Areas/[area]/Views/[controller]/[view].aspx
~/Areas/[area]/Views/[controller]/[view].ascx
~/Areas/[area]/Views/Shared/[view].aspx
~/Areas/[area]/Views/Shared/[view].ascx
It then falls back to non-area based views :
~/Views/[controller]/[view].aspx
~/Views/[controller]/[view].ascx
~/Views/Shared/[view].aspx
~/Views/Shared/[view].ascx
As you can see, the view engine expects the view and any EditorTemplate or DisplayTemplate to be located in either the area of the controller or in the main views folder.
So, one possible solution for sharing views and templates between areas is to move the files to the main views folder. However, since my main views folder is crowded enough as it is, I prefer not to do that.
Since the default view engine uses simple path generation based on the area, controller and view names it's not going to be trivial to force it to look for templates in the folders relative to the currently rendering view. That context is simply not available.
Solution
An alternate approach is to expand on the Shared folder pattern by introducing a Shared area. We need to tweak the view engine a bit to get that supported:
public class WebFormViewEngine : System.Web.Mvc.WebFormViewEngine
{
public WebFormViewEngine()
: this( null )
{
}
public WebFormViewEngine( IViewPageActivator viewPageActivator ) : base( viewPageActivator )
{
AreaViewLocationFormats = AreaViewLocationFormats
.Union( new[] { "~/Areas/Shared/Views/{1}/{0}.aspx", "~/Areas/Shared/Views/{1}/{0}.ascx" } )
.ToArray();
AreaPartialViewLocationFormats = AreaViewLocationFormats;
}
}
That's all there is to it. We've added some additional search paths for the view engine to consider. It will now look in the Shared area folder for views and templates.
During application startup, you do need to replace the default WebFormsViewEngine with this customized version:
ViewEngines.Engines.Remove( ViewEngines.Engines.First( e => e is System.Web.Mvc.WebFormViewEngine ) );
ViewEngines.Engines.Insert( 0, new WebFormViewEngine() );
If you're using Razor views, you can apply a similar fix to the RazorViewEngine.
TL;DR
Reusing views from one area in a controller for another area is possible by specifying the full path to the view. Doing so however breaks the Editor- and DisplayTemplates because they are resolved independently from the view that is being rendered.
The solution is to introduce a Shared area. Move the views and templates there and then tweak the view engine to also consider the shared area when resolving the views and templates.
?
Thank you very much!
I have to add some information:
1) In order to use this technique with Razor you have to register both Razor and WebForm engines.
2) Into the constructor you have to copy the content of the property AreaViewLocationFormats into AreaPartialViewLocationFormats.
Anonymous
Hi all,
remember to copy web.config file from Area/Views to the new Shared/Views.
Thanks to oo for the code to solve the _ViewStart!
Anonymous
You have just saved my life. I have been hunting down an explanation like this for some time and yours is the clearest and simplest explanation of the view search algorithm. Thanks!
Unknown
Hi Marnix,
I hit a similar problem, but with needing to share Razor - not WebForm - views between areas in MVC4, but not wanting to bundle everything into the root 'Views' folder.
As an addendum to your post, RazorViewEngine can be used in place of WebFormViewEngine, with .cshtml / .vbhtml replacing .aspx and .ascx. This gives the same functionality to Razor views.
oo
Thanks for this.
Views in the Shared area use _ViewStart.cshtml in the shared area folder, and not in the original Area. In my case this is undesirable, as each Area has its own layout.
I amended the generated _ViewStart.cshtml to prefer the Layout in the original area, if existing:
http://pastie.org/4111318