【问题标题】:MEF with MVC 4 or 5 - Pluggable Architecture (2014)使用 MVC 4 或 5 的 MEF - 可插拔架构 (2014)
【发布时间】:2014-01-27 19:34:51
【问题描述】:

我正在尝试使用像 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 Controllers and MEF, how to bring these two together? 中所解释的那样,这是我接下来要尝试的事情。但令我惊讶的是,MEF 不能与 MVC 一起使用。

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

【问题讨论】:

  • 大家是否都已将这个设置用于 MVC5?我正在尝试使用 MVC 5 设置相同的东西。感谢您的帮助
  • 这是一个竞争示例,它具有实现 EF 和海峡 ASP.net 的版本似乎完整。 codeproject.com/Articles/1109475/…
  • 为什么没有更多的应用程序使用 MEF?似乎每个人都喜欢这个。

标签: c# asp.net .net asp.net-mvc mef


【解决方案1】:

我参与过一个项目,该项目具有与您描述的类似的可插拔架构,并且使用了相同的技术 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 文件夹之外。要解决此问题,请关注以下link

【讨论】:

  • 每个模块的自定义路由怎么样?我认为每个模块都需要获取路由表的引用,并且全局 asax 应该具有路由接口,其中路由接口将在模块文件夹和核心中查找。
  • 我们通过为每个插件定义单独的区域来解决这个问题。在每个插件中,我们创建了一个继承自 AreaRegistration 的类,并且通过覆盖 RegisterArea 方法,我们能够定义我们想要在插件中使用的路由。
  • 您是否有此解决方案的示例项目?
  • 我同意 cpoDesign。一个示例项目会很好
  • 我也同意 GitHub 上的示例项目非常适合下载 :)
【解决方案2】:

请注意,MEF 的容器有一个“不错的功能”,它可以保留对它创建的任何 IDisposable 对象的引用,并且会导致巨大的内存泄漏。据称可以使用此 nuget 解决内存泄漏问题 - http://nuget.org/packages/NCode.Composition.DisposableParts.Signed

【讨论】:

  • 另一个说 DryIoc 更好的理由 :)
【解决方案3】:

那里有实现插件架构的项目。您可能想使用其中之一或查看他们的源代码以了解他们如何完成这些事情:

另外,404 on Controllers in External Assemblies 正在采取一种有趣的方法。通过阅读这个问题,我学到了很多东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-17
    • 2014-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    • 2013-12-27
    相关资源
    最近更新 更多