Daniel Hill
Daniel Hill

Reputation: 71

Invalid Model State with ViewModel - C# ASP.NET CORE

Hopefully a basic question, I've created a "Create" page for Contacts which creates a list of all Customers in the system. I use a ViewModel to do this and this has been fine. However now on the Customers model I've assigned the CustomerName field to be Required it's causing a ModelState.IsValid to be false, and I cannot figure out why.

Customer

public class Customer
    {
        public int CustomerId { get; set; }
        [Required]
        [Display(Name = "Customer Name")]
        public string? CustomerName { get; set; }
        public ICollection<Contact> Contacts { get; set; }
        public ICollection<Job> Jobs { get; set; }
    }

Contact

public class Contact
    {
        public int ContactId { get; set; }
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Display(Name = "Email Address")]
        public string EmailAddress { get; set; }
        public string Telephone { get; set; }
        public Customer Customer { get; set; }
    }

ViewModel

    public class ContactDetailViewModel
    {
        public Contact Contact { get; set; }
        public Customer Customer { get; set; }
        public List<SelectListItem> Customers { get; set; }
    }

Form

        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Contact.FirstName" class="control-label"></label>
                <input asp-for="Contact.FirstName" class="form-control" />
                <span asp-validation-for="Contact.FirstName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Contact.LastName" class="control-label"></label>
                <input asp-for="Contact.LastName" class="form-control" />
                <span asp-validation-for="Contact.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Contact.EmailAddress" class="control-label"></label>
                <input asp-for="Contact.EmailAddress" class="form-control" />
                <span asp-validation-for="Contact.EmailAddress" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Contact.Telephone" class="control-label"></label>
                <input asp-for="Contact.Telephone" class="form-control" />
                <span asp-validation-for="Contact.Telephone" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Customer.CustomerName" class="control-label"></label>
                <select class="form-control dropdown" asp-for="Customer.CustomerId" asp-items="@Model.Customers">
                    <option></option>
                </select>
                <span asp-validation-for="Customer.CustomerId" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>

Controller

public async Task<IActionResult> Create([Bind("ContactId,FirstName,LastName,EmailAddress,Telephone")] Contact contact, ContactDetailViewModel contactDetailViewModel)
        {
            contact.Customer = await _context.Customers.FirstOrDefaultAsync(c => c.CustomerId == contactDetailViewModel.Customer.CustomerId);
            //Has to be a better way of doing this??? Why is it faling without... 06/03/2021
            //ModelState.Remove("Customer.CustomerName");

            if (ModelState.IsValid)
            {
                _context.Add(contact);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(contact);
        }

If I don't include ModelState.Remove("Customer.CustomerName"); then it doesn't work. However I don't want to be doing anything with the CustomerName as I just want to update the Contact.Customer to be the "new" selected customer.

Thanks!

Upvotes: 1

Views: 1140

Answers (1)

Serge
Serge

Reputation: 43850

You have the error in your class. Add CustomerId to Contact:

public class Contact
    {
        public int ContactId { get; set; }
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Display(Name = "Email Address")]
        public string EmailAddress { get; set; }
        public string Telephone { get; set; }
        public int  CustomerId { get; set; }
        public virtual  Customer Customer { get; set; }
    }

and fix the view:

<div class="form-group">
                <label asp-for="Customer.CustomerName" class="control-label"></label>
                <select class="form-control dropdown" asp-for="Contact.CustomerId" asp-items="@Model.Customers">
                    <option></option>
                </select>
                <span asp-validation-for="Contact.CustomerId" class="text-danger"></span>
            </div>

and you can fix action too:


 contact.Customer = await _context.Customers.FirstOrDefaultAsync(c => c.CustomerId == contactDetailViewModel.Contact.CustomerId);

but it is a reduntant code. You don't need this code at all, since you can save Contact without this.

and you need to fix the binding too by adding CustomerId:

public async Task<IActionResult> Create([Bind("ContactId, CustomerId, FirstName,LastName,EmailAddress,Telephone")] Contact contact, ContactDetailViewModel contactDetailViewModel)

and by the way you include the whole Customer in the model only for

  <label asp-for="Customer.CustomerName" class="control-label"></label>

you can leave Customer property null and use this code:

  <label asp-for="Contact.CustomerId" class="control-label"> Customer </label>

Upvotes: 1

Related Questions