Reputation: 71
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
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