MVC re-using Views with a library project

I had quite a few sites that used common MVC partial views spread across different Visual Studio projects, living by the DRY principle I decided to move these to a library project to make managing things easier. Crucially I wanted to also be able to override the common views in my projects if I needed to.

MVC does support it but I found a few hurdles when setting up the project so I thought I would share them here should they be help to anyone else.

By far the easiest way to implement this is to just setup another Empty MVC project rather than a class library, that way everything should be setup properly. You can then reference the project from your main web solution.

Although the view files will not exist in the final output directory they will be copied to the bin directory assuming everything is setup correctly. Out of the box this will not work since the default Razor view engine will not check in the bin folder for views.

protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
    ViewEngines.Engines.Clear();
    // Set alternative view location
    RazorViewEngine engine = new RazorViewEngine();

    engine.ViewLocationFormats = new[] 
    {
        "~/Views/{1}/{0}.cshtml",
        "~/Views/Shared/{0}.cshtml",
        "~/Views/Partials/{0}.cshtml",
        "~/bin/Views/{1}/{0}.cshtml",
        "~/bin/Views/Shared/{0}.cshtml",
        "~/bin/Views/Partials/{0}.cshtml",
    };

    engine.PartialViewLocationFormats = engine.ViewLocationFormats;
    ViewEngines.Engines.Add(engine);
}

Now when resolving a view or partial view the view engine will first check the local view folders as normal and if there are no site specific views found it will move on to the bin location where the common reusable views are output. One caveat here is that if you want to perform this kind of fall through check you can only resolve views in the standard way i.e:

@Html.Partial("_Partial", model)

You can still specify full paths if you know the location exists and you want it to be fixed like so:

@Html.Partial("~/Views/Partials/_Partial.cshtml", model)

If the class library project is used then there are a few more steps to get things working. First issue will likely be broken intellisense on the views and weird errors such as:

CS0103: The name 'model' does not exist in the current context

To fix this make sure you have a web.config in both the project root and the View folder root. The content of these config files will depend on the version of MVC being used and any external dependencies. The best method is to create a new Empty MVC project then compare the web.config files.

An example web.config file for the Views folder in MVC 5.2.3

<?xml version="1.0"?>
<configuration>
    <configSections>
        <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
            <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
            <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <system.web.webPages.razor>
        <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <pages pageBaseType="System.Web.Mvc.WebViewPage">
            <namespaces>
<add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Optimization"/> <add namespace="System.Web.Routing" /> <!-- any additional dependencies here --> </namespaces> </pages> </system.web.webPages.razor> <appSettings> <add key="webpages:Enabled" value="false" /> </appSettings> <system.webServer> <handlers> <remove name="BlockViewHandler"/> <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" /> </handlers> </system.webServer> <system.web> <compilation> <assemblies> <add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies> </compilation> </system.web> </configuration>

Next ensure all the views are set to be built as Content and copied to the output directory, these settings can be found in the individual file properties of the views. Finally you also need to ensure that the View folder's web.config is also set to be built as Content and copied to the output directory.