È possibile personalizzare i validatori ei metadati che provengono dalla classe concreta, ma la soluzione presenta diverse parti mobili, inclusi due provider di metadati personalizzati.
Innanzitutto, creare un numero personalizzato Attribute
per decorare ciascuna proprietà della classe base. Questo è necessario come indicatore per i nostri fornitori personalizzati, per indicare quando è necessaria un'ulteriore analisi. Questo è l'attributo:
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class BaseTypeAttribute : Attribute { }
successivo, creare un personalizzato ModelMetadataProvider
eredita da 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);
}
}
Quindi, creare un costume ModelValidatorProvider
eredita da 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();
}
}
Dopo di che, registrarsi entrambi i fornitori personalizzati in Application_Start
in Global.asax.cs:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider());
ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider();
Ora, cambiare i vostri modelli in questo 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
{
}
Si noti che la classe di base ha una nuova proprietà, ConcreteType
. Questo sarà usato per indicare quale classe ereditaria ha istanziato questa classe base. Ogni volta che una classe ereditaria ha metadati che sovrascrivono i metadati nella classe base, il costruttore della classe ereditante deve impostare la proprietà ConcreteType della classe base.
Ora, anche se la vista usa la classe base, gli attributi specifici di qualsiasi classe di derivazione concreta appariranno nella vista e influenzeranno la convalida del modello.
Inoltre, si dovrebbe essere in grado di trasformare la vista in un modello per il tipo Persona e utilizzare il modello per qualsiasi istanza che utilizza la classe di base o che ne eredita.
Il mittente è una persona, ma la persona non è un mittente, la tua vista è fortemente digitata a persona, quindi, non verrà mai rilevato alcunché che abbia a che fare con il mittente. – Maess
In realtà se si aggiunge questo alla vista: Model.GetType(). ToString() vedrai che viene visualizzato quanto segue: QuickTest.Models.Sender che significa che il tipo è noto quando la vista è renderizzata. – pacu
Tuttavia, EditorFor() sta per trattare è come il tipo che hai fortemente digitato, che è persona. – Maess