MEF з MVC 4 або 5 - підключається архітектура (2014)


80

Я намагаюся створити додаток 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 разом з поданнями були скопійовані в каталог ~ / Modules // у проекті запуску (мені це вдалося зробити), і MEF просто забрав би їх. Прагнучи змусити MEF завантажити ці бібліотеки.

Існує також MefContrib, як пояснено у цій відповіді ASP.NET MVC 4.0 Контролери та MEF, як об’єднати ці два? це наступне, що я збираюся спробувати. Але я здивований, що MEF не працює нестандартно з MVC.

Хтось мав подібну архітектуру, що працює (з MefContrib чи без)? Спочатку я навіть думав вилучити Orchard CMS і використовувати її як фреймворк, але це занадто складно. Також було б непогано розробити програму в MVC5, щоб скористатися перевагами WebAPI2.


1
Чи всі ви отримували цю установку для роботи з MVC5? Я намагаюся налаштувати те саме на MVC 5. Ваша допомога вдячна
Junior

1
Ось конкурентний приклад, який має версії, що реалізують як EF, так і протоку ASP.net Здається завершеним. codeproject.com/Articles/1109475/…
BrownPony

Чому більше програм не використовують MEF? Здається, кожен котиться на цьому.
Джонні,

Відповіді:


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Повинен бути встановлений в 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папкою. Щоб вирішити цю проблему, перейдіть за таким посиланням .


1
як щодо власного маршруту для кожного окремого модуля? Я думаю, що кожен модуль повинен отримати посилання на маршрутизуваний та глобальний asax, повинен мати інтерфейс маршруту, в якому інтерфейс маршруту буде виглядати як у папці модуля, так і в ядрі.
шариф y

3
Ми вирішили це, визначивши окрему область для кожного плагіна. У кожному плагіні ми створили клас, який успадковує AreaRegistration, і замінивши метод RegisterArea, ми змогли визначити маршрути, які ми хотіли використовувати в плагінах.
Ілля Дімов

10
У вас є десь зразок проекту для цього рішення?
cpoDesign

2
я згоден з cpoDesign. зразок проекту був би непоганий
chris vietor

2
Я також згоден, що зразок проекту на GitHub було б чудово завантажити :)
Kbdavis07,

5

Тільки майте на увазі, що контейнер MEF має "приємну функцію", яка зберігає посилання на будь-який створений IDisposable об'єкт, що призведе до величезного витоку пам'яті. Нібито витік пам'яті можна усунути за допомогою цього nuget - http://nuget.org/packages/NCode.Composition.DisposableParts.Signed


Ще одна причина сказати, що DryIoc краще :)
Хасан Тарек,

3

Є проекти, що реалізують архітектуру плагінів. Можливо, ви захочете скористатися одним із них або поглянути на їх вихідний код, щоб побачити, як вони виконують ці дії:

Крім того, 404 щодо контролерів у зовнішніх складах застосовує цікавий підхід. Я багато чому навчився, просто прочитавши питання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.