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

2023-09-02 01:28:41 作者:最犀利的 内涵犀利

我想建立一个可插入式架构像果园CMS中的MVC4 / MVC5应用。所以我有一个MVC应用程序,这将是启动项目,并照顾权威性的,导航等,然后将有单独建成asp.net类库或剥离下来MVC项目中的多个模块,并有控制器,视图,数据回购等。

I am trying to build a MVC4/MVC5 application with a pluggable architecture like Orchard CMS. So I have a MVC application which will be the startup project and take care of auth, navigation etc. Then there will be multiple modules built separately as asp.net class libraries or stripped down mvc projects and have controllers, views, data repos etc.

我花了一整天经历在网络上的教程和下载等样品,发现肯尼大约有最好的例子 - http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and-webapi.html

I have spent all day going through tutorials on the web and downloading samples etc and found that Kenny has the best example around - http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and-webapi.html

我能够从模块(单独的DLL)导入控制器如果我添加引用这些DLL。但是,使用MEF背后的原因是能够在运行时添加的模块。我想随着意见的DLL文件拷贝到启动项目在〜/模块//目录(我已经成功地做​​到这一点)和MEF只想接他们回家。努力使MEF加载这些库。

I am able to import the controllers from the modules(separate DLLs) if I add reference to those DLLs. But the reason behind using MEF is being able to add modules at runtime. I want the DLLs along with views to be copied to a ~/Modules// directory in the startup project (I have managed to do this) and MEF would just pick them up. Struggling to make MEF load these libraries.

还有MefContrib在这个答案ASP.NET MVC 4.0控制器和MEF,如何使这两个在一起?这是未来的事情,我要试试。不过,我很惊讶,MEF行不通的用MVC的开箱。

There is also MefContrib as explained in this answer ASP.NET MVC 4.0 Controllers and MEF, how to bring these two together? which is the next thing I am about to try. But I'm surprised that MEF doesnt work out of the box with MVC.

有没有人有类似的架构工作(带或不带MefContrib)?或者你知道的任何现有的框架,我可以看看吗?最初,我甚至想到剥离果园CMS和使用它作为一个框架,但它太复杂。也将是不错的发展MVC5应用程序采取WebAPI2的优势。

Has anyone got a similar architecture working (with or without MefContrib)? Or do you know of any existing framework that I could look at? Initially I even thought of stripping Orchard CMS and using it as a framework but it is too complex. Also would be nice to develop the app in MVC5 to take advantage of WebAPI2.

推荐答案

我做过一个项目,也有类似的插入式架构像你描述的,它使用同样的技术 ASP.NET MVC MEF 。我们有这样处理的身份验证,授权和所有请求的主机ASP.NET MVC应用程序。我们的插件(模块)被复制到它的一个子文件夹。该插件还为 ASP.NET MVC 应用程序有它自己的模式,控制器,视图,CSS和JS文件。这是我们遵循的步骤,使其工作:

I have worked on a project that had similar pluggable architecture like the one you described and it used the same technologies ASP.NET MVC and MEF. We had a host ASP.NET MVC application that handled the authentication, authorization and all requests. Our plugins(modules) were copied to a sub-folder of it. The plugins also were ASP.NET MVC applications that had its own models, controllers, views, css and js files. These are the steps that we followed to make it work:

设置MEF

我们根据 MEF 则发现所有组合的部件在应用程序启动和创建可组合部件的目录中创建引擎。这是在应用程序启动pefromed仅一次任务。该引擎需要发现所有可插拔的部件,这在我们的例子中或位于主机应用程序的文件夹或在模块(插件) 文件夹。

We created engine based on MEF that discovers all composable parts at application start and creates a catalog of the composable parts. This is a task that is pefromed only once at application start. The engine needs to discover all plugable parts, that in our case were located either in the bin folder of the host application or in the Modules(Plugins) folder.

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部件的类的样品code。类的撰写方法是从 Global.asax中的的Application_Start 方法调用。 CS 文件。的code为减小为简单起见

This is the sample code of the class that performs discovery of all MEF parts. The Compose method of the class is called from the Application_Start method in the Global.asax.cs file. The code is reduced for the sake of simplicity.

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));
    }
}

假定所有的插件都复制到模块文件夹位于主机应用程序的根目录下的一个独立的子文件夹。每个插件子文件夹包含浏览子文件夹和 DLL 从每个插件。在的Application_Start 上述方法也被初始化自定义控制器工厂和自定义视图引擎,我将在下面定义。

It is assumed that all plugins are copied in a separate sub-folder of the Modules folder that is located in the root of the host application. Each plugin subfolder contains Views sub-folder and the dll from each plugin. In the Application_Start method above are also initialized the custom controller factory and the custom view engine which I will define below.

创建控制器工厂,读取MEF

下面是$ C $下定义自定义控制器工厂,会发现需要处理请求的控制器:

Here is the code for defining custom controller factory which will discover the controller that needs to handle the request:

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();
        }
    }
}

此外每个控制器必须标有导出属性:

Additionally each controller must be marked with Export attribute:

[Export("Plugin1", typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Plugin1Controller : Controller
{
    //
    // GET: /Plugin1/
    public ActionResult Index()
    {
        return View();
    }
}

导出的第一个参数属性构造必须是唯一的,因为它规定了合同的名称和唯一标识每个控制器。该 PartCreationPolicy 必须设置为无共享,因为控制器不能再用于多个请求。

The first parameter of the Export attribute constructor must be unique because it specifies the contract name and uniquely identifies each controller. The PartCreationPolicy must be set to NonShared because controllers cannot be reused for multiple requests.

这知道您的插件的意见的创建视图引擎

Creating View Engine that knows to find the views from the plugins

创建自定义视图引擎是必要的,因为按照惯例视图引擎查找观点只在主机应用程序的意见文件夹。由于插件都位于独立的模块文件夹,我们需要告诉给视图引擎来寻找有也。

Creation of custom view engine is needed because the view engine by convention looks for views only in the Views folder of the host application. Since the plugins are located in separate Modules folder, we need to tell to the view engine to look there also.

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();
    }
}

解决使用强类型的意见问题的插件

仅使用上述code,我们不能用我们的插件(模块)的强类型的意见,因为模型存在的文件夹之外。为了解决这个问题遵循以下link.

By using only the above code, we couldn't use strongly typed views in our plugins(modules), because models existed outside of the bin folder. To solve this problem follow the following link.