ASP.NET MVC:视图引擎无法找到使用由MEF加载的模型类型的视图

我正在尝试创建一个框架,允许将控制器和视图动态导入MVC应用程序。 到目前为止它是如何工作的:

  • 我正在使用.NET 4,ASP.NET MVC 3 RC和Razor ViewEngine
  • 每个项目使用MEF导出和导入控制器 – 我从一个给定项目调用一组控制器和视图“模块”
  • 使用MEF发现的程序集由BuildManager使用预应用程序启动方法和BuildManager.AddReferencedAssembly动态引用。
  • 使用构建事件将二进制文件(来自导出项目)和视图复制到目标项目的文件夹结构中
  • 使用自定义控制器工厂选择控制器,该工厂inheritance自DefaultControllerFactory并覆盖GetControllerType()
  • 使用自定义视图引擎选择视图,该引擎inheritance自RazorViewEngine并覆盖GetView()和GetPartialView()以允许它在特定于模块的视图目录中查找视图

除了使用强类型模型的视图 ,一切都可以工作。 使用动态模型的视图工作正常,但是当我使用@model指定模型类型时,我得到一个YSOD,其中显示“未找到视图’索引’或其主文件”。

在调试我的ViewEngine实现时,我可以看到: this.VirtualPathProvider.FileExists(String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller")))返回true,而

this.FileExists(controllerContext, String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller")))返回false。

在Reflector中, FileExists()的RazorViewEngine实现最终实现了这样做:

 return (BuildManager.GetObjectFactory(virtualPath, false) != null); 

但是,我无法从Reflector查看BuildManager.GetObjectFactory() ,因为它以某种方式隐藏。

我怀疑它与模型类型是从MEF加载的类型这一事实有关,但由于我已经引用了MEF从BuildManager发现的程序集,因此我没有引导。 任何人都可以更深入地了解可能发生的事情吗?


更新:原来我在.NET 4之前使用的是过时版本的Reflector。我现在可以看到GetObjectFactory(),但我似乎找不到任何有用的东西。 我已经尝试将其添加到我的FindView()重载中:

try {var path = String.Format(this.ViewLocationFormats [2],viewName,controllerContext.RouteData.GetRequiredString(“controller”)); var objFactory = System.Web.Compilation.BuildManager.GetObjectFactory(virtualPath:path,throwIfNotFound:true); } catch {}

不幸的是, objFactory最终为null,并且不会抛出任何exception。 处理编译错误的所有位都是私有方法或类型的一部分,因此我无法调试其中任何一个,但它甚至看起来他们最终会抛出exception,这似乎并没有发生。 看起来我又陷入了死胡同。 救命!


更新2

我发现在调用FindView()时,如果我调用AppDomain.CurrentDomain.GetAssemblies()包含模型类型所在的程序集。 但是,我无法使用Type.GetType()加载类型。


更新3

这就是我所看到的: 未找到



更新4

这是ViewEngine实现:

 using System; using System.Linq; using System.Web.Mvc; using System.Web.Hosting; using System.Web.Compilation; namespace Site.Admin.Portal { public class ModuleViewEngine : RazorViewEngine { private static readonly String[] viewLocationFormats = new String[] { "~/Views/{0}/{{1}}/{{0}}.aspx", "~/Views/{0}/{{1}}/{{0}}.ascx", "~/Views/{0}/{{1}}/{{0}}.cshtml", "~/Views/{0}/Shared/{{0}}.aspx", "~/Views/{0}/Shared/{{0}}.ascx", "~/Views/{0}/Shared/{{0}}.cshtml" }; public ModuleViewEngine(IModule module) { this.Module = module; var formats = viewLocationFormats.Select(f => String.Format(f, module.Name)).ToArray(); this.ViewLocationFormats = formats; this.PartialViewLocationFormats = formats; this.AreaViewLocationFormats = formats; this.AreaPartialViewLocationFormats = formats; this.AreaMasterLocationFormats = formats; } public IModule Module { get; private set; } public override ViewEngineResult FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache) { var moduleName = controllerContext.RouteData.GetRequiredString("module"); if (moduleName.Equals(this.Module.Name, StringComparison.InvariantCultureIgnoreCase)) { return base.FindPartialView(controllerContext, partialViewName, useCache); } else return new ViewEngineResult(new String[0]); } public override ViewEngineResult FindView(ControllerContext controllerContext, String viewName, String masterName, Boolean useCache) { var moduleName = controllerContext.RouteData.GetRequiredString("module"); if (moduleName.Equals(this.Module.Name, StringComparison.InvariantCultureIgnoreCase)) { var baseResult = base.FindView(controllerContext, viewName, masterName, useCache); return baseResult; } else return new ViewEngineResult(new String[0]); } } } 

基于Update 2,我猜你所拥有的是一个显式加载的程序集副本(也就是说,它是通过Load之外的其他方法加载的,比如LoadFrom)。 显式加载的程序集被放在一个特殊的位置,因为它们不允许满足隐式类型要求。 Fusion(assembly装载程序)的规则可能非常晦涩难懂。

我同意Matthew的评估,为了使这个工作,你的DLL将必须在/ bin中,否则它永远不能满足隐式类型要求。

导入的库不在/bin目录中,因此在尝试解析引用时不会进行探测。 我发现了一篇我在MVC + MEF文章(第2部分)中发表的作品。 基本上,您需要将扩展​​所在的目录添加到AppDomain的探测路径中。

基本上我在构建我的容器:

 ///  /// Creates the composition container. ///  ///  protected virtual CompositionContainer CreateCompositionContainer() { var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(MapPath("~/bin"))); var config = CompositionConfigurationSection.GetInstance(); if (config != null && config.Catalogs != null) { config.Catalogs .Cast() .ForEach(c => { if (!string.IsNullOrEmpty(c.Path)) { string path = c.Path; if (path.StartsWith("~")) path = MapPath(path); foreach (var directoryCatalog in GetDirectoryCatalogs(path)) { // Register our path for probing. RegisterPath(directoryCatalog.FullPath); // Add the catalog. catalog.Catalogs.Add(directoryCatalog); } } }); } var provider = new DynamicInstantiationExportProvider(); var container = new CompositionContainer(catalog, provider); provider.SourceProvider = container; return container; } 

我在当前域中注册目录的所有目录:

 ///  /// Registers the specified path for probing. ///  /// The probable path. private void RegisterPath(string path) { AppDomain.CurrentDomain.AppendPrivatePath(path); } 

我相信同样适用于MVC3。

更新 :如果我错了,请纠正我,但我不相信每次请求都会实例化一次ViewEngines,您创建一个向MVC注册的实例。 因为这只有一个IModule实例与ViewEngine一起使用,所以如果路径与第一个IModule.Name不匹配,它将无法找到? 那有意义吗?