MVC4 MEF-based dynamically loaded plugins
更新:请阅读下面的文章,了解最基本的解决方案
我有一些关于带插件的MVC4解决方案的新手问题。我搜索了一下发现一些好东西,但它不完全符合我的要求,所以我在这里寻求一些建议。
对于MVC中类似小部件的插件来说,最好的解决方案似乎是可移植区域(在MVCContrib包中)。我在这里找到了基本指导:
http://lostechies.com/erichester/2009/11/01/asp-net-mvc-portable-areas-via-mvcontrib/
这里还有一些有用的提示:
http://geekswithblogs.net/michelotti/archive/2010/04/05/mvc-portable-areas-ndash-web-application-projects.aspx
这篇文章的更多内容:
如何将ASP.NET MVC区域创建为插件dll?
这都很酷,但遗憾的是我的要求有点不同:
不幸的是,我需要一个动态添加和发现插件的系统,而对于可移植区域则不是这样,它必须由主MVC站点项目引用。我只想上传一些东西到网站上,让它发现并使用新的组件,所以我将使用MEF来完成这个任务。
幸运的是,我的插件不会像小部件那样复杂,而且可能是异构的;相反,它们是必须遵循公共共享模式的组件。把它们想象成专门的编辑器:对于每种数据类型,我将提供一个具有编辑功能的组件:新建、编辑、删除。所以我在考虑插件控制器,它实现了一个公共接口,并提供诸如new、edit、delete等操作。
我必须使用MVC4,将来我必须添加本地化和移动定制。
我必须避免复杂框架的依赖性,并尽可能保持代码的简单。
所以,每当我想在这个网站上添加一个新的数据类型进行编辑时,我只想在它的插件文件夹中放置一个DLL,用于逻辑材料(控制器等),并在正确的位置放置一些视图,以使站点发现并使用新的编辑器。
最终,我可以将视图包括在DLL本身中(我发现这个:http://razorgenerator.codeplex.com,以及本教程:http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled-razor-views-in-your-dll/,我想我可以使用codeplex razorgenerator,因为它引用的代码与vs2012不兼容),但我可能会使用它。我最好把它们分开(也因为本地化和移动感知的要求);我正在考虑在我的站点管理区域添加一个上载机制,在那里你可以用带视图的控制器和文件夹的dll上载一个zip,然后让服务器在需要时解压缩和存储文件。这将允许我轻松地修改视图,而不必重新部署整个外接程序。
所以我开始寻找MEF和MVC,但是大多数的帖子都是关于MVC2的,不兼容。我更幸运的是,这主要集中在Web API上,但看起来很有前途,也很简单:
http://kennytordeur.blogspot.it/2012/08/mef-in-aspnet-mvc-4-和-webapi.html
这实际上是在"标准"MVC应用程序中添加了一个基于MEF的依赖关系解析器和控制器工厂。总之,本文中的示例引用了一个组装解决方案,而我需要部署几个不同的插件。所以我稍微修改了代码,使用指向插件文件夹的mef directorycatalog(而不是assemblycatalog),然后用类库中的单个插件创建了一个测试MVC解决方案。
无论如何,当我尝试加载插件控制器时,框架使用空类型调用我的工厂getcontrollerinstance,因此MEF当然不能继续组合。可能我遗漏了一些明显的东西,但我对MVC4还不熟悉,欢迎任何建议或有用的(兼容MVC4)链接。谢谢!
以下是基本代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | public static class MefConfig { public static void RegisterMef() { CompositionContainer container = ConfigureContainer(); ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container)); System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new MefDependencyResolver(container); } private static CompositionContainer ConfigureContainer() { //AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); DirectoryCatalog catalog = new DirectoryCatalog( HostingEnvironment.MapPath("~/Plugins")); CompositionContainer container = new CompositionContainer(catalog); return container; } } public class MefDependencyResolver : IDependencyResolver { private readonly CompositionContainer _container; public MefDependencyResolver(CompositionContainer container) { _container = container; } public IDependencyScope BeginScope() { return this; } public object GetService(Type serviceType) { var export = _container.GetExports(serviceType, null, null).SingleOrDefault(); return (export != null ? export.Value : null); } public IEnumerable GetServices(Type serviceType) { var exports = _container.GetExports(serviceType, null, null); List createdObjects = new List(); if (exports.Any()) createdObjects.AddRange(exports.Select(export => export.Value)); return createdObjects; } public void Dispose() { } } public class MefControllerFactory : DefaultControllerFactory { private readonly CompositionContainer _compositionContainer; public MefControllerFactory(CompositionContainer compositionContainer) { _compositionContainer = compositionContainer; } protected override IController GetControllerInstance( System.Web.Routing.RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new ArgumentNullException("controllerType"); var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault(); IController result; if (null != export) result = export.Value as IController; else { result = base.GetControllerInstance(requestContext, controllerType); _compositionContainer.ComposeParts(result); } //eelse return result; } } |
您可以从以下位置下载完整的测试解决方案:
http://www.filedropper.com/mvcplugins
编辑:第一个有效的最小解决方案以下是我的发现,希望它们能对其他新手有所帮助:我没有成功地运行上面回复中引用的框架,我想肯定有一些东西需要为VS2012和MVC4更新。不管怎样,我看了一下代码,又在谷歌上搜索了一下:
1)首先,我感到困惑的一个原因是两个同名的不同接口:IDependencyResolver。如果我理解的很好,一个(system.web.http.dependencies.idependencyresolver)用于WebAPI,另一个(system.web.mvc.idependencyresolver)用于通用DI。这篇文章帮助了我:http://lucid nusse.co.uk/dependency-injection-web-api-and-mvc-4-rc/。
2)另外,第三个组件是DefaultControllerFactory派生的控制器工厂,这对本文至关重要,因为它是用于插件托管控制器的工厂。
我目前正在处理同一个问题。我找到了这个解决方案:
- 博客帖子:http://blog.longle.net/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/
- 源代码:https://bitback.org/darincreason/mvcaplication8.web
基本上,它从指定位置加载程序集,并在Web应用程序启动时使用某些名称模式:
程序集信息.cs:
1 2 |
插件eabootstrapper.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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", SearchOption.AllDirectories)) PluginAssemblies.Add(Assembly.LoadFile(file)); PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly); // Add assembly handler for strongly-typed view models AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; } private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs) { var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies(); // Check we don't already have the assembly loaded foreach (var assembly in currentAssemblies) { if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name) { return assembly; } } return null; } } |
但我相信您可以创建一些可以动态加载程序集的目录观察器,所以您甚至不需要重新启动Web应用程序。
在我看来,它满足了你的1、2和4需求。它非常简单,不需要任何框架,配置最少,允许动态加载插件,并与MVC4一起工作。
此解决方案将程序集插入区域目录,但我相信您可以很容易地使用路由调整程序集以播放所需的内容。
你可以在这里找到更完整的解决方案,这里还有更多的背景。我们的需求也是一个DI,所以这也会有点帮助,尽管不需要(第一个链接也为DI提供了解决方案)。祝你好运,这并不难。还请注意,这些是针对MVC3的,但很容易转换/迁移到MVC 4