Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net Web Api nested model validation

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.

like image 394
Toni Avatar asked Feb 14 '23 23:02

Toni


1 Answers

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;
    }
}
like image 138
David Ewen Avatar answered Feb 24 '23 00:02

David Ewen