DropDownLists 的验证已损坏

Validation for DropDownLists Broken

这与 将模型数据从两个 DropDownList 传递到部分视图有关,其中用户必须 select 视图中的部门和年份。然后数值数据显示在局部视图的 table 中。该视图包含下拉菜单和 table headers。分部视图包含带有数值数据的行。现在,验证被打破了。两个下拉菜单都是必需的。如果我提交的表单没有 selected,我会得到这个错误:

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

错误发生在这一行:

@Html.DropDownListFor(m => m.SelectedDepartment, Model.Departments,
    "Select Department", new { @class = "form-control" })

打到控制器时,模型状态无效

查看:

@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);
        }
    } 
}

型号:

    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";
    }

控制器:

// 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");
    }
}

这似乎是 HttpPost 方法的问题。提交表单时,集合未通过,因此我们丢失了其中的数据。现在,HttpPost 方法 return 相同的视图,但它只分配 if 块中的集合,因此当缺少任何一个下拉列表时, if 状态将为 false执行将进入 else 块,并将 return 与模型一起查看。在这种情况下,集合从未分配数据。将 HttpPost 更新为以下内容,因此无论其他条件如何都会填充集合。

// 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");
    }
}