Bạn có thể tùy chỉnh trình xác thực và siêu dữ liệu đến từ lớp bê tông, nhưng giải pháp có một số phần chuyển động, bao gồm hai nhà cung cấp siêu dữ liệu tùy chỉnh.
Đầu tiên, tạo một tùy chỉnh Attribute
để trang trí từng thuộc tính của lớp cơ sở. Điều này là cần thiết như một lá cờ cho các nhà cung cấp tùy chỉnh của chúng tôi, để chỉ ra khi cần phân tích thêm. Đây là thuộc tính:
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class BaseTypeAttribute : Attribute { }
Tiếp theo, tạo một phong tục ModelMetadataProvider
kế thừa từ 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);
}
}
Sau đó, tạo một phong tục ModelValidatorProvider
kế thừa từ 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();
}
}
Sau đó, đăng ký cả nhà cung cấp tùy chỉnh trong Application_Start
trong Global.asax.cs:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider());
ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider();
Bây giờ, thay đổi mô hình của bạn như sau:
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
{
}
Lưu ý rằng các lớp cơ sở có một tài sản mới, ConcreteType
. Điều này sẽ được sử dụng để chỉ ra lớp kế thừa nào đã khởi tạo lớp cơ sở này. Bất cứ khi nào một lớp kế thừa có siêu dữ liệu ghi đè siêu dữ liệu trong lớp cơ sở, hàm tạo của lớp kế thừa sẽ thiết lập thuộc tính ConcreteType lớp cơ sở.
Bây giờ, mặc dù chế độ xem của bạn sử dụng lớp cơ sở, các thuộc tính cụ thể cho bất kỳ lớp kế thừa cụ thể nào sẽ xuất hiện trong chế độ xem của bạn và sẽ ảnh hưởng đến việc xác thực mô hình.
Ngoài ra, bạn sẽ có thể biến Chế độ xem thành mẫu cho loại Người và sử dụng mẫu cho bất kỳ cá thể nào sử dụng lớp cơ sở hoặc kế thừa từ mẫu đó.
Tên người gửi là người, nhưng người đó không phải là người gửi, chế độ xem của bạn được nhập mạnh vào người, do đó, nó sẽ không bao giờ phát hiện bất kỳ điều gì liên quan đến người gửi. – Maess
Trên thực tế nếu bạn thêm điều này vào khung nhìn: Model.GetType() ToString() bạn sẽ thấy rằng sau đây được hiển thị: QuickTest.Models.Sender có nghĩa là loại được biết khi khung nhìn được hiển thị. – pacu
Tuy nhiên, EditorFor() sẽ xử lý là loại bạn đã gõ mạnh, đó là người. – Maess