基于动态加载的插件
更新:在这篇文章中阅读下面的最小解决方案
我有一些关于带插件的MVC4解决方案的新手问题。 我搜索了一下,发现了一些好东西,但它不完全符合我的要求,所以我在这里问一些建议。
似乎MVC中类似插件的插件的最佳解决方案是可移植区域(在MvcContrib包中)。 我在这里找到了基本的指导:
http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/
以及一些有用的提示:
http://geekswithblogs.net/michelotti/archive/2010/04/05/mvc-portable-areas-ndash-web-application-projects.aspx
更多内容在这篇文章中:
如何创建ASP.NET MVC区域作为插件DLL?
这很酷,但可悲的是我的要求有点不同:
不幸的是,我需要一个系统,在这个系统中动态地添加和发现插件,而便携式区域则不是这样,它必须由主MVC站点项目引用。 我想只是上传一些东西到网站上,并发现并使用新的组件,所以我打算使用MEF来做到这一点。
幸运的是,我的插件不会像小部件,它可能非常复杂和异构; 相反,它们是必须遵循共同的共享模式的组件。 把它们想象成专门的编辑器:对于每种数据类型,我都会提供一个具有编辑功能的组件:新建,编辑,删除。 所以我想到的是插件控制器,它实现了一个通用接口并提供诸如新建,编辑,删除等操作。
我必须使用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-and-webapi.html
这实质上是将基于MEF的依赖关系解析器和控制器工厂添加到“标准”MVC应用程序。 无论如何,该帖子中的示例是指单一程序集解决方案,而我需要部署几个不同的插件。 所以我稍微修改了代码,使用指向我的插件文件夹的MEF DirectoryCatalog(而不是AssemblyCatalog),然后创建了一个测试MVC解决方案,并在类库中使用了一个插件。
无论如何,当我尝试加载插件控制器时,框架调用我的工厂GetControllerInstance与一个空类型,所以当然MEF不能进行组合。 可能我错过了一些明显的东西,但我是MVC 4的新手,欢迎任何建议或有用的(符合MVC4)链接。 谢谢!
这是基本的代码:
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-nonsense.co.uk/dependency-injection-web-api-and-mvc-4-rc/。
2)另外,第三个组件是DefaultControllerFactory派生的控制器工厂,这对本文很重要,因为它是用于插件托管控制器的工厂。
这里是我的所有这些实现,从几个示例稍作修改:首先HTTP解析器:
public sealed class MefHttpDependencyResolver : IDependencyResolver { private readonly CompositionContainer _container; public MefHttpDependencyResolver(CompositionContainer container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public object GetService(Type serviceType) { if (serviceType == null) throw new ArgumentNullException("serviceType"); string name = AttributedModelServices.GetContractName(serviceType); try { return _container.GetExportedValue(name); } catch { return null; } } public IEnumerable GetServices(Type serviceType) { if (serviceType == null) throw new ArgumentNullException("serviceType"); string name = AttributedModelServices.GetContractName(serviceType); try { return _container.GetExportedValues(name); } catch { return null; } } public IDependencyScope BeginScope() { return this; } public void Dispose() { } }
然后,MVC解析器非常相似,即使在这种情况下对于虚拟示例严格不必要:
public class MefDependencyResolver : IDependencyResolver { private readonly CompositionContainer _container; public MefDependencyResolver(CompositionContainer container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public object GetService(Type type) { if (type == null) throw new ArgumentNullException("type"); string name = AttributedModelServices.GetContractName(type); try { return _container.GetExportedValue(name); } catch { return null; } } public IEnumerable GetServices(Type type) { if (type == null) throw new ArgumentNullException("type"); string name = AttributedModelServices.GetContractName(type); try { return _container.GetExportedValues(name); } catch { return null; } } }
最后控制器工厂:
[Export(typeof(IControllerFactory))] public class MefControllerFactory : DefaultControllerFactory { private readonly CompositionContainer _container; [ImportingConstructor] public MefControllerFactory(CompositionContainer container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public override IController CreateController(RequestContext requestContext, string controllerName) { var controller = _container .GetExports() .Where(c => c.Metadata.Name.Equals(controllerName, StringComparison.OrdinalIgnoreCase)) .Select(c => c.Value) .FirstOrDefault(); return controller ?? base.CreateController(requestContext, controllerName); } }
至于示例控制器,我将它创建为一个类库项目:
[Export(typeof(IController))] [PartCreationPolicy(CreationPolicy.NonShared)] [ExportMetadata("Name", "Alpha")] public sealed class AlphaController : Controller { public ActionResult Index() { ViewBag.Message = "Hello, this is the PLUGIN controller!"; return View(); } }
在主项目(MVC站点)中,我有一个Plugins文件夹,我在其中复制此DLL,并在其控制器的视图的文件夹中添加一组“标准”视图。
这是最简单的情况,可能还有更多的发现和改进,但我需要简单的开始。 无论如何,任何建议都是值得欢迎的。
我目前正在研究相同的问题。 我找到了这个解决方案:
基本上它会在Web应用程序启动时从指定位置和一些名称模式加载程序集:
AssemblyInfo.cs中:
[assembly: PreApplicationStartMethod(
typeof(PluginAreaBootstrapper), "Init")]
PluginAreaBootstrapper.cs:
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的需求。 它非常简单,不需要任何框架,具有最少的配置,允许动态加载插件,并可以与MVC 4一起使用。
该解决方案将程序集插入到Area目录中,但我相信您可以很容易地将其调整为按照您使用路由的方式进行播放。
你可以在这里找到更完整的解决方案,以及更多背景。 我们的需求也是一个DI,所以这也会有所帮助,虽然不需要(第一个链接也为DI提供了解决方案,祝你好运,这并不难)。另外请注意,这些是为MVC3,但很容易转换/迁移到MVC 4
链接地址: http://www.djcxy.com/p/53781.html