Search This Blog

Sunday 8 December 2019

Validate nested class in MVC with Validator.TryValidateObject()

using System.ComponentModel.DataAnnotations;


public class ValidateModel
{
    static string result = string.Empty;
    public static string Validate(object obj)
    {
        var context = new ValidationContext(obj, serviceProvider: null, items: null);
        var results = new List<ValidationResult>();
        var isValid = Validator.TryValidateObject(obj, context, results, true);
        if (!isValid)
            foreach (var validationResult in results)
                result += validationResult.ErrorMessage;
        foreach (var prop in obj.GetType().GetProperties())
        {
            if (prop.PropertyType == typeof(string) || prop.PropertyType.IsValueType) continue;
            var value = prop.GetValue(obj);
            if (value == null) continue;
            var isEnumerable = value as IEnumerable;
            if (isEnumerable == null)
                Validate(value);
            else
                foreach (var nestedModel in isEnumerable)
                    Validate(nestedModel);
        }
        return result;
    }
}

Note:- 
MVC ModelBinder is doing all complex type DataAnnotations validation itself when you checked ModelState.IsValid in the action but it's failed when you pass the object instead of model.

In that case, you need to call the Validator.TryValidateObject() function but it's also failed to validate the complex/nested class DataAnnotations.

The above example is the solution for overcome to this issue using recursive function call.


=>Sample Model
public class RegistrationRequest
{
    public object body { get; set; }
}
public class Registration
{
    public int Id { get; set; }
    [Required]
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    [Required]
    public string LastName { get; set; }
    public Address address { get; set; }
    public List<PastHistory> pastHistory { get; set; }
}
public class Address
{
    [Required]
    public string Address1 { get; set; }
    [Required]
    public string Address2 { get; set; }
    public string Address3 { get; set; }
    [Required]
    public string State { get; set; }
    [Required]
    public string City { get; set; }
    [Required]
    public string Pincode { get; set; }
}
public class PastHistory
{
    [Required]
    public string ComapnyName { get; set; }
}

=>Sample Controller
[HttpPost]
public HttpResponseMessage Registration(RegistrationRequest reg)
{
    Registration registration = JsonConvert.DeserializeObject<Registration>(reg.body.ToString());

    string validateResult = ValidateModel.Validate(registration);
    if (!string.IsNullOrEmpty(validateResult))
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, validateResult);
    else
        return Request.CreateResponse(HttpStatusCode.OK);
}

=>Sample JSON
   "body":{ 
      "Id":"1",
      "FirstName":"Ram",
      "MiddleName":"",
      "LastName":"",
      "Address":{ 
         "Address1":"",
         "Address2":"",
         "Address3":"",
         "State":"",
         "City":"",
         "Pincode":""
      },
      "PastHistory":[ 
         { 
            "ComapnyName":""
         }
      ]
   }
}



3 comments:

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. I like this but result variable is not initialized in each api call. It keeps the previous error msg with the next api call. What to do here

    ReplyDelete