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

Dan*_*fer 12 c# asp.net-mvc mef razor

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

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

到目前为止,除了使用强类型模型的视图,一切都有效.使用动态模型的视图工作正常,但是当我使用指定模型类型时@model,我得到一个YSOD,其中显示"未找到视图'索引'或其主要".

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

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

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

return (BuildManager.GetObjectFactory(virtualPath, false) != null);
Run Code Online (Sandbox Code Playgroud)

但是,我无法BuildManager.GetObjectFactory()从Reflector 查看,因为它隐藏在某种程度上.

我怀疑它与模型类型是从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,并且不会抛出任何异常.处理编译错误的所有位都是私有方法或类型的一部分,因此我无法调试其中任何一个,但它甚至看起来他们最终会抛出异常,这似乎并没有发生.看起来我又陷入了死胡同.救命!


更新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]);
        }        
    }
}
Run Code Online (Sandbox Code Playgroud)

Bra*_*son 10

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

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


Mat*_*ott 5

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

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

/// <summary>
/// Creates the composition container.
/// </summary>
/// <returns></returns>
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<CatalogConfigurationElement>()
            .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;
}
Run Code Online (Sandbox Code Playgroud)

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

/// <summary>
/// Registers the specified path for probing.
/// </summary>
/// <param name="path">The probable path.</param>
private void RegisterPath(string path)
{
    AppDomain.CurrentDomain.AppendPrivatePath(path);
}
Run Code Online (Sandbox Code Playgroud)

我相信同样适用于MVC3.

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