0

Overview

I'm working on a project where I ingest a CSV file, where each record results in the creation of a model in the back end. After ingestion, I am able to create a list of models, and I can return the list of models back to the view with no issue.

However, I want to do custom validation on each model in the list and return the validation results to the view. In this example, I have custom model validation that checks if the email already exists via a database check. When I am creating a model one at a time through a Form on a view, everything goes well. All the validation is run, and there are no issues.

Problem

The issue is when I programmatically create List<Person>(), and then try to validate each Person model in the list. What ends up happening is all the errors are aggregated and when I return the list to the view and display in table via foreach(var p in Model.PersonList), with @Html.ValidationSummary(), then each table row shows all the errors of all the models, not just the errors that pertain to that PersonModel.

Within a foreach loop, I call TryValidateModel() and follow it with ModelState.Clear(), but this does not help. I've also tried using ModelState.Clear() at the beginning of the foreach loop, again no help.

Simplified code of what I'm doing and where I'm stuck.

Controller code - root of problem is here in the foreach loop:

[HttpPost]
public IActionResult BulkModelCreation(BulkUpload inputCSV)
{
   // No issue here, I successfully convert CSV to data table. Each Record represents a model 
   // If there are 10 records in the CSV, then I will have a list of 10 People
   DataTable dt = CSVHelper.CSVToDataTable(inputCSV.InputFile);
  
   List<Person> people = new List<Person>();

   if (dt != null)
   {  
      // My issues is located here, I think
      // In the foreach loop, I read each row from the DataTable, and create a new Person Model
      // I want to validate each PersonModel and then add Person to people list
      foreach (DataRow dr in dt.Rows)
      {
         // Create new person
         Person p = new Person();
         p.FirstName = dr["FirstName"].ToString();
         p.LastName = dr["LastName"].ToString();
         // More model attributes
         p.Email = dr["Email"].ToString();

         // Validate the model, and the validation works
         TryValidateModel(p);

         people.add(p);

         // This is where I am unsure of what to do
         //To try and reset for the next PersonModel in the List, but this does not work. 
         // If any person in the list has a duplicate email, then all items in the list will show
         // as having a duplicate email.
         ModelState.Clear();
      }   
   }   

   // Other stuff, create model to be returned that has an attribute that is a List<People>
   ModelToReturn ModelReturn = new ModelToReturn();
   ModelReturn.Persons = people;

   // Obj is returned, and data is populated. No issue here
   return View(ModelReturn)
}

Simplified model class:

public class PersonModel
{
    [Required]
    public string FirstName {get;set;}
    [Required]
    public string LastName {get;set;}
    // More stuff
    //Custom Valiation
    [EmailValidation]
    public string Email {get;set;]
}

Custom validation:

public class EmailValidationAttribute: ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var PersonModel = (PersonModel)validationContext.ObjectInstance;
        // Passing in Person Repository service so that I can check the db if email already exists
        var service = (IPersonRepository)validationContext.GetService(typeof(IPersonRepository));
        bool isDuplicate = service.CheckDuplicate(PersonModel.Email);

        if (isDuplicate)
        {
            return new ValidationResult("Email Already Exists");
        }
        else
        {
             return ValidationResult.Success;
        }
    }       
}

Simplified view:

<table class="table">
  <thead>
    <tr>
      <th scope="col">First Name</th>
      <th scope="col">Last Name</th>
      <th scope="col">More Stuff</th>
      <th scope="col">Email</th>
    </tr>
  </thead>
  <tbody>
    @foreach(var p in Model.Persons)
    {
    <tr>
      <td>
         <input asp-for="FirstName" class="form-control" />
      </td>
      <td>
         <input asp-for="LastName" class="form-control" />
      </td>
      <td>MORE STUFF</td>
      <td>
         <input asp-for="Email" class="form-control" />
             @Html.ValidationSummary()<!-- if one person has a duplicate email, then this will say 'Duplicate Email' for every single record in Model.Persons-->
      </td>
     </tr>
    }
  </tbody>
</table>

Note, I have reviewed and tried what was suggested in this post: Validate list of models programmatically in ASP.NET MVC

But that does not work, either for the OP, and myself. Any help with this would be greatly appreciated.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Andrew
  • 1
  • You'll need a more customized validation. Keep a dictionary of record id (row id) and its corresponding validation object then the view will need to parse and display the errors per record. I may be useful to map to a new model PersonResponseModel which will probably be very similar to PersonModel but with your error property and omit further validation attributes. Or wrap the person list in a TransactionResponseModel with properties related to the transaction (status, errors, etc.). – Jasen Dec 20 '21 at 19:22
  • Thanks @Jasen that's what I ended up doing and it worked out. – Andrew May 04 '22 at 18:10

0 Answers0