最后,我能够使用 customTypeDescriptor 生成客户端验证和绑定期间验证模型所需的 HTML 标记。
第一个 MyCustomTypeDescriptor.cs:
/// <summary>
/// CustomTypeDescriptor that provides validation in both MVC Web and WCF services.
/// </summary>
public class MyCustomTypeDescriptionProvider : TypeDescriptionProvider
{
public MyCustomTypeDescriptionProvider(TypeDescriptionProvider parent)
:base(parent)
{
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new MyCustomTypeDescriptor(base.GetTypeDescriptor(objectType, instance));
}
}
public class MyCustomTypeDescriptor : CustomTypeDescriptor
{
public MyCustomTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent)
{ }
public override PropertyDescriptorCollection GetProperties()
{
var originalProperties = base.GetProperties();
if (this.IsRequired(originalProperties))
{
var newProperties = new List<PropertyDescriptor>();
foreach (PropertyDescriptor property in originalProperties)
{
var attrs = property.Attributes;
var newAttrs = new Attribute[attrs.Count + 1];
attrs.CopyTo(newAttrs, 0);
newAttrs[attrs.Count] = new RequiredAttribute();
newProperties.Add(TypeDescriptor.CreateProperty(property.ComponentType, property, newAttrs));
}
return new PropertyDescriptorCollection(newProperties.ToArray());
}
else
{
return originalProperties;
}
}
/// <summary>
/// IsRequired just simulates more complex validation rule (dependant on another value in model)
/// </summary>
/// <param name="originalProperties"></param>
/// <returns></returns>
private bool IsRequired(PropertyDescriptorCollection originalProperties)
{
if (originalProperties == null || originalProperties.Count == 0)
{
throw new ArgumentNullException();
}
var dependantProperty = originalProperties.Find("DependantValue", false);
if (dependantProperty == null)
{
throw new InvalidOperationException();
}
var value = (int)dependantProperty.GetValue(base.GetPropertyOwner(dependantProperty));
return value > 0;
}
}
然后绑定这个描述符(每个实例!)我使用 MyModelValidatorProvider:
/// <summary>
/// validator provider is used only for unobtrusive validation
/// </summary>
public class MyModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var isPropertyValidation = metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName);
var model = context.Controller.ViewData.Model as TestCustomizedModel;
if (isPropertyValidation && model != null)
{
TypeDescriptor.AddProvider(new MyCustomTypeDescriptionProvider(TypeDescriptor.GetProvider(model)), model);
AttributeCollection newAttributes;
newAttributes = TypeDescriptor.GetProperties(model).Find(metadata.PropertyName, false).Attributes;
var attrArray = new Attribute[newAttributes.Count];
newAttributes.CopyTo(attrArray, 0);
attributes = attrArray;
}
return base.GetValidators(metadata, context, attributes);
}
}
这很好用,但是,在 ModelBinding 期间,没有设置 ViewData,因此 ValidatorProvider 不会挂钩。作为对此的解决方案,我使用了 MyModelBinder:
/// <summary>
/// Model binder that attaches CustomTypeDescriptor and validates model.
/// </summary>
public class MyModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
TypeDescriptor.AddProvider(new MyCustomTypeDescriptionProvider(TypeDescriptor.GetProvider(bindingContext.Model)), bindingContext.Model);
var errors = DataAnnotationRunner.GetErrors(bindingContext.Model);
if (errors != null)
{
foreach (var error in errors)
{
bindingContext.ModelState.AddModelError(error.MemberNames.FirstOrDefault() ?? string.Empty, error.ErrorMessage);
}
}
}
}
现在我可以使用 MyCustomTypeDescriptor 和 DataAnnotationRunner 来验证所有 MVC Web、MVC 其他类而不是控制器、html 帮助程序(不显眼的验证)以及其他项目,如 WCF 服务......
这一切都很好,只是感觉不对劲。如果我能够以某种方式将 MyCustomTypeDescriptor 直接连接到 MVC,那就太好了,但是正如这个链接所声称的那样,这似乎是不可能的。
如何在 ASP.NET MVC 中提供我自己的 ICustomTypeDescriptor?
欢迎任何有助于使该解决方案更优雅的改进。谢谢你。