MEF với MVC 4 hoặc 5 - Kiến trúc có thể cắm được (2014)


80

Tôi đang cố gắng tạo ứng dụng MVC4 / MVC5 với kiến ​​trúc có thể cắm được như Orchard CMS. Vì vậy, tôi có một ứng dụng MVC sẽ là dự án khởi động và chăm sóc xác thực, điều hướng, v.v. Sau đó, sẽ có nhiều mô-đun được xây dựng riêng biệt dưới dạng thư viện lớp asp.net hoặc rút gọn các dự án mvc và có bộ điều khiển, chế độ xem, kho dữ liệu, v.v.

Tôi đã dành cả ngày để xem các hướng dẫn trên web và tải xuống các mẫu, v.v. và nhận thấy rằng Kenny có ví dụ tốt nhất về - http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and -webapi.html

Tôi có thể nhập bộ điều khiển từ các mô-đun (các DLL riêng biệt) nếu tôi thêm tham chiếu vào các DLL đó. Nhưng lý do đằng sau việc sử dụng MEF là có thể thêm các mô-đun trong thời gian chạy. Tôi muốn các tệp DLL cùng với các khung nhìn được sao chép vào thư mục ~ / Modules // trong dự án khởi động (tôi đã quản lý để làm điều này) và MEF sẽ chỉ lấy chúng. Cố gắng làm cho MEF tải các thư viện này.

Ngoài ra còn có MefContrib như được giải thích trong câu trả lời này Bộ điều khiển ASP.NET MVC 4.0 và MEF, làm thế nào để kết hợp hai điều này với nhau? đó là điều tiếp theo tôi sắp thử. Nhưng tôi ngạc nhiên rằng MEF không hoạt động hiệu quả với MVC.

Có ai có một kiến ​​trúc tương tự đang hoạt động (có hoặc không có MefContrib) không? Ban đầu, tôi thậm chí đã nghĩ đến việc tách Orchard CMS và sử dụng nó như một khung công tác nhưng nó quá phức tạp. Cũng sẽ rất tốt nếu bạn phát triển ứng dụng trong MVC5 để tận dụng lợi thế của WebAPI2.


1
Bạn đã từng có thiết lập này để làm việc với MVC5 chưa? Tôi đang cố gắng thiết lập điều tương tự với MVC 5. Sự giúp đỡ của bạn được đánh giá cao
Junior

1
Đây là một ví dụ cạnh tranh có các phiên bản triển khai cả EF và eo biển ASP.net Có vẻ đã hoàn tất. codeproject.com/Articles/1109475/…
BrownPony

Tại sao nhiều ứng dụng không sử dụng MEF? Tất cả mọi người dường như tự mình lăn lộn với cái này.
johnny

Câu trả lời:


105

Tôi đã làm việc trên một dự án có kiến ​​trúc có thể cắm được tương tự như bạn đã mô tả và nó sử dụng các công nghệ tương tự ASP.NET MVC và MEF. Chúng tôi đã có một ứng dụng ASP.NET MVC lưu trữ xử lý xác thực, ủy quyền và tất cả các yêu cầu. Các plugin (mô-đun) của chúng tôi đã được sao chép vào một thư mục con của nó. Các plugin cũng là các ứng dụng ASP.NET MVC có các mô hình, bộ điều khiển, chế độ xem, tệp css và js riêng. Đây là các bước mà chúng tôi đã làm theo để làm cho nó hoạt động:

Thiết lập MEF

Chúng tôi đã tạo công cụ dựa trên MEF để khám phá tất cả các bộ phận có thể ghép khi khởi động ứng dụng và tạo danh mục các bộ phận có thể ghép được. Đây là tác vụ chỉ được thực hiện một lần khi khởi động ứng dụng. Công cụ cần phải khám phá tất cả các bộ phận có thể cắm được, trong trường hợp của chúng tôi, chúng nằm trong binthư mục của ứng dụng máy chủ hoặc trong Modules(Plugins)thư mục.

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

