我的模型中有以下设置:
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
{
}
}
在我看来:
@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>
}
启用了客户端验证。但是,如果我将一个Sender类型的对象发送到View,则客户端验证不会检测到需要Address1字段。有没有办法在这种情况下使客户端验证工作?
PS: 我发现如果我使用以下内容在视图中显示Address1字段,则客户端验证有效:
<div class="editor-field">
@Html.Editor("Address1", Model.Address1)
@Html.ValidationMessageFor(model => model.Address1)
</div>
答案 0 :(得分:8)
您可以自定义验证器和元数据来自您的具体类,但该解决方案有几个移动部件,包括两个自定义元数据提供程序。
首先,创建一个自定义Attribute
来装饰基类的每个属性。这对于我们的自定义提供程序来说是必需的,以指示何时需要进一步分析。这是属性:
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class BaseTypeAttribute : Attribute { }
接下来,创建一个继承自ModelMetadataProvider
的自定义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);
}
}
然后,创建一个继承自ModelValidatorProvider
的自定义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();
}
}
之后,在Global.asax.cs中注册Application_Start
中的两个自定义提供程序:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider());
ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider();
现在,改变你的模型:
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
{
}
请注意,基类有一个新属性ConcreteType
。这将用于指示哪个继承类已实例化此基类。每当继承类具有覆盖基类中元数据的元数据时,继承类的构造函数应该设置基类ConcreteType属性。
现在,即使您的视图使用基类,特定于任何具体继承类的属性也将出现在您的视图中,并将影响模型的验证。
此外,您应该能够将View转换为Person类型的模板,并将该模板用于使用基类的任何实例或从中继承。
答案 1 :(得分:1)
嗯,这是一个棘手的问题,因为HtmlHelper<T>.EditorFor
方法使用HtmlHelper<T>
的泛型参数来确定需要哪些验证属性。
我建议您编写自己的EditorFor扩展方法,该方法将调用委托给非泛型HtmlHelper.Editor方法。
答案 2 :(得分:0)
您是否考虑过为Person,Sender和Receiver创建自己的EditorTemplate? EditorFor和DisplayFor查找与对象类型匹配的自定义模板。
内部方法将查找与对象类型匹配的模板。然后,它将查找与基类匹配的模板,然后查找继承链。