MEF и MVC 3 – как динамически загружать встроенные представления из контейнера mef?

Я создаю приложение MVC 3, в котором используется MEF. Основная идея состоит в том, чтобы иметь подключаемый механизм, в котором модели, контроллеры и представления загружаются динамически во время выполнения из контейнера mef.

Каждый плагин/модуль состоит из двух сборок:

  • Module1.Data.dll (содержит определения моделей)
  • Module1.Web.dll (содержит контроллеры и представления)

и помещаются в каталог плагинов внутри корзины веб-приложения:

  • Веб-приложение/Бин/Плагины/Module1.Data.dll
  • WebApp/Bin/Плагины/Module1.Web.dll
  • Веб-приложение/Бин/Плагины/Module2.Data.dll
  • WebApp/Bin/Плагины/Module2.Web.dll
  • Веб-приложение/Бин/Плагины/ModuleCore.Data.dll
  • WebApp/Бин/Плагины/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 - он был написан нормально.

Вопрос/проблема: почему билдер не смотрел в папку 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);
  }
} 

но недостатком этого решения является то, что каждый раз, когда представление компилируется, необходимо добавлять ссылки на все сборки в папке плагинов, что может вызвать проблемы с производительностью позже, когда будет использоваться много плагинов.

Любые более приятные решения?


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 прямо в представление используйте модель представления, которая будет расположена в той же сборке, что и представление.

Итак, вместо:

Модель 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