xVal如何验证复杂类型的子属性?

时间:2009-09-02 19:56:47

标签: asp.net-mvc validation data-annotations xval

我在我的ASP.NET MVC应用程序中使用xVal,这在一般情况下很棒。在Steve Sanderson's blog post之后,我创建了一个DataAnnotationsValidationRunner来对属性对象进行服务器端验证。这适用于简单的类。例如人:

public static class DataAnnotationsValidationRunner
{
    public static IEnumerable<ErrorInfo> GetErrors(object o)
    {
        return from prop in TypeDescriptor.GetProperties(o).Cast<PropertyDescriptor>()
               from attribute in prop.Attributes.OfType<ValidationAttribute>()
               where !attribute.IsValid(prop.GetValue(o))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), o);
    }
}

public class Person
{
    [Required(ErrorMessage="Please enter your first name")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Please enter your last name")]
    public string LastName { get; set; }
}

但是,如果我向此人添加Address属性,并使用DataAnnotation属性标记Address类,则不会验证它们。例如

public class Person
{
    [Required(ErrorMessage="Please enter your first name")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Please enter your last name")]
    public string LastName { get; set; }

    public Address Address { get; set; }
}

public class Address 
{
    [Required(ErrorMessage="Please enter a street address")]
    public string Street { get; set; }

    public string StreetLine2 { get; set; }

    [Required(ErrorMessage = "Please enter your city")]
    public string City { get; set; }

    [Required(ErrorMessage = "Please enter your state")]
    public string State { get; set; }

    [Required(ErrorMessage = "Please enter your zip code")]
    public string Zip { get; set; }

    public string Country { get; set; }
}

一个问题是DataAnnotationValidationRunner不会向下走复杂的子属性。此外,如果将这些错误添加到错误集合中,则在添加到模型状态时仍需要正确添加前缀。例如。 Person错误添加如下:

    catch (RulesException ex)
    {
        ex.AddModelStateErrors(ModelState, "person");
    }

我认为地址规则例外需要以“person.address”作为前缀。是否支持使用xVal处理子对象验证的方法,或者创建扁平数据传输对象是否是唯一的解决方案?

2 个答案:

答案 0 :(得分:2)

首先,您需要区分Steve Sanderson的DataAnnotationsModelBinder和

关于您的第一个问题(“DataAnnotationValidationRunner不会走下复杂的子属性”):

你可能是指Brad Wilson的DataAnnotationModelBinder吗?如果是这样,它确实应该将复杂的ViewModel验证到最后的属性。如果没有,请尝试使用它而不是您正在使用的DataAnnoationsModelRunner。这篇关于Client-Side Validation with xVal的博客文章博客文章展示了如何做。

DataAnnotationModelBinder的第一个版本有一个错误,当它与复杂的视图模型一起使用时会崩溃。也许有一个新版本可以修复崩溃,但忽略了复杂的模型?

在任何情况下,我都建议在上面链接的博客文章的演示项目中使用DataAnnotationModelBinder的版本。我在我自己的实际项目中使用它,它确实适用于复杂的视图模型。

关于您的第二个问题“是否支持使用xVal处理子对象验证的方法”

您尚未发布任何驻留在ASPX表单上的代码,但您可能也指的是&lt;%= Html.ClientSideValidation()%&gt;仅将客户端验证添加到该模型类型的直接属性,但不添加子对象的属性。您可以使用多个ClientSideValidation语句来解决问题,例如:

<%= Html.ClientSideValidation<ModelType>()%>
<%= Html.ClientSideValidation<ChildModelType>("ChildModelPropertyName")%>

答案 1 :(得分:1)

我遇到了同样的问题。我需要验证可以作为另一个对象的属性出现的复杂对象。我还没有进入客户端验证(但是),但Adrian Grigore关于多个html.ClientSideValidation()的想法似乎可能就是那里的票。

我最终创建了一个标记界面,标记了我需要验证的所有类。它可以是一个属性,也可以将此想法用于类的所有属性。

基本上,它使用您在上面提到的DataAnnotationsValidationRunner验证对象,然后迭代对象的属性并对所有这些属性运行DataAnnotationsValicationRunner,直到无法检查为止。

这是我所做的伪代码:

IEnumarable<ValidationError> GetErrors(object instance) {
    List<ValidationError> errors = new List<ValidationError>();
    errors.AddRange(GetDataAnnotationErrors(instance));
    errors.AddRange(GetPropertyErrors(instance));
    return errors;
}
IEnumerable<ValidationError> GetDataAnnotationErrors(object instance) {
    // code very similar to what you have above
}
IEnumearable<ValidationError> GetPropertyErrors(object instance)
{
     var errors = new List<ValidationError>();
     var objectsToValidate = instance.GetType().GetProperties().Where(p => p.PropertyType.GetInterface().Contains(typeof(IMarkerInterface)));
     // the call above could do any type of reflecting over the properties you want
     // could just check to make sure it isn't a base type so that all custom 
     // object would be checked
     if(objectsToValidate == null) return errors;
     foreach(object obj in objectsToValidate)
     {
          errors.AddRange(GetDataAnnotationErrors(obj));
          errors.AddRange(GetPropertyErrors(obj));
     }
     return errors;
}

我希望这很清楚。我一直在域对象上测试这个系统,到目前为止一直很好。在这里和那里解决一些问题,但这个想法已经证明了我正在做的事情。