Създавам 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)
Сега правим следното:
- Стартирайте приложението и отидете на HOST/ShowUser/Index (методът на действие Index се изпълнява на ShowUserController и изгледът Index.cshtml се извлича)
- След като изгледът Index.cshtml бъде извлечен - започва компилация (от RazorBuildProvider)
- Възникват изключения: „не може да намери тип данни в пространството от имена 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, което може да причини проблеми с производителността по-късно, когато ще се използват много добавки.
Някакви по-добри решения?