2009-09-26 12 views
35

Configuración:Genérico hereda ViewPage <> y la nueva propiedad

  • CustomViewEngine
  • CustomController Base
  • CustomViewPage Base (en esta base, se añade una nueva propiedad "MyCustomProperty")

Problema:

Cuando una vista está fuertemente tipada, como: <@ Page Inherits="CustomViewPage<MyCustomObject" MyCustomProperty="Hello">, obtengo un compi ler afirmando "Analizador" error que MyCustomProperty no es una propiedad pública de System.Web.Mvc.ViewPage

he hecho numerosos ensayos y errores (ver más abajo) para ver cuál es la causa de este error y han llegado a las siguientes conclusiones:

  • El error solo ocurre cuando declaro "MyCustomProperty" o cualquier otra propiedad en la directiva @Page de la vista.
  • El error siempre mostrará "System.Web.Mvc.ViewPage" en lugar de la clase inherits = ".." declarada.
+0

Lo siento, no puedo ayudar más y me encantaría saber por qué sucede esto. Consulte la sección "Cómo los controles de entrada obtienen sus valores" de http://stackoverflow.com/questions/1434734/asp-net-mvc-dropdownlist-selected-value-problem. Esto podría ayudar, quizás no, pero parece que MVC busca valores en un orden que no siempre es obvio. –

+0

