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

发布于 2024-10-05 11:38:02 字数 4479 浏览 0 评论 0原文

我正在尝试创建一个框架,允许将控制器和视图动态导入到 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"))) 返回 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 加载的类型这一事实有关,但由于我已经从 BuildManager 引用 MEF 发现的程序集,所以我没有线索。谁能更深入地了解可能发生的情况?


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

尝试 { var path = String.Format(this.ViewLocationFormats[2], viewName,controllerContext.RouteData.GetRequiredString("controller")); var objFactory = System.Web.Compilation.BuildManager.GetObjectFactory(virtualPath: 路径, throwIfNotFound: true); } 抓住 { 不幸的是

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


更新2

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


更新 3

这是我所看到的: not found



​​ 更新 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]);
        }        
    }
}

I'm attempting to create a framework for allowing controllers and views to be dynamically imported into an MVC application. Here's how it works so far:

  • I'm using .NET 4, ASP.NET MVC 3 RC and the Razor ViewEngine
  • Controllers are exported and imported using MEF per project - I call a set of controllers and views from a given project a "Module"
  • Assemblies discovered using MEF are dynamically referenced by the BuildManager using a pre-application start method and BuildManager.AddReferencedAssembly.
  • Binaries (from exporting project) and Views are copied into the target project's folder structure using a build event
  • Controllers are selected using a custom controller factory which inherits from DefaultControllerFactory and overrides GetControllerType()
  • Views are selected using a custom view engine which inherits from RazorViewEngine and overrides GetView() and GetPartialView() to allow it to look for views in Module-specific view directories

Everything works so far except for views using a strongly typed model. Views that use the dynamic model work fine, but when I specify a model type using @model, I get a YSOD that says "The view 'Index' or its master was not found".

When debugging my ViewEngine implementation, I can see that:
this.VirtualPathProvider.FileExists(String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"))) returns true, while

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

Looking in Reflector, the RazorViewEngine implementation of FileExists() ultimately winds up doing this:

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

However, I can't view BuildManager.GetObjectFactory() from Reflector because it's hidden somehow.

I'm suspecting that it has something to do with the fact that the model type is a type that is loaded from MEF, but since I'm already referencing the assemblies discovered by MEF from BuildManager, I'm out of leads. Can anyone provide a little more insight into what might be going on?


Update:
Turns out I was using an outdated version of Reflector from before .NET 4. I can see GetObjectFactory() now, but I can't really seem to find anything helpful. I've tried adding this into my FindView() overload:

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
{
}

Unfortunately, objFactory ends up null, and no exception gets thrown. All the bits that deal with compilation errors are part of private methods or types so I can't debug any of that, but it even seems like they'd end up throwing an exception, which doesn't seem to be happening. Looks like I'm at a dead end again. Help!


Update 2

I've discovered that at the point where FindView() is being called, if I call AppDomain.CurrentDomain.GetAssemblies(), the assembly that the model type is in is included. However, I cannot load the type using Type.GetType().


Update 3

Here's what I'm seeing:
not found



Update 4

Here's the ViewEngine implementation:

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]);
        }        
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夜清冷一曲。 2024-10-12 11:38:02

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

我同意 Matthew 的评估,即要使其正常工作,您的 DLL 必须位于 /bin 中,否则它将永远无法满足隐式类型要求。

Based on Update 2, I'm guessing what you've got is an explicitly loaded copy of your assembly (that is, it was loaded through some other method than Load, like LoadFrom). Explicitly loaded assemblies are set off aside into a special place, because they are not allowed to satisfy implicit type requirements. The rules for Fusion (the assembly loader) can be pretty arcane and hard to understand.

I agree with Matthew's assessment that, to get this to work, your DLL is going to have to be in /bin or else it will never be able to satisfy the implicit type requirement.

计㈡愣 2024-10-12 11:38:02

导入的库不在 /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;
}

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

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

我相信这同样适用于 MVC3。

更新:如果我错了,请纠正我,但我不相信 ViewEngines 会在每个请求中实例化一次,而是创建一个向 MVC 注册的实例。因此,只有一个 IModule 实例与您的 ViewEngine 一起使用,因此如果路径与第一个 IModule.Name 不匹配,就不会找到它?这有道理吗?

The imported libaries aren't in the /bin directory so aren't probed when trying to resolve references. I discovered a work around which I published in my MVC + MEF article (Part 2). Essentially you need to add your directories where your extensions sit to the probing path of the AppDomain.

Essentially where I am building my container:

/// <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;
}

I register all the directories of catalogs in the current domain:

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

I believe the same should work for MVC3.

UPDATE: Correct me if I am wrong, but I don't believe that ViewEngines are instantiated once per request, you create a single instance that you register with MVC. Because of this only one IModule instance is ever used with your ViewEngine, so if a path doesn't match that first IModule.Name it won't be found? Does that make sense?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文