MEF и MVC 3 - как динамически загружать встроенные представления из контейнера mef? - PullRequest
11 голосов
/ 27 ноября 2011

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

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

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

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

  • WebApp / Bin / Plugins / Module1.Data.dll
  • WebApp / Bin / Plugins / Module1.Web.dll
  • WebApp / Bin / Плагины / Module2.Data.dll
  • WebApp / Bin / Plugins / Module2.Web.dll
  • WebApp / Bin / Плагины / 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 / Контроллеры /
  • 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);
  }
} 

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

Какие-нибудь более приятные решения?

1 Ответ

1 голос
/ 21 ноября 2012

Вот мысль.

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

Так вместо:

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

Вы бы получили:

Модель UserDTO находится в Module1.Data.dll. ShowUserController.cs находится в Module1.Web.dll / Контроллеры / UserVM находится в Module1.Web.dll / ViewModels Index.cshtml находится в Module1.Web.dll / Views / ShowUser (с объявленным @model Module1.Web.ViewModels.UserVM)

Контроллер сопоставляет ваши DTO с ViewModels

См. AutoMapper , чтобы помочь с отображением

...