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.
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
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
Sin embargo, EditorFor() va a tratar como el tipo al que lo ha escrito fuerte, que es persona. – Maess