Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Razor Generator: how to use view compiled in a library as the partial view for master defined in main mvc project

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:

  1. The views compile successfully into DLLs (I've confirmed that they are in the binary)

  2. 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?

like image 492
tomash Avatar asked Nov 23 '12 10:11

tomash


People also ask

How do you render a partial view inside a view in MVC?

In order to add Partial View, you will need to Right Click inside the Controller class and click on the Add View option in order to create a View for the Controller.

What is partial view in Razor?

A partial view is a Razor markup file ( . cshtml ) without an @page directive that renders HTML output within another markup file's rendered output. The term partial view is used when developing either an MVC app, where markup files are called views, or a Razor Pages app, where markup files are called pages.

How do I add a partial view model?

To create a partial view, right click on the Shared folder -> click Add -> click View.. to open the Add View popup, as shown below. You can create a partial view in any View folder. However, it is recommended to create all your partial views in the Shared folder so that they can be used in multiple views.


2 Answers

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.

like image 139
Tom Bowers Avatar answered Sep 28 '22 21:09

Tom Bowers


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

public ActionResult Basket()
{
    return View();
}

/Areas/Components/Views/Shopping/Basket.cshtml

Welcome to the Basket!

/Areas/Components/Views/_ViewStart.cshtml

@{
    //-- NOTE: "Layout_Component.cshtml" do not exist in the BaseMVC project. I did not
    // experiment with having it in both projects. A tip if you do is to ensure both
    // the base and consumer _Layout_Component.cshtml files are both razor
    // generated to allow the razor generator to handle the overrride. See
    // my other SO answer linked below.
    Layout = "~/Views/Shared/_Layout_Component.cshtml";
}

Link referenced in the code comment: Override view in ASP.NET MVC site not working

ConsumerMvc Example

/Views/Shared/_Layout_Component.cshtml

@{
    Layout = "~/Views/Shared/_Layout_ConsumerMvc.cshtml";
}
@RenderBody()

My Url

http://www.consumermvc.example.com/Components/Shopping/Basket

like image 43
Valamas Avatar answered Sep 28 '22 21:09

Valamas