MEF 与 MVC 4或5-可插入架构(2014)

我正在尝试构建一个 MVC4/MVC5应用程序,该应用程序具有像 Orchard CMS 这样的可插入架构。因此,我有一个 MVC 应用程序,这将是启动项目,并照顾认证,导航等。然后将有多个模块分别构建为 asp.net 类库或者简化 mvc 项目,并且有控制器、视图、数据回收等。

我花了一整天的时间在网上浏览教程和下载样本等,发现肯尼有最好的例子左右-http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and-webapi.html

如果添加对这些 DLL 的引用,我就能够从模块(单独的 DLL)导入控制器。但是使用 MEF 背后的原因是能够在运行时添加模块。我希望将 DLL 和视图一起复制到启动项目中的 ~/Module//目录(我已经做到了这一点) ,然后 MEF 会将它们捡起来。努力让 MEF 加载这些库。

还有 MefContrib,正如这个答案中所解释的 NET MVC 4.0控制器和 MEF,如何将二者结合起来?,这是我将要尝试的下一件事情。但是我很惊讶 MEF 在 MVC 中不能开箱即用。

是否有人使用类似的架构(使用或不使用 MefContrib) ?最初我甚至想过剥离 Orchard CMS 并将其作为一个框架使用,但它太复杂了。在 MVC5中开发应用程序以利用 WebAPI2也是不错的。

43780 次浏览

我曾经参与过一个项目,该项目具有类似于您所描述的可插拔架构,并且使用了相同的技术 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方法从 Global.asax.cs文件中的 Application_Start方法调用。为了简单起见,减少了代码。

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设置为 NonShared,因为控制器不能用于多个请求。

创建知道从插件中查找视图的视图引擎

需要创建自定义视图引擎,因为按照约定,视图引擎只在宿主应用程序的 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文件夹之外。要解决这个问题,请遵循以下 链接

有一些项目实现了一个插件架构。您可能希望使用其中之一,或者查看它们的源代码,看看它们是如何完成这些任务的:

同时,外部程序集中的控制器上的404采取了一种有趣的方法,我通过阅读问题学到了很多。

请注意,MEF 的容器有一个“不错的特性”,可以保留对它创建的任何 IDisposable 对象的引用,这将导致巨大的内存泄漏。据说内存泄漏可以用这个核心 -http://nuget.org/packages/NCode.Composition.DisposableParts.Signed来解决