2011-11-29 15 views
9

que tienen la siguiente configuración en mi modelo:¿Se puede heredar el modelo cuando se utiliza una vista de tipo fuerte en MVC3?

namespace QuickTest.Models 
{ 
    public class Person 
    { 
     [Required] 
     [Display(Name = "Full name")] 
     public string FullName { get; set; } 

     [Display(Name = "Address Line 1")] 
     public virtual string Address1 { get; set; } 
    } 
    public class Sender : Person 
    { 
     [Required] 
     public override string Address1 { get; set; } 
    } 
    public class Receiver : Person 
    { 
    } 
} 

y en mi opinión: se habilita

@model QuickTest.Models.Person 
@{ 
    ViewBag.Title = "Edit"; 
} 
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> 

@using (Html.BeginForm()) { 
    <fieldset> 
     <legend>Person</legend> 
     <div class="editor-label"> 
      @Html.LabelFor(model => model.FullName) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.FullName) 
      @Html.ValidationMessageFor(model => model.FullName) 
     </div> 
     <div class="editor-label"> 
      @Html.LabelFor(model => model.Address1) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.Address1) 
      @Html.ValidationMessageFor(model => model.Address1) 
     </div> 

     <div class="errors"> 
      @Html.ValidationSummary(true) 
     </div> 
     <p> 
      <input type="submit" value="Save" /> 
     </p> 
    </fieldset> 
} 

validación del lado del cliente. Sin embargo, si envío un objeto de tipo Remitente a la Vista, la validación del lado del cliente no detecta que se requiere el campo Dirección1. ¿Hay alguna manera de hacer que la validación del cliente funcione en este escenario?

PS: descubrí que la validación del cliente funciona si uso el siguiente para mostrar el campo Dirección 1 en la vista:

<div class="editor-field"> 
    @Html.Editor("Address1", Model.Address1) 
    @Html.ValidationMessageFor(model => model.Address1) 
</div> 
+0

El remitente es una persona, pero la persona no es un remitente, su vista está fuertemente tipada a la persona, por lo tanto, nunca va a detectar nada que tenga que ver con el remitente. – Maess

+0

En realidad, si agrega esto a la vista: Model.GetType(). ToString() verá que se muestra lo siguiente: QuickTest.Models.Sender, lo que significa que el tipo se conoce cuando se procesa la vista. – pacu

+0

Sin embargo, EditorFor() va a tratar como el tipo al que lo ha escrito fuerte, que es persona. – Maess

Respuesta

8

Puede personalizar los validadores y los metadatos para que procedan de su clase concreta, pero la solución tiene varias partes móviles, incluidos dos proveedores de metadatos personalizados.

Primero, cree un Attribute personalizado para decorar cada propiedad de la clase base. Esto es necesario como indicador para nuestros proveedores personalizados, para indicar cuándo se necesita un análisis más detallado. Este es el atributo:

[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)] 
public class BaseTypeAttribute : Attribute { } 

A continuación, cree una costumbre ModelMetadataProvider heredando de DataAnnotationsModelMetadataProvider:

public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider 
{ 
    protected override ModelMetadata CreateMetadata(
     IEnumerable<Attribute> attributes, 
     Type containerType, 
     Func<object> modelAccessor, 
     Type modelType, 
     string propertyName) 
    { 
     var attribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) as BaseTypeAttribute; 
     if (attribute != null && modelAccessor != null) 
     { 
      var target = modelAccessor.Target; 
      var containerField = target.GetType().GetField("container"); 
      if (containerField == null) 
      { 
       var vdi = target.GetType().GetField("vdi").GetValue(target) as ViewDataInfo; 
       var concreteType = vdi.Container.GetType(); 
       return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName); 
      } 
      else 
      { 
       var container = containerField.GetValue(target); 
       var concreteType = container.GetType(); 
       var propertyField = target.GetType().GetField("property"); 
       if (propertyField == null) 
       { 
        concreteType = base.GetMetadataForProperties(container, containerType) 
         .FirstOrDefault(p => p.PropertyName == "ConcreteType").Model as System.Type; 
        if (concreteType != null) 
         return base.GetMetadataForProperties(container, concreteType) 
          .FirstOrDefault(pr => pr.PropertyName == propertyName); 
       } 
       return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName); 
      } 
     } 
     return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); 
    } 
} 

