20

I would like to break out my controllers and views into separate class libraries so they can be reused in multiple ASP.NET MVC 3 applications. The controllers part was not an issue when using a separate assembly, however getting the view engine to locate the view was.

I ended up using Compile your asp.net mvc Razor views into a seperate dll.

Is there an easier way that I missed?

blu
  • 12,905
  • 20
  • 70
  • 106

3 Answers3

18

I have modified the idea posted here, to work with MVC3. It was pretty fast and easy. The only minor drawback is that shared views need to be embedded resources, and therefore, compiled.

  • Put your shared views (.cshtml, .vbhtml files) into a library project. (I also have some shared controllers in this project.) If you want to use the _Layout.cshtml from your application, make sure you include a _ViewStart.cshtml, that points to it, in with your shared views.

  • In the library project, set all of your views' Build Action properties to Embedded Resource.

  • In the library project add the following code which will write the contents of your views to a tmp/Views directory.

.

public class EmbeddedResourceViewEngine : RazorViewEngine
{
    public EmbeddedResourceViewEngine()
    {
        ViewLocationFormats = new[] {
        "~/Views/{1}/{0}.aspx",
        "~/Views/{1}/{0}.ascx",
        "~/Views/Shared/{0}.aspx",
        "~/Views/Shared/{0}.ascx",
        "~/Views/{1}/{0}.cshtml",
        "~/Views/{1}/{0}.vbhtml",
        "~/Views/Shared/{0}.cshtml",
        "~/Views/Shared/{0}.vbhtml",
        "~/tmp/Views/{0}.cshtml",
        "~/tmp/Views/{0}.vbhtml"
    };
        PartialViewLocationFormats = ViewLocationFormats;

        DumpOutViews();
    }

    private static void DumpOutViews()
    {
        IEnumerable<string> resources = typeof(EmbeddedResourceViewEngine).Assembly.GetManifestResourceNames().Where(name => name.EndsWith(".cshtml"));
        foreach (string res in resources) { DumpOutView(res); }
    }

    private static void DumpOutView(string res)
    {
        string rootPath = HttpContext.Current.Server.MapPath("~/tmp/Views/");
        if (!Directory.Exists(rootPath))
        {
            Directory.CreateDirectory(rootPath);
        }

        Stream resStream = typeof(EmbeddedResourceViewEngine).Assembly.GetManifestResourceStream(res);
        int lastSeparatorIdx = res.LastIndexOf('.');
        string extension = res.Substring(lastSeparatorIdx + 1);
        res = res.Substring(0, lastSeparatorIdx);
        lastSeparatorIdx = res.LastIndexOf('.');
        string fileName = res.Substring(lastSeparatorIdx + 1);

        Util.SaveStreamToFile(rootPath + fileName + "." + extension, resStream);
    }
}

I'm using Adrian's StreamToFile writer, found here.

  • In the Global.asax.cs of your application add:

.

public static void RegisterCustomViewEngines(ViewEngineCollection viewEngines)
{
   //viewEngines.Clear(); //This seemed like a bad idea to me.
   viewEngines.Add(new EmbeddedResourceViewEngine());
}

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    RegisterCustomViewEngines(ViewEngines.Engines);
}
Community
  • 1
  • 1
Carson Herrick
  • 357
  • 4
  • 13
  • in my case resStream is null.Possible some issues with ReflectionPermissions (http://msdn.microsoft.com/en-us/library/xc4235zt(v=vs.110).aspx). Has anybody the same issue? – Anton Putov Jul 27 '14 at 13:24
  • This actually worked. Easiest solution so far. Props! – Exzile Dec 08 '14 at 14:50
3

Just a few additions to Carson Herrick's excellent post...

  1. You will need to resolve a few of the references (you will need to include System.Runtime.Remoting into your project).

  2. Utils.SaveStreamToFile needs to be changed to ->

    System.Runtime.Remoting.MetadataServices.MetaData.SaveStreamToFile(resStream, rootPath + fileName + "." + extension);
    
  3. You may get the error - The view must derive from WebViewPage, or WebViewPage<TModel>. The answer is here: The view must derive from WebViewPage, or WebViewPage<TModel>

  4. When you deploy the project, it is highly likely you will get an error when you load the project. You need to give the APP POOL you are using (full) rights to the folder.

Community
  • 1
  • 1
JsAndDotNet
  • 16,260
  • 18
  • 100
  • 123
3

Take a look at mvc contrib's portable areas: http://www.lostechies.com/blogs/hex/archive/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib.aspx They were made specifically for this purpose. If you go that road, it is less code you have to mantain ;-)

santiagoIT
  • 9,411
  • 6
  • 46
  • 57
  • I have looked at the los techies post. I'm sure it works well for people, but it is total overkill for what I need. Messaging with the bus and the other configuration seems heavy handed to me. The Razor view compilation is one property setting on the views (albeit on every view) and that's it. – blu Jan 26 '11 at 02:34