Asp.net Web Api nested model validation

2019-05-06 18:59发布

I'm running in to a bit of a problem in asp.net web api's model binding and validation (via data annotations).

It seems like if i have a model with property such as

Dictionary<string, childObject> obj { get; set; }

the childObject's validations don't seem to trigger. The data is bound from json with Json.Net serializer.

Is there some workaround or fix to this? Or have I misunderstood something else related to this?


I can't help but wonder why this doesn't result in errors:

public class Child
{        
    [Required]
    [StringLength(10)]
    public string name;
    [Required]
    [StringLength(10)]
    public string desc;     
}

//elsewhere
Child foo = new Child();
foo.name = "hellowrodlasdasdaosdkasodasasdasdasd";

List<ValidationResult> results = new List<ValidationResult>();
Validator.TryValidateObject(foo, new ValidationContext(foo), results, true);
// results.length == 0 here.

Oh god. I had forgotten to declare properties instead of fields.

2条回答
Rolldiameter
2楼-- · 2019-05-06 19:37

There are 2 ways you can setup validation of the Dictionary Values. If you don't care about getting all the errors but just the first one encountered you can use a custom validation attribute.

public class Foo
{
    [Required]
    public string RequiredProperty { get; set; }

    [ValidateDictionary]
    public Dictionary<string, Bar> BarInstance { get; set; }
}

public class Bar
{
    [Required]
    public string BarRequiredProperty { get; set; }
}

public class ValidateDictionaryAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (!IsDictionary(value)) return ValidationResult.Success;

        var results = new List<ValidationResult>();
        var values = (IEnumerable)value.GetType().GetProperty("Values").GetValue(value, null);
        values.OfType<object>().ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
        Validator.TryValidateObject(value, new ValidationContext(value, null, validationContext.Items), results);
        return results.FirstOrDefault() ?? ValidationResult.Success;
    }

    protected bool IsDictionary(object value)
    {
        if (value == null) return false;
        var valueType = value.GetType();
        return valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof (Dictionary<,>);
    }
}

The other way is to create your own Dictionary as an IValidatableObject and do the validation in that. This solution gives you the ability to return all the errors.

public class Foo
{
    [Required]
    public string RequiredProperty { get; set; }

    public ValidatableDictionary<string, Bar> BarInstance { get; set; }
}

public class Bar
{
    [Required]
    public string BarRequiredProperty { get; set; }
}

public class ValidatableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();
        Values.ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
        return results;
    }
}
查看更多
The star\"
3楼-- · 2019-05-06 19:56

Validation always passes on fields because attributes can only be applied to properties. You need to change the fields name and desc into properties using auto implemented getter and setters.

These should then look something like

public string name { get; set; }
查看更多
登录 后发表回答