MEF и MVC 3 - как да зареждам динамично вградени изгледи от mef контейнер?

Създавам MVC 3 приложение, където се използва MEF. Основната идея е да има механизъм за приставки, при който моделите, контролерите и изгледите се зареждат динамично по време на изпълнение от mef контейнера.

Всеки плъгин/модул се състои от две сглобки:

  • Module1.Data.dll (съдържа дефиниции на модели)
  • Module1.Web.dll (съдържа контролери и изгледи)

и се поставят в директорията Plugins в кошчето за уеб приложения:

  • WebApp/Bin/Plugins/Module1.Data.dll
  • WebApp/Bin/Plugins/Module1.Web.dll
  • WebApp/Bin/Plugins/Module2.Data.dll
  • WebApp/Bin/Plugins/Module2.Web.dll
  • WebApp/Bin/Plugins/ModuleCore.Data.dll
  • WebApp/Bin/Plugins/ModuleCore.Web.dll
  • и т.н...

Има и основен модул, към който се обръщат всички други модули: ModuleCore.Data.dll и съответно ModuleCore.Web.dll.

След това в Global.asax контейнерът се изгражда по следния начин:

AggregateCatalog catalog = new AggregateCatalog();
var binCatalog = new DirectoryCatalog(HttpRuntime.BinDirectory, "Module*.dll");
var pluginsCatalot = new DirectoryCatalog(Path.Combine(HttpRuntime.BinDirectory, "Plugins"), "Module*.dll");
catalog.Catalogs.Add(binCatalog);
catalog.Catalogs.Add(pluginsCatalot);
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
AppDomain.CurrentDomain.AppendPrivatePath(Path.Combine(HttpRuntime.BinDirectory, "Plugins"));

CustomViewEngine е създаден и регистриран и се използва за намиране на изгледи в сглобяване на модул:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());

фабрика за контролери за зареждане на контролери от контейнер:

ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(_container));

и също персонализиран доставчик на виртуален път за получаване на сглобки от контейнер:

HostingEnvironment.RegisterVirtualPathProvider(new ModuleVirtualPathProvider());

Добре, така че цялата инфраструктура за работа с модулни модели, контролери и изгледи е готова. Сега всичко работи... с изключение на едно нещо - строго въведени изгледи.

За да илюстрираме проблема по-подробно, нека подготвим сцената:

  • Моделът UserDTO се намира в Module1.Data.dll
  • ShowUserController.cs се намира в Module1.Web.dll/Controllers/
  • Index.cshtml се намира в Module1.Web.dll/Views/ShowUser (с деклариран @model Module1.Data.UserDto)

Сега правим следното:

  1. Стартирайте приложението и отидете на HOST/ShowUser/Index (методът на действие Index се изпълнява на ShowUserController и изгледът Index.cshtml се извлича)
  2. След като изгледът Index.cshtml бъде извлечен - започва компилация (от RazorBuildProvider)
  3. Възникват изключения: „не може да намери тип данни в пространството от имена Module1“, с други думи UserDTO не може да бъде намерен по време на динамичното изграждане на изгледа

Така че изглежда, че компилаторът/конструкторът не е прегледал папката bin/Plugins за Module1.Data.dll, защото когато копирах този файл в папката bin - той беше формулиран добре.

Въпрос/проблем: защо builder не погледна в папката bin/Plugins, въпреки че тази директория беше добавена от метода AppDomain.CurrentDomain.AppendPrivatePath? Как да добавя частни пътеки за конструктор на асемблиране веднъж, така че папката с добавки да бъде взета под внимание??

Успях да свърша някаква работа, като създадох CustomRazorBuildProvider, който заменя стандартния:

public class CustomRazorBuildProvider : RazorBuildProvider
{
  public override void GenerateCode(System.Web.Compilation.AssemblyBuilder assemblyBuilder)
  {
    Assembly a = Assembly.LoadFrom(Path.Combine(HttpRuntime.BinDirectory, "Plugins", "Module1.Data.dll"));
    assemblyBuilder.AddAssemblyReference(a);      
    base.GenerateCode(assemblyBuilder);
  }
} 

но недостатъкът на това решение е, че всеки път, когато изгледът се компилира, трябва да се добавят препратки към всички асембли в папката Plugins, което може да причини проблеми с производителността по-късно, когато ще се използват много добавки.

Някакви по-добри решения?


person untoldex    schedule 27.11.2011    source източник
comment
Някога решавали ли сте това? В момента се опитвам да разреша същия проблем с моето MVC приложение. Имате ли някакъв работещ източник, който бих могъл да разгледам?   -  person Coppermill    schedule 26.06.2012
comment
Да, разреших го чрез CustomRazorBuildProvider, както е описано по-горе. Въпреки това оттогава нашето приложение навлиза все повече в MVVM подхода, където се използват по-малко изгледи с бръснач и се създават повече изгледи с чист html/javascript.   -  person untoldex    schedule 14.07.2012


Отговори (1)


Ето една мисъл.

Ако следвате модела на изгледа на модела, тогава вместо да изпращате DTO направо към изгледа, използвайте ViewModel, който ще бъде разположен в същата сглобка като изгледа.

Така че вместо:

Моделът UserDTO се намира в Module1.Data.dll ShowUserController.cs се намира в Module1.Web.dll/Controllers/ Index.cshtml се намира в Module1.Web.dll/Views/ShowUser (с деклариран @model Module1.Data.UserDto)

Ти Би имал:

Моделът UserDTO се намира в Module1.Data.dll ShowUserController.cs се намира в Module1.Web.dll/Controllers/ UserVM, намиращ се в Module1.Web.dll/ViewModels Index.cshtml се намира в Module1.Web.dll/Views/ShowUser ( с деклариран @model Module1.Web.ViewModels.UserVM)

Накарайте контролера да картографира вашите DTO към ViewModels

Вижте AutoMapper за помощ при картографирането

person SimonGates    schedule 21.11.2012