Подтвердить что ты не робот

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

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

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

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

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

  • WebApp/Bin/Плагины/Module1.Data.dll
  • WebApp/Bin/Плагины/Module1.Web.dll
  • WebApp/Bin/Плагины/Module2.Data.dll
  • WebApp/Bin/Плагины/Module2.Web.dll
  • WebApp/Bin/Плагины/ModuleCore.Data.dll
  • WebApp/Bin/Плагины/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());

контроллер factory для загрузки контроллеров из контейнера:

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 (с объявленным модулем Module1.Data.UserDto).

Теперь мы делаем следующее:

  • Запустите приложение и перейдите к HOST/ShowUser/Index (метод действия Индекс выполняется на ShowUserController и вызывается просмотр Index.cshtml)
  • После просмотра объекта Index.cshtml - начинается сбор (с помощью RazorBuildProvider)
  • Исключения выбрасываются: "не удается найти тип данных в пространстве имен 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);
  }
} 

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

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

4b9b3361

Ответ 1

Вот мысль.

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

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

Модель UserDTO находится в Module1.Data.dll ShowUserController.cs находится в Module1.Web.dll/Контроллеры/ Index.cshtml находится в Module1.Web.dll/Views/ShowUser (с объявленным модулем 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, чтобы помочь с отображением