MEF和MVC 3 - 如何从MEF容器动态加载嵌入式视图?

11
我正在构建一个使用MEF的MVC 3应用程序。主要思想是采用插件机制,其中模型、控制器和视图在运行时从mef容器中动态加载。

每个插件/模块由两个程序集组成:

  • Module1.Data.dll(包含模型定义)
  • Module1.Web.dll(包含控制器和视图)

并且它们放置在Web应用程序bin目录下的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(在ShowUserController上执行动作方法Index并获取视图Index.cshtml)
  2. 获取视图Index.cshtml后-编译开始(由RazorBuildProvider执行)
  3. 抛出异常:“在名称空间Module1中找不到Data类型”,换句话说,在动态构建视图期间无法找到UserDTO。

因此,似乎编译器/生成器没有查看bin/Plugins文件夹以找到Module1.Data.dll,因为当我将此文件复制到bin文件夹中时-它可以正常工作。

问题/困难:为什么生成器没有查看bin/Plugins文件夹,即使使用了AppDomain.CurrentDomain.AppendPrivatePath方法添加了该目录?如何一次添加程序集构建器的私有路径,以便考虑插件文件夹?

我通过创建CustomRazorBuildProvider来进行了一些解决方法,它覆盖了标准的RazorBuildProvider:

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文件夹中所有程序集的引用,当使用大量插件时,这可能会导致性能问题。

有更好的解决方案吗?


2
你最终解决了这个问题吗?我现在也在尝试解决我的MVC应用程序的同样问题。你有没有能够查看的正在运行的源代码呢? - Coppermill
1
是的,我按照上面描述的CustomRazorBuildProvider解决了它。然而自那时以来,我们的应用程序更多地采用MVVM方法,使用较少的razor视图,而是构建更多的纯html/javascript视图。 - untoldex
1个回答

1

这里有一个想法。

如果您遵循View Model模式,那么不要直接将DTO发送到视图,而是使用ViewModel,该ViewModel位于与View相同的程序集中。

所以,不是:

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以帮助进行映射


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接