MVC 4或5的MEF – 可插拔架构(2014)
我正在尝试使用像Orchard CMS这样的可插入架构来构buildMVC4 / MVC5应用程序。 所以我有一个MVC应用程序,这将是启动项目和照顾auth,导航等。然后将有多个模块单独构build为asp.net类库或剥离mvc项目,并有控制器,视图,数据仓库等。
我已经花了整整一天的时间在网上的教程和下载样本等,发现肯尼有最好的例子 – http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and -webapi.html
我能够从模块(独立的DLL)中导入控制器,如果我添加对这些DLL的引用。 但是使用MEF的原因是能够在运行时添加模块。 我希望将DLL和视图一起复制到启动项目的〜/ Modules //目录中(我设法做到了这一点),MEF只会把它们选中。 努力使MEF加载这些库。
还有MefContrib在这个答案解释ASP.NET MVC 4.0控制器和MEF,如何把这两个一起? 这是我将要尝试的下一件事情。 但是我很惊讶,MEF没有像MVC那样开箱即可。
有没有人有类似的架构工作(有或没有MefContrib)? 最初,我甚至想到剥离Orchard CMS并将其用作框架,但这太复杂了。 在MVC5中开发应用程序以利用WebAPI2也是很好的。
我曾经参与过一个类似于您所描述的可插拔架构的项目,它使用了与ASP.NET MVC
和MEF
相同的技术。 我们有一个主机ASP.NET MVC应用程序处理身份validation,授权和所有请求。 我们的插件(模块)被复制到它的一个子文件夹。 插件也是ASP.NET MVC
应用程序,它有自己的模型,控制器,视图,CSS和JS文件。 这些是我们遵循的步骤,以使其工作:
设置MEF
我们创build了基于MEF
引擎,该引擎在应用程序启动时发现所有可组合的部分,并创build可组合部件的目录。 这是一个在应用程序启动时仅被冲刷一次的任务。 引擎需要发现所有可插入的部分,在我们的情况下,这些部分位于主机应用程序的bin
文件夹中或Modules(Plugins)
文件夹中。
public class Bootstrapper { private static CompositionContainer CompositionContainer; private static bool IsLoaded = false; public static void Compose(List<string> pluginFolders) { if (IsLoaded) return; var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"))); foreach (var plugin in pluginFolders) { var directoryCatalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules", plugin)); catalog.Catalogs.Add(directoryCatalog); } CompositionContainer = new CompositionContainer(catalog); CompositionContainer.ComposeParts(); IsLoaded = true; } public static T GetInstance<T>(string contractName = null) { var type = default(T); if (CompositionContainer == null) return type; if (!string.IsNullOrWhiteSpace(contractName)) type = CompositionContainer.GetExportedValue<T>(contractName); else type = CompositionContainer.GetExportedValue<T>(); return type; } }
这是执行发现所有MEF部件的类的示例代码。 该类的Compose
方法由Global.asax.cs
文件中的Application_Start
方法调用。 为简单起见,代码被减less了。
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { var pluginFolders = new List<string>(); var plugins = Directory.GetDirectories(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules")).ToList(); plugins.ForEach(s => { var di = new DirectoryInfo(s); pluginFolders.Add(di.Name); }); AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); Bootstrapper.Compose(pluginFolders); ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory()); ViewEngines.Engines.Add(new CustomViewEngine(pluginFolders)); } }
假设所有插件都复制到位于主机应用程序根目录下的Modules
文件夹的一个单独的子文件夹中。 每个插件子文件夹都包含Views
子文件夹和每个插件的dll
。 上面的Application_Start
方法也初始化了我将在下面定义的自定义控制器工厂和自定义视图引擎。
创build从MEF读取的控制器工厂
这里是定义自定义控制器工厂的代码,它将发现需要处理请求的控制器:
public class CustomControllerFactory : IControllerFactory { private readonly DefaultControllerFactory _defaultControllerFactory; public CustomControllerFactory() { _defaultControllerFactory = new DefaultControllerFactory(); } public IController CreateController(RequestContext requestContext, string controllerName) { var controller = Bootstrapper.GetInstance<IController>(controllerName); if (controller == null) throw new Exception("Controller not found!"); return controller; } public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) { return SessionStateBehavior.Default; } public void ReleaseController(IController controller) { var disposableController = controller as IDisposable; if (disposableController != null) { disposableController.Dispose(); } } }
此外,每个控制器必须标有“ Export
属性:
[Export("Plugin1", typeof(IController))] [PartCreationPolicy(CreationPolicy.NonShared)] public class Plugin1Controller : Controller { // // GET: /Plugin1/ public ActionResult Index() { return View(); } }
Export
属性构造函数的第一个参数必须是唯一的,因为它指定了合同名称并唯一标识每个控制器。 PartCreationPolicy
必须设置为NonShared,因为控制器不能重复使用多个请求。
创build视图引擎,知道从插件中查找视图
需要创build自定义视图引擎,因为按照惯例,视图引擎仅在主机应用程序的Views
文件夹中查找视图。 由于插件位于单独的Modules
文件夹中,因此我们需要告诉视图引擎在那里查看。
public class CustomViewEngine : RazorViewEngine { private List<string> _plugins = new List<string>(); public CustomViewEngine(List<string> pluginFolders) { _plugins = pluginFolders; ViewLocationFormats = GetViewLocations(); MasterLocationFormats = GetMasterLocations(); PartialViewLocationFormats = GetViewLocations(); } public string[] GetViewLocations() { var views = new List<string>(); views.Add("~/Views/{1}/{0}.cshtml"); _plugins.ForEach(plugin => views.Add("~/Modules/" + plugin + "/Views/{1}/{0}.cshtml") ); return views.ToArray(); } public string[] GetMasterLocations() { var masterPages = new List<string>(); masterPages.Add("~/Views/Shared/{0}.cshtml"); _plugins.ForEach(plugin => masterPages.Add("~/Modules/" + plugin + "/Views/Shared/{0}.cshtml") ); return masterPages.ToArray(); } }
用插件中的强types视图解决问题
通过仅使用上面的代码,我们不能在我们的插件(模块)中使用强types视图,因为模型存在于bin
文件夹之外。 要解决这个问题,请按照下面的链接 。
只要知道MEF的容器有一个“很好的function”,它保持对它创build的任何IDisposable对象的引用,并将导致巨大的内存泄漏。 据说内存泄漏可以用这个nuget来解决 – http://nuget.org/packages/NCode.Composition.DisposableParts.Signed
有实现插件体系结构的项目。 您可能想要使用其中的一个或查看其源代码,以查看它们如何完成这些事情:
- ASP.NET MVC插件框架 (使用MVC 4)
- .NET 4.0带embedded式视图的ASP.NET MVC 3插件体系结构 (显然使用MVC 3,但基本原则可能仍然适用)
此外, 404外部程序员控制器正在采取一种有趣的方法。 通过阅读这个问题,我学到了很多东西。