我正在尝试使用Orchard CMS等可插拔架构构建MVC4/MVC5应用程序.所以我有一个MVC应用程序,它将是启动项目并负责身份验证,导航等.然后将有多个模块单独构建为asp.net类库或剥离mvc项目并具有控制器,视图,数据存储库等.
我花了一整天的时间浏览网页上的教程并下载样本等,发现Kenny有最好的例子 - 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应用程序来处理身份验证,授权和所有请求.我们的插件(模块)被复制到它的子文件夹中.插件也是ASP.NET MVC
具有自己的模型,控制器,视图,css和js文件的应用程序.以下是我们遵循的步骤:
设置MEF
我们基于创建了引擎MEF
,在应用程序启动时发现所有可组合部件,并创建可组合部件的目录.这是一项在应用程序启动时只进行一次修改的任务.引擎需要发现所有可插入部件,在我们的例子中,它们位于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
类的方法是从所谓Application_Start
的方法中的Global.asax.cs
文件.为简单起见,减少了代码.
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
上面的方法中也初始化了自定义控制器工厂和我将在下面定义的自定义视图引擎.
创建从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
必须设置为无共享,因为控制器不能为多个请求重复使用.
创建知道从插件中查找视图的View Engine
需要创建自定义视图引擎,因为按照惯例,视图引擎仅在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(); } }
解决插件中强类型视图的问题
通过仅使用上面的代码,我们无法在插件(模块)中使用强类型视图,因为模型存在于bin
文件夹之外.要解决此问题,请按照以下链接.
请注意,MEF的容器有一个"很好的功能",可以保持对它创建的任何IDisposable对象的引用,并且会导致巨大的内存泄漏.据称,使用此nuget可以解决内存泄漏问题 - http://nuget.org/packages/NCode.Composition.DisposableParts.Signed