我正在构建一个使用 MEF 的 MVC 3 应用程序。主要思想是拥有插件机制,在运行时从 mef 容器动态加载模型、 Controller 和 View 。
每个插件/模块由两个程序集组成:
- Module1.Data.dll(包含模型定义)
- Module1.Web.dll(包含 Controller 和 View )
并放置在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,并用于在模块组装中查找 View :
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());
用于从容器加载 Controller 的 Controller 工厂:
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(_container));
还有用于从容器获取程序集的自定义虚拟路径提供程序:
HostingEnvironment.RegisterVirtualPathProvider(new ModuleVirtualPathProvider());
好的,用于处理可插入模型、 Controller 和 View 的整个基础设施已经准备就绪。现在一切正常......除了一件事 - 强类型 View 。
为了更详细地说明问题,让我们准备场景:
- UserDTO 模型位于 Module1.Data.dll
- ShowUserController.cs 位于 Module1.Web.dll/Controllers/
- Index.cshtml 位于 Module1.Web.dll/Views/ShowUser(带有声明的 @model Module1.Data.UserDto)
现在我们执行以下操作:
- 运行应用程序并转到 HOST/ShowUser/Index(在 ShowUserController 上执行操作方法 Index 并获取 View Index.cshtml)
- 获取 View Index.cshtml 后 - 编译开始(由 RazorBuildProvider)
- 抛出异常:“无法在命名空间 Module1 中找到数据类型”,换句话说,在动态构建 View 期间找不到 UserDTO
所以编译器/构建器似乎没有通过 bin/Plugins 文件夹查找 Module1.Data.dll,因为当我将此文件复制到 bin 文件夹中时 - 它的措辞很好。
问题:为什么构建器没有查看 bin/Plugins 文件夹,即使该目录是通过 AppDomain.CurrentDomain.AppendPrivatePath 方法添加的? 如何为程序集生成器添加一次私有(private)路径以便考虑插件文件夹?
我设法通过创建覆盖标准的 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);
}
}
但此解决方案的缺点是,每次编译 View 时,都需要添加对 Plugins 文件夹中所有程序集的引用,这可能会在以后使用大量插件时导致性能问题。
还有更好的解决方案吗?
最佳答案
这是一个想法。
如果您遵循 View 模型模式,则不要将 DTO 直接发送到 View ,而是使用与 View 位于同一程序集中的 ViewModel。
所以代替:
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)
让 Controller 将 DTO 映射到 ViewModel
参见AutoMapper帮助进行映射
关于asp.net-mvc-3 - MEF 和 MVC 3 - 如何从 mef 容器动态加载嵌入 View ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8285474/