We have an ASP.NET MVC 4 application with around 3000 views in it. We've decided to split this set of views into separated DLLs and compile it with RazorGenerator. We keep only main _Layout.cshtml and related files in the main MVC project.
We cannot load partial views from DLL together with master view in main MVC project. Detailed description is below.
What is already done:
The views compile successfully into DLLs (I've confirmed that they are in the binary)
The PrecompiledMvcEngine object is created and registered for each DLL containing views using the code below in Application_Start in Global.asax.cs:
.
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// ...
// some code determining whether we've got an assembly with views
// ...
var engine = new PrecompiledMvcEngine(assembly);
engine.UsePhysicalViewsIfNewer = true;
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
What does not work:
I cannot load a view defined in the main MVC project (say _Layout.cshtml) with partial view defined in one of the libraries (say Partial.cshtml). I use the following code in controller's action to tell the MVC framework which view I requested:
var view = "~/Views/" + partialName + ".cshtml";
return View(view, "~/Views/Shared/_Layout.cshtml", model);
The error messages says: The view '~/Views/Partial.cshtml' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/Partial.cshtml ~/Views/Shared/_Layout.cshtml
When I attempt to load the views separately by specifying either:
return View("~/Views/Shared/_Layout.cshtml", model);
or
return View(view, model);
, the right view is found. However I need them to be loaded together. The code works when I have all required .cshtml files in the main MVC project.
Note that the views in compiled DLLs have PageVirtualPathAttribute with the same path as specified in the controller action, e.g.:
namespace SomeBaseNamespace.Views
{
[GeneratedCode("RazorGenerator", "1.5.0.0"), PageVirtualPath("~/Views/Partial.cshtml")]
public class Partial : WebViewPage<PartialModel>
{
[CompilerGenerated]
private static class <Execute>o__SiteContainer3
{
// logic
}
public override void Execute()
{
// logic
}
}
}
To sum up, the question is how to call the master view stored in main MVC project with a partial compiled view defined in another project?
At app start, when your app calls this line...
The assemblies containing your external views have likely not yet been loaded, and are therefore not included as view engines. I'd actually recommend against using
AppDomain.CurrentDomain.GetAssemblies()
anyway, as that will include all assemblies loaded at startup.The solution is to add the RazorGenerator.Mvc NuGet package to each project which contains compiled views. This will add the following app start code in a similar manner to yours...
Note how this creates a view engine using the current assembly (your views assembly) and adds it to the static
ViewEngines
collection (contained within the main MVC project).Once in production, I'd also recommend turning off the
UsePhysicalViewsIfNewer
setting, which adds a significant performance overhead.Not all assemblies are loaded when
Application_Start
is called. Add an extra handler:Terminology
BaseMvc - with Razor Generated Views, Controllers etc
ConsumerMvc - Has layout for this project and references BaseMvc
Summary
Create the delivery of the view in the base controller. The view uses a layout which is present in the ConsumerMvc via the _ViewStart.cshtml in BaseMvc. For my situation I had projects with differing layouts, hence the "pointer" layout view. I thought it a useful example.
BaseMvc Example
I created an
AREA
so I could set a default layout./Areas/Components/Controllers/ShoppingController.cs
/Areas/Components/Views/Shopping/Basket.cshtml
/Areas/Components/Views/_ViewStart.cshtml
Link referenced in the code comment: Override view in ASP.NET MVC site not working
ConsumerMvc Example
/Views/Shared/_Layout_Component.cshtml
My Url
http://www.consumermvc.example.com/Components/Shopping/Basket