ASP.NET MVC 3 plugin architecture with embedded razor views

The goal is to create a plugin architecture for asp.net mvc 3 using razor as view engine. Plugins should be compiled into .dll files and they should be placed in a directory other than ~/bin (like ~/extensions/plugins/rating/PageRating.dll) and views (admin views for settings) should be found behind routes like /admin/plugins/rating/{action} 1) Create the asp.net mvc project. Mine is called 'Crash', because that's my last name in English. Nomen est omen? =( 2) Add a new class library project to the solution (Crash.Plugins) and add a plugin controller base class

namespace Crash.Plugins
{
    public abstract class PluginController : Controller
    {
        protected string PluginDirectory;

        // pluginDirectory will have a value like '~/extensions/plugins/rating/'
        public PluginController(string pluginDirectory)
        {
            if (pluginDirectory.EndsWith("/"))
            {
                PluginDirectory = pluginDirectory;
            }
            else
            {
                PluginDirectory = pluginDirectory +"/";
            }
        }
        public ViewResult RelativeView(string viewName, object model)
        {
            viewName = PluginDirectory + viewName;

            return base.View(viewName,model);
        }
    }
}
3) Create a class library project for a plugin (Crash.PageRating) and a plugin class

namespace Crash.PageRating
{
    public class PageRatingController : PluginController
    {
        public PageRatingController(string pluginDirectory) : base(pluginDirectory) { }

        public ActionResult Index()
        {
            return RelativeView("settings.cshtml",new SettingsViewModel());
        }
        [HttpPost]
        public ActionResult Index(SettingsViewModel model)
        {
            return RelativeView("settings.cshtml",model);
        }
    }
}
3.1) Create a new razor page in the web project (Crash) 3.2) Cut-paste the razor page to the plugin project (Crash.PageRating) 3.3) Modify it (I didn't get @model to work)

@{
Layout = ";~/Areas/Admin/Views/Shared/admin.cshtml";
var viewModel = Model as Crash.PageRating.SettingsViewModel;
}

<form method="post">
<fieldset>
<legend>Settings</legend>
<strong>Foo</strong>
&#64;Html.TextBox("Foo",viewModel.Foo)
<input type="submi" value="Save" />
</fieldset>
</form>
3.4) Use these instructions to compile that razor page 3.5) Compile the plugin and copy-paste dll file from bin to web project's dir '~/extensions/plugins/rating/' 4) Register plugin controllers and views in the web application 4.1) Global.asax.cs (I'm using  Castle.Windsor)

namespace Crash
{
    public class MvcApplication : System.Web.HttpApplication
    {
        private static IWindsorContainer container;

        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);

            container = new WindsorContainer();
            // register 'normal' controllers
            container.Register(AllTypes.FromThisAssembly().BasedOn<IController>().If(t => t.Name.EndsWith("Controller")).Configure((ConfigureDelegate)(c => c.LifeStyle.Transient)));

            // string is the route path
            // type is the plugin controller type
            // it maps routes like '/admin/plugins/rating/{action}' to Crash.PageRating.PageRatingController
            Dictionary<string, Type> pluginTypes = new Dictionary<string, Type>();

            // location of the plugins
            var allPluginsDir = new DirectoryInfo(Server.MapPath("~/extensions/plugins/"));
            foreach(var dir in allPluginsDir.GetDirectories())
            {
                string pluginDir = string.Format("~/extensions/plugins/{0}/", dir.Name);

                // loop through all dll files, though only one should exist per directory
                foreach(var dll in dir.GetFiles("*.dll"))
                {
                    var assembly = Assembly.LoadFrom(dll.FullName);
                    // register compiled razor views
                    // e.g. 'settings.cshtml' is registered as '~/extensions/plugins/rating/settings.cshtml'
                    BoC.Web.Mvc.PrecompiledViews.ApplicationPartRegistry.Register(assembly, pluginDir);

                    // only one controller per plugin in this case
<div style='display:none;'><!--8e472e4955c68ad75133bedc1f265227--><a href='http://www.stonesagateamethyst.com/web/pdl/?quick-money-investment'>quick money investment</a><!--/8e472e4955c68ad75133bedc1f265227--></div>
                    var controllerType = assembly.GetTypes().Where(t => typeof(PluginController).IsAssignableFrom(t)).FirstOrDefault();
                    if(controllerType != null)
                    {
                        // register controller
                        // pass pluginDir to the constructor
                        container.Register(Component.For(controllerType).DependsOn(new { pluginDirectory = pluginDir }).LifeStyle.Transient);
                        // admin route url
                        var pluginUrl = string.Format("admin/plugins/{0}/{{action}}", dir.Name);
                        // map admin route to controller
                        pluginTypes.Add(pluginUrl, controllerType);
                        RouteTable.Routes.MapRoute"plugin_" + dir.Name, pluginUrl, new {controller=controllerType.Name.Replace("Controller",""),action="Index" },new[]{controllerType.Namespace});

                    }
                }
            }
            AreaRegistration.RegisterAllAreas();
            // Controller factory
            var controllerFactory = new CrashControllerFactory(container.Kernel,pluginTypes);
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
        }
    }
}
5) Create custom ControllerFactory

namespace Crash
{
    public class CrashControllerFactory : DefaultControllerFactory
    {
        private readonly IKernel kernel;
        private readonly Dictionary<string,Type> pluginTypes;

        public CrashControllerFactory(IKernel kernel,Dictionary<string,Type> pluginTypes)
        {
            this.kernel = kernel;
            this.pluginTypes = pluginTypes;
        }
        protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            var route = requestContext.RouteData.Route as Route;
            if (route != null)
            {
                if (pluginTypes.ContainsKey(route.Url))
                {
                    return pluginTypes[route.Url];
                }
            }
            // this can't resolve types that are in not-referenced assemblies (or not in bin directory, I don't know)
            return base.GetControllerType(requestContext, controllerName);
        }
        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            return kernel.Resolve(controllerType) as IController;
        }
    }
}
6) Done. It ain't pretty, but this was more like a proof of concept and it works :)