Validation for DropDownLists Broken

This is related to a previous question on passing model data to a partial view from two DropDownLists where the user must select department and year in a view. Numerical data is then displayed in a table in the partial view. The view contains the dropdowns and the table headers. The partial view contains the rows with the numerical data. Right now, the validation is broken. Both dropdowns are required. If I submit form with either dropdown not selected, I get this error:

There is no ViewData item of type 'IEnumerable<SelectListItem>' that has
the key 'SelectedDepartment'

Error occurs at this line:
@Html.DropDownListFor(m => m.SelectedDepartment, Model.Departments,
    "Select Department", new { @class = "form-control" })

When it hits the controller, the model state is invalid.

View:

@model BudgetDemo.Models.BudgetsActualsViewModel

@using (Html.BeginForm("GetBudgetsActuals", "BudgetsActuals", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    <div class="col-md-6">
        <div class="form-group">
            @Html.DropDownListFor(m => m.SelectedDepartment, Model.Departments, 
                "Select Department", new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.SelectedDepartment, "", 
                new { @class = "text-danger" })
        </div>
    </div>
    <div class="col-md-6">
        <div class="form-group">
            @Html.DropDownListFor(m => m.SelectedYear, Model.Years, "Select Year", 
                new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.SelectedYear, "", 
                new { @class = "text-danger" })
        </div>
   </div>

   <div class="form-group">
       <div class="col-lg-7">
           <input type="submit" value="Submit" class="btn btn-info" />
       </div>
   </div>

    @if (Model.SelectedDepartment != null && Model.SelectedYear != null)
    {
        // table headers, etc
        @if (Model != null)
        {
            Html.RenderPartial("_BudgetsActuals", Model.BudgetActualCollection);
        }
    } 
}

Model:
    public class BudgetsActualsViewModel
    {
        // Cost Center / Department Drop Down
        [Display(Name = "Cost Center/Department")]
        [Required(ErrorMessage = "Cost Center/Department is required.")]
        [StringLength(62)]
        public string SelectedDepartment { get; set; }
        public List<SelectListItem> Departments { get; set; }
    
        // Year Drop Down
        [Display(Name = "Year")]
        [Required(ErrorMessage = "Year is required.")]
        public string SelectedYear { get; set; }
        public List<SelectListItem> Years { get; set; }
    
        // Account and Name fields
        [Display(Name = "Account")]
        [StringLength(9)]
        public string Account { get; set; }
    
        [Display(Name = "Name")]
        [StringLength(12)]
        public string CostCenter { get; set; }
    
        // Seven calculated fields: Oct Actual, Oct Budget, YTD Actual, YTD 
        // Budget, YTD Variance, Est To Complete, Est at Complete
        [Display(Name = "TotalCurrentMonthActualConversion", 
        ResourceType = typeof(DynamicDisplayAttributeNames))]
        public int TotalCurrentMonthActual { get; set;  }
    
        [Display(Name = "TotalCurrentMonthBudgetConversion", 
            ResourceType = typeof(DynamicDisplayAttributeNames))]
        public int TotalCurrentMonthBudget { get; set; }
    
        [Display(Name = "YTD Actual", AutoGenerateFilter = false)]
        public int TotalYTDActual { get; set; }
    
        [Display(Name = "YTD Budget", AutoGenerateFilter = false)]
        public int TotalYTDBudget { get; set; }
    
        [Display(Name = "YTD Variance", AutoGenerateFilter = false)]
        public int TotalVariance { get; set; }
    
        [Display(Name = "Est to Complete", AutoGenerateFilter = false)]
        public int TotalETCBudget { get; set; }
    
        [Display(Name = "Est at Complete", AutoGenerateFilter = false)]
        public int TotalEAC { get; set; }
    }
    
    public class DynamicDisplayAttributeNames
    {
        public static string TotalCurrentMonthActualConversion { get; set; } 
            = DateTime.Now.AddMonths(-1).ToString("MMM") + " Actual";
        public static string TotalCurrentMonthBudgetConversion { get; set; } 
            = DateTime.Now.AddMonths(-1).ToString("MMM") + " Budget";
    }

Controller:
// POST: Grab data for department and year
[HttpPost]
public ActionResult GetBudgetsActuals(BudgetsActualsViewModel model)
{
    try
    { 
        if (ModelState.IsValid)
        {
            var repo = new BudgetDemoRepository();
            ModelState.Clear();

            model.Departments = repo.GetBudgetsActuals().Departments;
            model.Years = repo.GetBudgetsActuals().Years;
            model.BudgetActualCollection = repo.GetBudgetsActualsData(model);
         }
         else
         {
             // Execution gets to here before returning to view
             model.BudgetActualCollection = new 
                 List<BudgetDemo.Models.BudgetsActualsViewModel>();
         }
         return View(model);
    }
    catch
    {
         return View("Error");
    }
}

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

This seems to be an issue with HttpPost method. When the form is submitted, the collections weren’t passed, so we loose the data in them. Now, HttpPost method return the same view, but it only assigns the collections in if block, so when any one of the dropdown is missing, if state will be false and execution will go in else block and will return view with model. In this case, collections were never assigned data. Update HttpPost to below, so collections are populated regardless of other conditions.

// POST: Grab data for department and year
[HttpPost]
public ActionResult GetBudgetsActuals(BudgetsActualsViewModel model)
{
    try
    { 
         // assign collections before returning view
         var repo = new BudgetDemoRepository();
         model.Departments = repo.GetBudgetsActuals().Departments;
         model.Years = repo.GetBudgetsActuals().Years;

         if (ModelState.IsValid)
         {            
            ModelState.Clear();            
            model.BudgetActualCollection = repo.GetBudgetsActualsData(model);
         }
         else
         {
             model.BudgetActualCollection = new 
                 List<BudgetDemo.Models.BudgetsActualsViewModel>();
         }
         return View(model);
    }
    catch
    {
         return View("Error");
    }
}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x