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