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:
- Create a new C# project using the Class Library template, for framework version 4.5
- Using Nuget Package manager add the following packages:
I also added in the .NET framework libraries for System.Web.Abstractions (4.0) and System.Data.Linq (4.0)
- Open the .csprj file in a text editor, and change (or edit) the ProjectTypeGuids setting to the following:
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.
- 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.
- 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.
- 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.
<?xml version="1.0"?> <configuration> <configSections> <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=184.108.40.206, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=220.127.116.11, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=18.104.22.168, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> </sectionGroup> </configSections> <system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=22.214.171.124, 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="126.96.36.199"/> <add key="webpages:Enabled" value="false" /> </appSettings> <system.web> <compilation targetFramework="4.5"> <assemblies> <add assembly="System.Web.Abstractions, Version=188.8.131.52, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add assembly="System.Web.Routing, Version=184.108.40.206, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add assembly="System.Data.Linq, Version=220.127.116.11, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="System.Web.Mvc, Version=18.104.22.168, 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>