2010-12-01 13 views
12

no puede ver las vistas usando un tipo de modelo cargado por MEF. Estoy intentando crear un marco para permitir que los controladores y las vistas se importen dinámicamente en una aplicación MVC. Así es como funciona hasta el momento:ASP.NET MVC: el motor de vista

  • estoy usando .NET 4, ASP.NET MVC 3 RC y la Navaja ViewEngine
  • controladores se exportan e importan usando MEF por proyecto - que llamo un conjunto de controladores y las vistas de un proyecto determinado un "Módulo"
  • Los ensamblajes descubiertos mediante MEF son referenciados dinámicamente por BuildManager usando un método de inicio de pre-aplicación y BuildManager.AddReferencedAssembly.
  • binarios (de proyecto de exportación) y las vistas son copiados en la estructura de carpetas del proyecto de destino utilizando un evento de acumulación
  • controladores se seleccionan utilizando una fábrica de controlador personalizado que hereda de DefaultControllerFactory y anula GetControllerType()
  • Vistas se seleccionan utilizando un motor de vista personalizada que hereda de RazorViewEngine y anula GetView() y GetPartialView() para permitir que se busque vistas en específicos del módulo vista directorios

todo funciona hasta ahora excepto de vistas utilizando un mo fuertemente tipado del. Las vistas que usan el modelo dinámico funcionan bien, pero cuando especifico un tipo de modelo usando @model, obtengo un YSOD que dice "La vista 'Índice' o su maestro no se encontró".

Al depurar mi aplicación ViewEngine, puedo ver que: this.VirtualPathProvider.FileExists(String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"))) vuelve verdadera, mientras que

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

Buscando en el reflector, la puesta en práctica de RazorViewEngine FileExists() en última instancia termina haciendo esto:

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

Sin embargo, no puedo ver BuildManager.GetObjectFactory() del reflector porque está escondida alguna manera.

Sospecho que tiene algo que ver con el hecho de que el tipo de modelo es un tipo que se carga desde MEF, pero como ya estoy haciendo referencia a los ensamblados descubiertos por MEF de BuildManager, me he quedado sin conduce. ¿Alguien puede darnos un poco más de información sobre lo que podría estar pasando?


Actualización: Resulta que yo estaba usando una versión obsoleta del reflector de delante de .NET 4. Puedo ver GetObjectFactory() ahora, pero me parece que no puede realmente encontrar algo útil. He intentado añadir esto en mi FindView() Sobrecarga:

tratar {var path = String.Format (this.ViewLocationFormats [2], viewName, controllerContext.RouteData.GetRequiredString ("controlador")); var objFactory = System.Web.Compilation.BuildManager.GetObjectFactory (virtualPath: ruta, throwIfNotFound: true); } captura {}

Desafortunadamente, objFactory termina nula, y no es una excepción es lanzada.Todos los bits que se ocupan de los errores de compilación son parte de métodos o tipos privados, por lo que no puedo depurar nada de eso, pero incluso parece que terminan arrojando una excepción, lo que no parece estar sucediendo. Parece que estoy en un callejón sin salida otra vez. ¡Ayuda!


Actualización 2

he descubierto que en el punto donde se está llamando FindView(), si llamo AppDomain.CurrentDomain.GetAssemblies(), la asamblea que el tipo de modelo se encuentra en es incluido. Sin embargo, no puedo cargar el tipo usando Type.GetType().


Actualización 3

Aquí es lo que estoy viendo: not found



Update 4

Aquí la implementación 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]); 
     }   
    } 
} 
+0

BuildManager.GetObjectFactory no está oculto (es público, en realidad), pero es nuevo para .NET 4. Asegúrese de haber abierto las bibliotecas .NET correctas en Reflector. –

+0

Oh, lo tengo. Actualizado. –

+0

GetObjectFactory no lanza normalmente; es por eso que lo usamosEl hecho de que devuelva nulo generalmente significa que no se encontró el archivo. –

Respuesta

10

Basado en la Actualización 2, supongo que lo que tienes es una copia del ensamblaje cargada explícitamente (es decir, que se cargó mediante algún otro método que no sea Load, como LoadFrom). Los ensamblajes cargados explícitamente se colocan aparte en un lugar especial, porque no se les permite satisfacer los requisitos de tipo implícitos. Las reglas para Fusion (el cargador de ensamblaje) pueden ser bastante arcanas y difíciles de entender.

Estoy de acuerdo con la evaluación de Matthew de que, para que esto funcione, su DLL tendrá que estar en/bin o de lo contrario nunca podrá satisfacer el requisito de tipo implícito.

+0

Tiene razón, estoy usando LoadFile para obtener una instancia del ensamblado. Veré si puedo encontrar una forma diferente de hacerlo desde el MEF. –

+0

BINGO. Tuve que hacer algunas feos consultas de LINQ con mis catálogos de MEF, pero pude obtener instancias de las asambleas, ahora todo está funcionando. ¡¡Gracias!! –

5

Las librerias importadas no están en el directorio /bin por lo que no se sondeó al intentar resolver referencias. Descubrí un trabajo alrededor del cual publiqué en mi MVC + MEF article (Part 2). Esencialmente, necesita agregar sus directorios donde sus extensiones se sientan a la ruta de prueba del AppDomain.

Esencialmente donde estoy construyendo mi contenedor:

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

me registro de todos los directorios de catálogos en el dominio actual:

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

Creo que lo mismo debe trabajar para MVC3.

ACTUALIZACIÓN: Corrígeme si me equivoco, pero no creo que ViewEngines se instancian una vez por solicitud, se crea una única instancia que se registra en MVC. Debido a esto, solo se utiliza una instancia de IModule con su ViewEngine, por lo que si una ruta no coincide con esa primera IModule.Name, ¿no se encontrará? ¿Tiene sentido?

+0

Interesante ... Desafortunadamente, traté de usar 'AppDomain.AppendPrivatePath()', y no pareció ayudar. Creo que efectivamente podría estar haciendo lo mismo que BuildManager.AddReferencedAssembly(). Además, vale la pena señalar que no veo ninguno de los errores tapados en su artículo. Actualizaré mi pregunta con un límite de pantalla de lo que estoy viendo. –

+0

¿Se puede actualizar su código con el código de su ViewEngine? –

+0

No incrusto mis vistas en los ensamblados. Los archivos cshtml sin procesar se encuentran dentro de la estructura de carpetas del proyecto en ejecución. –

Cuestiones relacionadas