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...
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
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...
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(SomeBaseNamespace.Views.RazorGeneratorMvcStart), "Start")]
namespace SomeBaseNamespace.Views
{
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
}
}
}
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.