A continuación, cree una costumbre ModelValidatorProvider heredando de DataAnnotationsModelValidatorProvider:

public class MyModelMetadataValidatorProvider : DataAnnotationsModelValidatorProvider 
{ 
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) 
    { 
     List<ModelValidator> vals = base.GetValidators(metadata, context, attributes).ToList(); 

     var baseTypeAttribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) 
      as BaseTypeAttribute; 

     if (baseTypeAttribute != null) 
     { 
      // get our parent model 
      var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model, 
       metadata.ContainerType); 

      // get the concrete type 
      var concreteType = parentMetaData.FirstOrDefault(p => p.PropertyName == "ConcreteType").Model; 
      if (concreteType != null) 
      { 
       var concreteMetadata = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model, 
        Type.GetType(concreteType.ToString())); 

       var concretePropertyMetadata = concreteMetadata.FirstOrDefault(p => p.PropertyName == metadata.PropertyName); 

       vals = base.GetValidators(concretePropertyMetadata, context, attributes).ToList(); 
      } 
     } 
     return vals.AsEnumerable(); 
    } 
} 

Después de eso, se registra ambos proveedores personalizados en Application_Start en Global.asax.cs:

ModelValidatorProviders.Providers.Clear(); 
ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider()); 
ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider(); 

Ahora, cambiar sus modelos de este modo:

public class Person 
{ 
    public Type ConcreteType { get; set; } 

    [Required] 
    [Display(Name = "Full name")] 
    [BaseType] 
    public string FullName { get; set; } 

    [Display(Name = "Address Line 1")] 
    [BaseType] 
    public virtual string Address1 { get; set; } 
} 

public class Sender : Person 
{ 
    public Sender() 
    { 
     this.ConcreteType = typeof(Sender); 
    } 

    [Required] 
    [Display(Name = "Address Line One")] 
    public override string Address1 { get; set; } 
} 

public class Receiver : Person 
{ 
} 

Tenga en cuenta que la clase base tiene una nueva propiedad, ConcreteType. Esto se usará para indicar qué clase de herencia ha instanciado esta clase base. Siempre que una clase heredante tenga metadatos que anulen los metadatos en la clase base, el constructor de la clase heredada debe establecer la propiedad ConcreteType de la clase base.

Ahora, aunque su vista utiliza la clase base, los atributos específicos de cualquier clase de herencia concreta aparecerán en su vista y afectarán la validación del modelo.

Además, debe poder convertir la Vista en una plantilla para el tipo Persona, y usar la plantilla para cualquier instancia que use la clase base o herede de ella.

+0

var propertyField = target.GetType(). GetField ("propiedad"); ¿Para qué es esta línea? – Sergey

+0

Y lo que es para target.GetType(). GetField ("vdi") – Sergey

1

Hmm, esto es un asunto difícil ya que el método HtmlHelper<T>.EditorFor utiliza el parámetro genérico de HtmlHelper<T> para averiguar qué atributos de validación son necesarios.

Sugeriría escribir su propio método de extensión EditorFor que delega llamadas al método HtmlHelper.Editor no genérico.

0

¿Ha considerado crear su propia EditorTemplate para Persona, Remitente y Receptor? El EditorPara y la PantallaPara buscar una plantilla personalizada que coincida con el tipo del objeto.

El método interno buscará una plantilla que coincida con el tipo del objeto. Luego buscará una plantilla que coincida con la clase base y luego en la cadena de herencia.

+1

Sí, lo hice, pero en mi caso las diferencias entre el Remitente y el Receptor no son significativas, así que no creo que haya un punto en tener la misma vista dos veces solo por el bien de cambiar la directiva @model en la parte superior. – pacu

Cuestiones relacionadas