13

I'm designing a plugin framework for ASP.NET MVC3 using Razor views, and I'm having an issue getting the embedded views to work correctly.

The plugin framework is designed to have these features:

  • Each plugin has its own models, controllers and views. The views are embedded resources, and the controllers derive from a PluginController class
  • The plugins have dependency references to a shared class library that defines the PluginController base class
  • The "shell" web application that hosts the plugins must not hold references to any plugins at design time, since it does not know at design time which plugins it has.
  • The plugin dll's are dropped in a folder in the shell application, which is not the /bin folder
  • The shell takes care of:
    1. Discovering the plugins (using reflection)
    2. Registering all controllers (I'm using Spring.Net for this)
    3. Creating routes to the controllers
    4. Serving the razor files (cshtml) through a custom VirtualPathProvider

Now everything works fine, except when the embedded views have references to types within the plugin dll. Then I get the infamous error (names left out):

The type or namespace name '[Plugins]' does not exist in the namespace '[MyPluginSolution]' (are you missing an assembly reference?)

The reason for this is that the csc compiler which is invoked runtime to compile the razor views only get the dll references from the bin folder and the GAC.

I've also tried pre-compiling the views using this technique but in the end it gives the same results, since the runtime insists on compiling a wrapper for the pre-compiled razor view.

I could of course drop the plugin dll in the /bin folder, but my question is:

Is there a way to register dlls in a non-bin (and non-GAC) folder, and treat them as "first class citizens" so they can be used by the razor views?

lasseschou
  • 1,550
  • 15
  • 24

6 Answers6

14

Ok, the solution was found using this article.

First I create a class with a PreApplicationStartMethod. This method scans the plugin folder and copies the dlls to the AppDomain.DynamicDirectory.

Then each of these dll's are loaded using BuildManager.AddReferencedAssembly.

And voilà, the strongly-typed Razor views compile beautifully. See the code here:

[assembly: PreApplicationStartMethod(typeof(MySolution.PluginHandler.PluginActivator), "Initialize")]
namespace MySolution.PluginHandler
{
    public class PluginActivator
    {
        private static readonly DirectoryInfo PluginFolderInfo;

        static PluginActivator() {
            PluginFolderInfo = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins"));
        }

        public static void Initialize() {
            CopyPluginDlls(PluginFolderInfo, AppDomain.CurrentDomain.DynamicDirectory);
            LoadPluginAssemblies(AppDomain.CurrentDomain.DynamicDirectory);
        }

        private static void CopyPluginDlls(DirectoryInfo sourceFolder, string destinationFolder)
        {
            foreach (var plug in sourceFolder.GetFiles("*.dll", SearchOption.AllDirectories)) {
                if (!File.Exists(Path.Combine(destinationFolder, plug.Name))) {
                    File.Copy(plug.FullName, Path.Combine(destinationFolder, plug.Name), false);
                }
            }
        }

        private static void LoadPluginAssemblies(string dynamicDirectory)
        {
            foreach (var plug in Directory.GetFiles(dynamicDirectory, "*.dll", SearchOption.AllDirectories)) {
                Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(plug));
                BuildManager.AddReferencedAssembly(assembly);
            }
        }
    }
}

I hope this can help other programmers that want to create a clean plugin framework using these new technologies.

Marijn
  • 10,367
  • 5
  • 59
  • 80
lasseschou
  • 1,550
  • 15
  • 24
  • Is the reason for not putting plugins in the bin dir is that you dont want the app to restart? this solution only loads a plugin on app start-up (BuildManager.AddReferencedAssembly) - is it possible to reference the assembly dynamically in the dynamicDirectory? – Anthony Johnston Sep 06 '11 at 15:15
  • The reason is that this solution can have potentially a large amount of plugins, each with 3-5 dlls's. And since you cannot put them into subfolders under /bin, the preferred solution was to use another folder for this. – lasseschou Sep 07 '11 at 08:41
1

Please take a look at NOPCommerce Source code. It has nice plugin framework based on work by Shannon.

1

You can also go for "Area" feature in MVC3 and 4 , you can create a nice plugin system. It gives separation of handling plugin's model,view and controller in it's own assembly.

1

David Ebbo recently blogged about precompiling Razor views into assemblies. You can view the post here.

You should be able to avoid registering the assemblies directly by dynamically loading the assemblies (I would typically use my IoC container for this) and then calling BuildManager.AddReferencedAssembly for each plugin assembly.

Ben Foster
  • 34,340
  • 40
  • 176
  • 285
  • Thanks Ben. But in his case, just like in Chris van de Steeg's case, the host appliation has a direct reference to the plugin assembly, so it doesn't solve my problem unfortunately. – lasseschou Jun 24 '11 at 12:06
  • I've updated my answer but note this is all "in theory" :) - more details http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx – Ben Foster Jun 24 '11 at 12:19
  • Hi Ben, your latest edit pointed me in the right direction. The solution was to include a pre-Application_Start method that copies all plugin dlls to the AppDomain.DynamicDirectory, and afterwards invoking BuildManager.AddReferencedAssembly(). This article shows how to do this, both in Full trust and Medium trust: http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx. I'll post the answer myself here. – lasseschou Jun 27 '11 at 08:59
1

Check out MEF

You can also do this with Windsor Installers - Mike Hadlow ha a good stab at this: http://mikehadlow.blogspot.com/2010/10/experimental-aspnet-mvc-add-ins-updated.html

0

You might find this helpful http://www.adverseconditionals.com/2011/07/portable-aspnet-code-using.html

I have started creating two projects

MyLibrary MyLibrary.Templates

and have the views as Content in .Templates, and added as links and set to EmbeddedResource in MyLibrary. People that want to override the views can install the .Templates project.

mcintyre321
  • 12,996
  • 8
  • 66
  • 103