ASP.NET MVC3 – Areas in Separate Assemblies

10,367

Solution 1

1 - Seperate you Mvc Areas into differrent Mvc Projects to be compiled into their own seperate assemblies

2 - Add this to your AssemblyInfo.cs class, to call a method when the application is loaded

[assembly: PreApplicationStartMethod(typeof(PluginAreaBootstrapper), "Init")]

3 - Here's what the Init method looks like when it's invoked during the load

public class PluginAreaBootstrapper
{
    public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();

    public static List<string> PluginNames()
    {
        return PluginAssemblies.Select(
            pluginAssembly => pluginAssembly.GetName().Name)
            .ToList();
    }

    public static void Init()
    {
        var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");

        foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll"))
            PluginAssemblies.Add(Assembly.LoadFile(file));

        PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);
    }
}

4 - Add a custom RazorViewEngine

public class PluginRazorViewEngine : RazorViewEngine
{
    public PluginRazorViewEngine()
    {
        AreaMasterLocationFormats = new[]
        {
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.vbhtml"
        };

        AreaPartialViewLocationFormats = new[]
        {
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.vbhtml"
        };

        var areaViewAndPartialViewLocationFormats = new List<string>
        {
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.vbhtml"
        };

        var partialViewLocationFormats = new List<string>
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml"
        };

        var masterLocationFormats = new List<string>
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml"
        };

        foreach (var plugin in PluginAreaBootstrapper.PluginNames())
        {
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.cshtml");
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.vbhtml");
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{1}/{0}.cshtml");
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{1}/{0}.vbhtml");

            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.cshtml");
            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.vbhtml");
            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{0}.cshtml");
            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{0}.vbhtml");

            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.cshtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.vbhtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/{1}/{0}.cshtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/{1}/{0}.vbhtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/Shared/{0}.cshtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/Shared/{0}.vbhtml");
        }

        ViewLocationFormats = partialViewLocationFormats.ToArray();
        MasterLocationFormats = masterLocationFormats.ToArray();
        PartialViewLocationFormats = partialViewLocationFormats.ToArray();
        AreaPartialViewLocationFormats = areaViewAndPartialViewLocationFormats.ToArray();
        AreaViewLocationFormats = areaViewAndPartialViewLocationFormats.ToArray();
    }
}

5 - Register your Areas from your different Mvc (Area) Projects

namespace MvcApplication8.Web.MyPlugin1
{
    public class MyPlugin1AreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get { return "MyPlugin1"; }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "MyPlugin1_default",
                "MyPlugin1/{controller}/{action}/{id}",
                new {action = "Index", id = UrlParameter.Optional}
                );
        }
    }
}

Sourcecode and additional references can can be found here: http://blog.longle.io/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/

Solution 2

You can use MvcContrib with Portable Areas, but this way you would have embedded views.

Just create a MVC and a class library project. Create your area in the MVC project and after your finished move everything except the Views from the area into the class library.

Use NuGet to get this packaged and voila you can use your new NuGet Area in every MVC project.

Solution 3

You can separate your controllers and views without the use of areas. For the controllers you can use Windsor or any other IoC container to resolve the controllers from different assemblies. For example you can register all of your controllers in this way:

container.Register(AllTypes.FromAssemblyInDirectory(new AssemblyFilter(HttpRuntime.BinDirectory)).BasedOn<IController>().Configure(c => c.LifeStyle.Transient));

Also you have to implement IDependencyResolver then set DependencyResolver.SetResolver(...).

For the views you have two options:

  1. Embedded resources and VirtualPathProvider
  2. Simple copy the view files to the appropriate location after build/deploy

We built a simple framework (similar to Portable Areas) using Windsor and embedded resource views provided by a VirutalPathProvider implementation.

Share:
10,367
SkipHarris
Author by

SkipHarris

Updated on August 08, 2022

Comments

  • SkipHarris
    SkipHarris over 1 year

    I am trying to set up an MVC3 solution using areas, but I want to have my areas in different assemblies. For example, I want a parent assembly that contains shared resources like master pages, style sheets, scripts, login page, etc. But I want distinct areas of business functionality in separate assemblies.

    I tried this sample that was written for the MVC2 preview: http://msdn.microsoft.com/en-us/library/ee307987%28VS.100%29.aspx. (Note, I originally found this from this Stack Overflow thread: ASP.NET MVC - separating large app). But it appears that MVC3 does not have the option to move view files into the main project. I’m not crazy about using the embedded resource / VirtualPathProvider option.

    Any suggestions on how to make this work with MVC3?

    Thanks, Skip

  • Kalman Speier
    Kalman Speier almost 11 years
    Sorry but I can't share that code as it's belongs to my previous employee. Btw I can tell you that finally we dropped the embedded views, but the controllers remained in seperated assemblies. What specific part of this solution do you interested?
  • cecilphillip
    cecilphillip almost 11 years
    The embedded views and virtualPathProvider
  • Kalman Speier
    Kalman Speier almost 11 years
    Ok, here you can find the embedded resource handling classes: gist.github.com/speier/6037842
  • Saravanan
    Saravanan almost 9 years
    CAn you please let me know if this will work with aspx pages also