ASP.NET Razor Views in Class Libraries

One of my biggest complaints about WebForms in ASP.NET was that you couldn’t easily put interface code such as .ascx controls into DLLs and then use these in an ASP.NET website.

This meant that virtually all the interface code had to reside in the ASP.NET web project – which kills reuse and modularity. You end up with monolithic ASP.NET web applications containing all the UI code.

It was possible to create .ascx controls in DLLs, but it was a horrible kludge.

Enter the Razor

With the introduction of the Razor view engine we can now create views in class libraries, with Intellisense, compilation and design time support. However, it’s not a built-in functionality and takes some work to set up. There are two basic approaches, and you need to decide which is appropriate for you.

I’ve worked on this previously on MVC v3 and MVC v4, but I’ve decided to write about in this blog post purely from the MVC v5 and Razor v3 perspective. If you’re interested in older versions I recommend the following:

Full-Fat MVC Project

The easiest way to do this is create the class library using the ASP.NET Web Application and select the MVC template. This will create a web project which can be compiled down to a single pre-compiled DLL. You can then reference this in your main ASP.NET web application.

Indeed if you follow this approach you can use the routes, controllers and views as if they were part of the main web app.

However, if you’re looking to do something like create html email templates (as I was) you get a lot of overhead and stuff you don’t need throw into the project (Controllers, Models, Scripts etc.).

Razor-Thin Class Library

      The alternative approach I’ve investigated is to start with a basic C# class library for .NET 4.5 and then add just enough functionality to make Razor views work in the editor. I can then include the Razor content as an embedded resource and combine with a library like RazorEngine or RazorGenerator to run the view in code and generate the HTML.

      Edit: as a helpful resource I’ve also created a GitHub project which is a working example, and has a commit for each of the changes made.

      The steps required to make this work:

      1. Create a new C# project using the Class Library template, for framework version 4.5
      2. Using Nuget Package manager add the following packages:
        • Microsoft.AspNet.Razor
        • Microsoft.AspNet.WebPages
        • Microsoft.Web.Infrastructure
        • Microsoft.AspNet.Mvc

        I also added in the .NET framework libraries for System.Web.Abstractions (4.0) and System.Data.Linq (4.0)

      3. Open the .csprj file in a text editor, and change (or edit) the ProjectTypeGuids setting to the following:
            <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
        

        If the setting is not present in your project file, then add it after the <ProjectGuid> setting.  This makes the MVC razor views in the Add New Item dialog box.

      4. Add a web.config file to the project, with the content required to support views. The Visual Studio editor expects to see this file to render Razor Views. I’ve attached the one I created at the end of this post. This was based on the web.config that MVC creates in the Views folder of an MVC project, but has been edited to get intellisense working.

      5. My final step is to save everything, then close and re-open the solution. This is to ensure the VS IDE is properly aware that the project supports Razor pages and loads the correct intellisense. It’s possible it might work without this step, but if you have problems, give it a try.
      6. Having done this, Intellisense works for the model and inline code. You can specify @model <yourclass> at the top and get @Model.<someproperty> intellisense. You can also have all the Razor code functionality. However, HtmlHelper extension methods, such as  @Html.Raw(..), although recognised in the editor, won’t work in RazorEngine, for example, as the Html helper isn’t present in its rendering engine by default. Read this article to find out more.

      web.config file:

      <?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.0.0.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="yourDLLhere" />
            </namespaces>
          </pages>
        </system.web.webPages.razor>
      
        <appSettings>
          <add key="webpages:Version" value="3.0.0.0"/>
          <add key="webpages:Enabled" value="false" />
        </appSettings>
      
        <system.web>
          <compilation targetFramework="4.5">
            <assemblies>
              <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
              <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
              <add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
              <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
            </assemblies>
          </compilation>
          <httpRuntime targetFramework="4.5" />
        </system.web>
      
        <system.webServer>
          <handlers>
            <remove name="BlockViewHandler"/>
            <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
          </handlers>
        </system.webServer>
      
      </configuration>
      
      Advertisements

      16 thoughts on “ASP.NET Razor Views in Class Libraries

        • Looks like something is hard-coded in Razor IntelliSense. It works only if build output path is exactly “bin” and stops working otherwise.

        • Thanks for the pointer! When I wrote this article I think it was based on MVC v4 so it’s possible something else has changed. However between the two it should work.

      1. Hi!

        You need an extra projectypeguid to enable “Add View” under MVC

        {E53F8FEA-EAE0-44A6-8774-FFD645390401}

        It’d be very useful for future visitors if you update your post 🙂

      2. This blog did help a lot, I have successfully created a solution with a MVC project and a Class library project containing my views, and the whole thing works (MVC 5 & Razor 3 here). My solution is running without errors.

        Though I have two remaining (non-blocking) issue:
        First, the intellisense still does not work when I am using ViewBags. I have the following error: “One or more types required to compile a dynamic expression cannot be found. Are you missing a reference?”
        I tried adding the assembly reference in my web.config, but it did not work:

        Secondly, the intellisense still does not work when the HtmlHelpers I am using (Html.TextBoxFor(), Html.LabelFor()… etc.).
        I have the following error: The type arguments for method ‘System.Web.Mvc.Html.InputExtensions.TextBoxFor(…)’ cannot be inferred from the usage. Try specifying the type arguments explicitly.

        Do you have any ideas ?

      3. Hello and thank you very much for the blog post!
        I am facing one challenge though..
        How is it possible to compile it with js,css and image files so everything is contained in a single assembly? Is it possible at all?
        Thanks in advance!

        • There could be several ways to do this (others welcome to suggest theirs!).

          My approach was to create a Virtual controller on my end website. This was then passed the assembly name and resource, so it could return the relevant file from the DLL.

          For example, requesting http://mysite.com/Virtual/SomeLib.Namespace/Test.js would load the library SomeLib.Namespace.dll and look for a resource called Test.js. If found it would return as a javascript file. It did the same for other type e.g. images, html.

          • Thanks a lot for the quick reply! I will take a look at it 🙂
            I have another issue now though..
            Im going for the full fat mvc project solution and i compiled the mvc project to a single dll.
            But Im getting this error now:
            The view ‘Index’ or its master was not found or no view engine supports the searched locations.

            I had the impression that the views should just work?

            Thanks again!
            Anders

          • Razor views in class libraries cannot be referenced from a controller in a web project directly – it doesn’t know how to search for and compile views inside DLLs, it only understands self-hosted views in the actual project.

          • Ah ok makes sense.. Then i guess it might be a better idea to create a nuget package instead

      Leave a Reply

      Fill in your details below or click an icon to log in:

      WordPress.com Logo

      You are commenting using your WordPress.com account. Log Out / Change )

      Twitter picture

      You are commenting using your Twitter account. Log Out / Change )

      Facebook photo

      You are commenting using your Facebook account. Log Out / Change )

      Google+ photo

      You are commenting using your Google+ account. Log Out / Change )

      Connecting to %s