具有MVC 4或5的MEF-可插拔架构(2014)


80

我正在尝试使用Orchard CMS之类的可插入体系结构构建MVC4 / MVC5应用程序。因此,我有一个MVC应用程序,它将作为启动项目并负责auth,导航等。然后将有多个模块分别作为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也是很好的。


1
你们每个人都有使用MVC5的设置吗?我正在尝试使用MVC 5设置相同的功能。感谢您的帮助
少年

1
这是一个竞争示例,具有同时实现EF和strait ASP.net的版本。codeproject.com/Articles/1109475/…–
BrownPony

为什么没有更多的应用程序使用MEF?每个人似乎都为此付出了自己的努力。
约翰尼

Answers:


105

我曾从事过一个项目,该项目具有与您所描述的相似的可插拔体系结构,并且使用了相同的技术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文件夹之外。要解决此问题,请点击以下链接


1
每个模块的自定义路线如何?我认为每个需要获取路由表和全局asax引用的模块都应具有路由接口,其中路由接口将在模块文件夹和核心中同时出现。
sharif y 2014年

3
我们通过为每个插件定义单独的区域来解决此问题。在每个插件中,我们创建了一个从AreaRegistration继承的类,并且通过重写RegisterArea方法,我们能够定义我们要在插件中使用的路由。
Ilija Dimov 2014年

10
您是否有某个解决方案的示例项目?
cpoDesign 2014年

2
我同意cpoDesign。一个示例项目会很好
克里斯·维耶托

2
我还同意可以在GitHub上下载示例项目:)
Kbdavis07 2014年


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.