asp.net-mvcrazorasp.net-core-mvcselectlistitem

Select Box Not Populated On View After Model State Is Invalid


I'm creating a new .NET Core MVC project for people to use to submit proposals for presentation at a conference.

For the proposal submission form I'm using a binding model (AKA ViewModel) to bind the user's input on the form.

Here is part of the code for the binding model class:

public class BaseSubmissionBindingModel
{
    public BaseSubmissionBindingModel()
    {
    }

    [Required]
    public int ConferenceId { get; set; }

    [Required]
    [StringLength(250)]
    [Display(Name = "Title")]
    public string SubmissionTitle { get; set; }

    [Required]
    [StringLength(1000)]
    [Display(Name = "Abstract")]
    public string SubmissionAbstract { get; set; }

    public IEnumerable<SelectListItem> SubmissionCategoryItems { get; set; }

    [Required]
    [Display(Name = "Select the Submission Category ")]
    public string SelectedSubmissionCategory { get; set; }
}

Note the field SubmissionCategoryItems - this field is used to populate a drop down select form control in the View. The data for this control comes from a database query and which submission categories to show depends on the conference id value.

In the Controller class I get the submission categories for the conference id and create a List<SelectListItem> submissionCategoryItems with an SelectListItem object for each submission category.

I then create the baseSubmissionBindingModel object and assign the submissionCategoryItems to the IEnumerable<SelectListItem> SubmissionCategoryItems.

The controller then returns View(baseSubmissionBindingModel).

The view page for the user's input renders correctly and the Select Submission Category select box has the correct options for each submission category.

Below is the HTML on the create submission form:

<div class="form-group">
    <label asp-for="SelectedSubmissionCategory"></label>
    <select asp-for="SelectedSubmissionCategory" asp-items="@Model.SubmissionCategoryItems">
        <option value="" selected> --select-- </option>
    </select>
    <span asp-validation-for="SelectedSubmissionCategory"></span>
</div>

My problem occurs if the user does not enter a required value on the form but then clicks on the submit button for the form.

My controller class has the CreateSubmission method below which gets called when the user clicks submit on the form:

public IActionResult CreateSubmission(BaseSubmissionBindingModel baseSubmissionBindingModel)
{
    if (!ModelState.IsValid)
    {
        return View("Index", baseSubmissionBindingModel);
    }

    Log.Information("Submission data provided is " + baseSubmissionBindingModel.ToString());

    return View("Success", baseSubmissionBindingModel);
}

If the user submits the form without providing all the required data then the model's state is invalid. The initial View is returned to the user along with the validation error messages.

However, the select box for the submission category no longer has any options for the submission categories retrieved from the database. So the user can no longer select a submission category.

How can I ensure the submission category select box still has its option elements if the user fails to initially provide all the required data but still submits the form?

Thank you for the help.

Bruce


Solution

  • When model state validation fails, you are returning to the same View, which has the <select> tag helper. Your select tag helper is using the SubmissionCategoryItems property of the passed ViewModel as the source collection to build the <options> for the select element.

    Http is stateless.

    For your second call (the form submission call), it does not have any idea what you did in your previous call (you did setup these collection items in your GET action call). So you need to set the collection property value every time you return to a View which uses that collection to build the select element.

    public IActionResult CreateSubmission(BaseSubmissionBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            // Set the collection property value again
            model.SubmissionCategoryItems = GetCategoryItems();
            
            return View("Index", model);
        }
    
        // TODO : do something useful
        Log.Information("Submission data " + baseSubmissionBindingModel.ToString());
    
        return View("Success", model);
    }
    
    private List<SelectListItem> GetCategoryItems()
    {
        var items = new List<SelectListItem>();
    
        // TODO : I hard coded 2 items. You can replace it with items from your database
    
        items.Add(new SelectListItem() { Value = "1", Text = "Seattle" });
        items.Add(new SelectListItem() { Value = "2", Text = "Detroit" });
    
        return items;
    }