Đây là mã mẫu của lớp thực hiện khám phá tất cả các phần của MEF. Các Composephương pháp của lớp được gọi từ các Application_Startphương pháp trong các Global.asax.cstập tin. Mã được giảm vì lợi ích của đơn giản.

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

Giả định rằng tất cả các plugin được sao chép trong một thư mục con riêng biệt của Modulesthư mục nằm trong thư mục gốc của ứng dụng chủ. Mỗi thư mục con plugin chứa thư mục con Viewsvà DLL từ mỗi plugin. Trong Application_Startphương thức trên cũng được khởi tạo nhà máy sản xuất bộ điều khiển tùy chỉnh và công cụ chế độ xem tùy chỉnh mà tôi sẽ xác định bên dưới.

Tạo nhà máy sản xuất bộ điều khiển đọc từ MEF

Đây là mã để xác định nhà máy sản xuất bộ điều khiển tùy chỉnh sẽ khám phá bộ điều khiển cần xử lý yêu cầu:

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

Ngoài ra, mỗi bộ điều khiển phải được đánh dấu bằng Exportthuộc tính:

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

Tham số đầu tiên của phương thức Exportkhởi tạo thuộc tính phải là duy nhất vì nó chỉ định tên hợp đồng và xác định duy nhất từng bộ điều khiển. Các PartCreationPolicyphải được thiết lập để NonShared vì bộ điều khiển không thể được tái sử dụng cho nhiều yêu cầu.

Tạo View Engine biết cách tìm các khung nhìn từ các plugin

Việc tạo công cụ chế độ xem tùy chỉnh là cần thiết vì công cụ chế độ xem theo quy ước chỉ tìm kiếm các chế độ xem trong Viewsthư mục của ứng dụng lưu trữ. Vì các plugin được đặt trong Modulesthư mục riêng biệt , chúng tôi cần yêu cầu công cụ xem cũng xem ở đó.

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

Giải quyết vấn đề với các chế độ xem được nhập mạnh trong plugin

Bằng cách chỉ sử dụng mã trên, chúng tôi không thể sử dụng các chế độ xem được nhập mạnh trong các plugin (mô-đun) của chúng tôi, vì các mô hình tồn tại bên ngoài binthư mục. Để giải quyết vấn đề này, hãy làm theo liên kết sau .


1
làm thế nào về tuyến đường tùy chỉnh cho từng mô-đun riêng lẻ? tôi nghĩ rằng mỗi mô-đun cần thiết để có được tham chiếu của asax có thể định tuyến và toàn cầu nên có giao diện tuyến trong đó giao diện tuyến sẽ trông cả trong thư mục mô-đun và lõi.
sharif y

3
Chúng tôi đã giải quyết điều đó bằng cách xác định khu vực riêng biệt cho mỗi plugin. Trong mỗi plugin, chúng tôi đã tạo một lớp kế thừa từ AreaRegistration và bằng cách ghi đè phương thức RegisterArea, chúng tôi có thể xác định các tuyến mà chúng tôi muốn sử dụng trong các plugin.
Ilija Dimov

10
Bạn đã có dự án mẫu nào đó cho giải pháp này chưa?
cpoDesign

2
tôi đồng ý với cpoDesign. một dự án mẫu sẽ rất tốt
chris vietor

2
Tôi cũng đồng ý dự án mẫu trên GitHub sẽ là tuyệt vời để tải về :)
Kbdavis07


3

Có những dự án triển khai kiến ​​trúc plugin. Bạn có thể muốn sử dụng một trong những thứ này hoặc xem mã nguồn của chúng để xem cách chúng thực hiện những điều này:

Ngoài ra, 404 trên Bộ điều khiển trong Lắp ráp bên ngoài đang áp dụng một cách tiếp cận thú vị. Tôi đã học được rất nhiều chỉ bằng cách đọc câu hỏi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.