No puedo ver cómo puedo aplicar el enlace a esta situación. Probé todos los tipos de declaraciones de clases diferentes con tipos genéricos y diferentes patrones de herencia, nada = ( – Omar

+0

Encontré esto en Google: http://forums.asp.net/p/1432045/3408610.aspx#3408610 (Último mensaje en página) Pero, ¿por qué solo funciona si ese usuario usa "especificación de tipo de formato .NET sin formato" – Omar

Respuesta

56

Actualización: Parece que Technitium encontrado otra manera de hacer esto que se ve mucho más fácil, al menos en las versiones más nuevas de ASP.NET MVC. (Copiado su comentario a continuación)

No estoy seguro de si esto es nuevo en ASP.NET MVC 3, pero cuando cambié el atributo Inherits de hacer referencia a la genérica en sintaxis de C# a CLR sintaxis, la estándar ViewPageParserFilter genéricos analizados correctamente - no CustomViewTypeParserFilter requerido. Utilizando ejemplos de Justin, esto significa que el intercambio

<%@ Page Language="C#" MyNewProperty="From @Page directive!" 
    Inherits="JG.ParserFilter.CustomViewPage<MvcApplication1.Models.FooModel> 

a

<%@ Page Language="C#" MyNewProperty="From @Page directive!"` 
    Inherits="JG.ParserFilter.CustomViewPage`1[MvcApplication1.Models.FooModel]> 

Respuesta original a continuación:

OK, lo resolvió este. Fue un ejercicio fascinante, y la solución no es trivial, pero no es demasiado difícil una vez que lo haces funcionar la primera vez.

Aquí está el problema subyacente: el analizador de páginas ASP.NET no admite genéricos como tipo de página.

La manera en que ASP.NET MVC solucionó esto fue engañando al analizador de páginas subyacente y haciéndole creer que la página no es genérica. Hicieron esto construyendo un PageParserFilter personalizado y un FileLevelPageControlBuilder personalizado. El filtro de analizador busca un tipo genérico y, si encuentra uno, lo intercambia por el tipo de página de visualización no genérico para que el analizador de ASP.NET no se ahogue. Luego, mucho más tarde en el ciclo de vida de compilación de página, su clase de generador de página personalizada intercambia el tipo genérico de nuevo.

Esto funciona porque el tipo de página de visualización genérico deriva de la página de visualización no genérica y todas las propiedades interesantes que se establecen en una directiva @Page existe en la clase base (no genérica).Entonces, lo que realmente está sucediendo cuando las propiedades se establecen en la directiva @Page es que esos nombres de propiedad se validan con la clase base no genérica de la página de visualización.

De todos modos, esto funciona muy bien en la mayoría de los casos, pero no en el suyo porque codifican ViewPage como el tipo base no genérico en su implementación de filtro de página y no proporcionan una manera fácil de cambiarlo. Esta es la razón por la que siguió viendo ViewPage en su mensaje de error, ya que el error ocurre en el medio cuando ASP.NET intercambia en el marcador de posición de ViewPage y cuando intercambia la página de visualización genérica antes de la compilación.

La solución es crear su propia versión de los siguientes: filtro analizador

  1. página - esto es casi una copia exacta de ViewTypeParserFilter.cs en la fuente de MVC, con la única diferencia de que se refiere a sus tipos personalizados de vista y página en lugar del constructor de página MVC
  2. - esto es idéntico a ViewPageControlBuilder.cs en la fuente MVC, pero pone la clase en su propio espacio de nombres en lugar de los suyos.
  3. Derive su clase de página de vista personalizada directamente desde System.Web.Mvc.ViewPage (la versión no genérica). Pegue cualquier propiedad personalizada en esta nueva clase no genérica.
  4. deriva una clase genérica del # 3, copiando el código de la implementación de ViewPage de la fuente ASP.NET MVC.
  5. repita los números 2, 3 y 4 para los controles del usuario (@Control) si también necesita propiedades personalizadas en las directivas de control del usuario.

Luego debe cambiar el archivo web.config en su directorio de vistas (no en la aplicación principal web.config) para usar estos tipos nuevos en lugar de los predeterminados de MVC.

He incluido algunos ejemplos de código que ilustran cómo funciona esto. Muchas gracias al artículo de Phil Haack por ayudarme a entender esto, aunque también tuve que hurgar mucho en el código fuente de MVC y ASP.NET para entenderlo realmente.

En primer lugar, voy a empezar con los cambios necesarios en su web.config web.config:

<pages 
    validateRequest="false" 
    pageParserFilterType="JG.ParserFilter.CustomViewTypeParserFilter" 
    pageBaseType="JG.ParserFilter.CustomViewPage" 
    userControlBaseType="JG.ParserFilter.CustomViewUserControl"> 

Ahora, aquí está el filtro de páginas analizador (# 1 arriba):

namespace JG.ParserFilter { 
    using System; 
    using System.Collections; 
    using System.Web.UI; 
    using System.Web.Mvc; 

    internal class CustomViewTypeParserFilter : PageParserFilter 
    { 

     private string _viewBaseType; 
     private DirectiveType _directiveType = DirectiveType.Unknown; 
     private bool _viewTypeControlAdded; 

     public override void PreprocessDirective(string directiveName, IDictionary attributes) { 
      base.PreprocessDirective(directiveName, attributes); 

      string defaultBaseType = null; 

      // If we recognize the directive, keep track of what it was. If we don't recognize 
      // the directive then just stop. 
      switch (directiveName) { 
       case "page": 
        _directiveType = DirectiveType.Page; 
        defaultBaseType = typeof(JG.ParserFilter.CustomViewPage).FullName; // JG: inject custom types here 
        break; 
       case "control": 
        _directiveType = DirectiveType.UserControl; 
        defaultBaseType = typeof(JG.ParserFilter.CustomViewUserControl).FullName; // JG: inject custom types here 
        break; 
       case "master": 
        _directiveType = DirectiveType.Master; 
        defaultBaseType = typeof(System.Web.Mvc.ViewMasterPage).FullName; 
        break; 
      } 

      if (_directiveType == DirectiveType.Unknown) { 
       // If we're processing an unknown directive (e.g. a register directive), stop processing 
       return; 
      } 


      // Look for an inherit attribute 
      string inherits = (string)attributes["inherits"]; 
      if (!String.IsNullOrEmpty(inherits)) { 
       // If it doesn't look like a generic type, don't do anything special, 
       // and let the parser do its normal processing 
       if (IsGenericTypeString(inherits)) { 
        // Remove the inherits attribute so the parser doesn't blow up 
        attributes["inherits"] = defaultBaseType; 

        // Remember the full type string so we can later give it to the ControlBuilder 
        _viewBaseType = inherits; 
       } 
      } 
     } 

     private static bool IsGenericTypeString(string typeName) { 
      // Detect C# and VB generic syntax 
      // REVIEW: what about other languages? 
      return typeName.IndexOfAny(new char[] { '<', '(' }) >= 0; 
     } 

     public override void ParseComplete(ControlBuilder rootBuilder) { 
      base.ParseComplete(rootBuilder); 

      // If it's our page ControlBuilder, give it the base type string 
      CustomViewPageControlBuilder pageBuilder = rootBuilder as JG.ParserFilter.CustomViewPageControlBuilder; // JG: inject custom types here 
      if (pageBuilder != null) { 
       pageBuilder.PageBaseType = _viewBaseType; 
      } 
      CustomViewUserControlControlBuilder userControlBuilder = rootBuilder as JG.ParserFilter.CustomViewUserControlControlBuilder; // JG: inject custom types here 
      if (userControlBuilder != null) { 
       userControlBuilder.UserControlBaseType = _viewBaseType; 
      } 
     } 

     public override bool ProcessCodeConstruct(CodeConstructType codeType, string code) { 
      if (codeType == CodeConstructType.ExpressionSnippet && 
       !_viewTypeControlAdded && 
       _viewBaseType != null && 
       _directiveType == DirectiveType.Master) { 

       // If we're dealing with a master page that needs to have its base type set, do it here. 
       // It's done by adding the ViewType control, which has a builder that sets the base type. 

       // The code currently assumes that the file in question contains a code snippet, since 
       // that's the item we key off of in order to know when to add the ViewType control. 

       Hashtable attribs = new Hashtable(); 
       attribs["typename"] = _viewBaseType; 
       AddControl(typeof(System.Web.Mvc.ViewType), attribs); 
       _viewTypeControlAdded = true; 
      } 

      return base.ProcessCodeConstruct(codeType, code); 
     } 

     // Everything else in this class is unrelated to our 'inherits' handling. 
     // Since PageParserFilter blocks everything by default, we need to unblock it 

     public override bool AllowCode { 
      get { 
       return true; 
      } 
     } 

     public override bool AllowBaseType(Type baseType) { 
      return true; 
     } 

     public override bool AllowControl(Type controlType, ControlBuilder builder) { 
      return true; 
     } 

     public override bool AllowVirtualReference(string referenceVirtualPath, VirtualReferenceType referenceType) { 
      return true; 
     } 

     public override bool AllowServerSideInclude(string includeVirtualPath) { 
      return true; 
     } 

     public override int NumberOfControlsAllowed { 
      get { 
       return -1; 
      } 
     } 

     public override int NumberOfDirectDependenciesAllowed { 
      get { 
       return -1; 
      } 
     } 

     public override int TotalNumberOfDependenciesAllowed { 
      get { 
       return -1; 
      } 
     } 

     private enum DirectiveType { 
      Unknown, 
      Page, 
      UserControl, 
      Master, 
     } 
    } 
} 

Esta es la clase de página constructor (# 2 arriba):

namespace JG.ParserFilter { 
    using System.CodeDom; 
    using System.Web.UI; 

    internal sealed class CustomViewPageControlBuilder : FileLevelPageControlBuilder { 
     public string PageBaseType { 
      get; 
      set; 
     } 

     public override void ProcessGeneratedCode(
      CodeCompileUnit codeCompileUnit, 
      CodeTypeDeclaration baseType, 
      CodeTypeDeclaration derivedType, 
      CodeMemberMethod buildMethod, 
      CodeMemberMethod dataBindingMethod) { 

      // If we find got a base class string, use it 
      if (PageBaseType != null) { 
       derivedType.BaseTypes[0] = new CodeTypeReference(PageBaseType); 
      } 
     } 
    } 
} 

Y aquí es las clases vista personalizada de página: la base no genérico (# 3 abov e) y la clase derivada genérica (# 4 arriba):

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.Web.UI; 
using System.Diagnostics.CodeAnalysis; 
using System.Web.Mvc; 

namespace JG.ParserFilter 
{ 
    [FileLevelControlBuilder(typeof(JG.ParserFilter.CustomViewPageControlBuilder))] 
    public class CustomViewPage : System.Web.Mvc.ViewPage //, IAttributeAccessor 
    { 
     public string MyNewProperty { get; set; } 
    } 

    [FileLevelControlBuilder(typeof(JG.ParserFilter.CustomViewPageControlBuilder))] 
    public class CustomViewPage<TModel> : CustomViewPage 
     where TModel : class 
    { 
     // code copied from source of ViewPage<T> 

     private ViewDataDictionary<TModel> _viewData; 

     public new AjaxHelper<TModel> Ajax 
     { 
      get; 
      set; 
     } 

     public new HtmlHelper<TModel> Html 
     { 
      get; 
      set; 
     } 

     public new TModel Model 
     { 
      get 
      { 
       return ViewData.Model; 
      } 
     } 

     [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] 
     public new ViewDataDictionary<TModel> ViewData 
     { 
      get 
      { 
       if (_viewData == null) 
       { 
        SetViewData(new ViewDataDictionary<TModel>()); 
       } 
       return _viewData; 
      } 
      set 
      { 
       SetViewData(value); 
      } 
     } 

     public override void InitHelpers() 
     { 
      base.InitHelpers(); 

      Ajax = new AjaxHelper<TModel>(ViewContext, this); 
      Html = new HtmlHelper<TModel>(ViewContext, this); 
     } 

     protected override void SetViewData(ViewDataDictionary viewData) 
     { 
      _viewData = new ViewDataDictionary<TModel>(viewData); 

      base.SetViewData(_viewData); 
     } 

    } 
} 

Y aquí están las clases correspondientes a los controles de usuario (# 5 anterior):

namespace JG.ParserFilter 
{ 
    using System.Diagnostics.CodeAnalysis; 
    using System.Web.Mvc; 
    using System.Web.UI; 

    [FileLevelControlBuilder(typeof(JG.ParserFilter.CustomViewUserControlControlBuilder))] 
    public class CustomViewUserControl : System.Web.Mvc.ViewUserControl 
    { 
     public string MyNewProperty { get; set; } 
    } 

    public class CustomViewUserControl<TModel> : CustomViewUserControl where TModel : class 
    { 
     private AjaxHelper<TModel> _ajaxHelper; 
     private HtmlHelper<TModel> _htmlHelper; 
     private ViewDataDictionary<TModel> _viewData; 

     public new AjaxHelper<TModel> Ajax { 
      get { 
       if (_ajaxHelper == null) { 
        _ajaxHelper = new AjaxHelper<TModel>(ViewContext, this); 
       } 
       return _ajaxHelper; 
      } 
     } 

     public new HtmlHelper<TModel> Html { 
      get { 
       if (_htmlHelper == null) { 
        _htmlHelper = new HtmlHelper<TModel>(ViewContext, this); 
       } 
       return _htmlHelper; 
      } 
     } 

     public new TModel Model { 
      get { 
       return ViewData.Model; 
      }    
     } 

     [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] 
     public new ViewDataDictionary<TModel> ViewData { 
      get { 
       EnsureViewData(); 
       return _viewData; 
      } 
      set { 
       SetViewData(value); 
      } 
     } 

     protected override void SetViewData(ViewDataDictionary viewData) { 
      _viewData = new ViewDataDictionary<TModel>(viewData); 

      base.SetViewData(_viewData); 
     } 
    } 
} 

namespace JG.ParserFilter { 
    using System.CodeDom; 
    using System.Web.UI; 

    internal sealed class CustomViewUserControlControlBuilder : FileLevelUserControlBuilder { 
     internal string UserControlBaseType { 
      get; 
      set; 
     } 

     public override void ProcessGeneratedCode(
      CodeCompileUnit codeCompileUnit, 
      CodeTypeDeclaration baseType, 
      CodeTypeDeclaration derivedType, 
      CodeMemberMethod buildMethod, 
      CodeMemberMethod dataBindingMethod) { 

      // If we find got a base class string, use it 
      if (UserControlBaseType != null) { 
       derivedType.BaseTypes[0] = new CodeTypeReference(UserControlBaseType); 
      } 
     } 
    } 
} 

Por último, he aquí una vista de muestra que espectáculos esto en acción:

<%@ Page Language="C#" MyNewProperty="From @Page directive!" Inherits="JG.ParserFilter.CustomViewPage<MvcApplication1.Models.FooModel>" %> 
    <%=Model.SomeString %> 
    <br /><br />this.MyNewPrroperty = <%=MyNewProperty%> 
</asp:Content> 
+6

Eres mi amigo, eres mi héroe. Si pudiera alcanzar la pantalla y abrazarte, lo haría. Nunca hubiera resuelto esto con mi experiencia ... ¡nunca! Dejé de codificar durante una semana debido a este problema y finalmente obtuve una respuesta. Si pudiera, te daría el resto de mi 226 reputación, pero no puedo editar las recompensas. Gracias de nuevo, respuesta muy completa, con una explicación perfecta. Esperemos que esta respuesta ayude a otros que intentan hacer que esto funcione. – Omar

+0

¡Gracias por esto! El código MS es tan bello que se mantiene incluso por su propia cuenta :( – Sly

+1

No estoy seguro de si esto es nuevo en ASP.NET MVC 3, pero cuando cambié el atributo 'Inherits' de hacer referencia al genérico en sintaxis C# para Sintaxis de CLR, el estándar ViewPageParserFilter analizó genéricos correctamente - no se requiere 'CustomViewTypeParserFilter'. Usando los ejemplos de Justin, esto significa el intercambio de' <% @ Page Language = "C#" MyNewProperty = "¡Desde la directiva @Page!" Inherits = "JG.ParserFilter. CustomViewPage 'to' <% @ Page Language = "C#" MyNewProperty = "¡Desde la directiva @Page!" Inherits = "JG.ParserFilter.CustomViewPage \' 1 [MvcApplication1.Models.FooModel]> '. – Technetium

Cuestiones relacionadas