Reputation:
I have read that ViewBags are bad and should be avoided if at all possible, and yet every training piece I have seen (including the MVC 5 tutorial and many SO answers) shows the style shown below to populate a DropDownList in the View.
If it's one or two parameters, it's not noticeable, but a customer requested something very specific: a monster-sized table with 20 separate independent parameters used to filter the database query. The example I have shown you is highly trimmed down. Just imagine that there are far more parameters, and can imagine how the Index Action would become very fat and not-simple.
I can use #region
blocks to collapse sections of the Action, which can help with organization and readability, but I wonder if there is a better way to do this.
Here lies the big question: are there any alternatives to the use of ViewBags to populate DropDownLists from the Controller?
Excerpt from the Index Action in the Controller:
public ActionResult Index(string campus = "CRA", string fy = "FY16", int? ageYr)
{
// Used to populate the DropDownLists in the HTML.BeginForm() block.
ViewBag.CampusList = new SelectList(new List<string> { "CRA", "DRA", "MRA", "PRA" }, campus);
ViewBag.FyList = new SelectList(new List<string> { "FY12", "FY13", "FY14", "FY15", "FY16" }, fy);
// Optional Filter
List<int?> ages = query.Select(m => (int?)m.AgeYrAtEndOfSy).Distinct().OrderBy(m => m).ToList();
ages.Insert(0, null);
ViewBag.AgeYrList = new SelectList(ages, ageYr);
IEnumerable<StudentSummaryLayer0> query = db.StudentSummaryLayer0
.Where(m => m.Campus == campus).Where(m => m.FY == fy);
if (ageYr != null) query = query.Where(m => m.AgeYrAtEndOfSy == ageYr);
query = query.OrderBy(m => m.StudentName);
return View(query.ToList());
}
Excerpt from the Index View:
@model IEnumerable<RoseDashboardFY16.Models.StudentSummaryLayer0>
@using (Html.BeginForm("Index", "StudentSummaryLayer0", FormMethod.Post))
{
<div class="panel panel-default">
<div class="panel-heading">Filter results</div>
<div class="panel-body">
<p>
Required Filters: <br />
<strong>Campus</strong>: @Html.DropDownList("campus", (SelectList)ViewBag.CampusList)
<strong>FY</strong>: @Html.DropDownList("fy", (SelectList)ViewBag.FyList)
</p>
@*Imagine many more parameters.*@
<p style="line-height:2.0">
Optional Attribute Filters: <br />
<span class="KeepTogether">
<strong>Age (Yr)</strong>: @Html.DropDownList("ageYr", (SelectList)ViewBag.AgeYrList)
</span>
</p>
<div><input type="submit" value="Search" /><input type="reset" value="Reset"></div>
</div>
</div>
}
Upvotes: 0
Views: 1231
Reputation: 26018
There are many ways to populate drop down lists. I never use view bags
. Below is code on how I normally populate my drop down lists.
In my example I am going to populate a drop down list containing a list of statuses. When my view
contains a list of items and nothing else then I am ok to pass through a list to the view, and not a view model
. If there is more data that needs to be passed to the view then I will make use of a view model. In this example I made use of a view model because it needs to contain a list of statuses and the selected status id:
public class TestViewModel
{
public int StatusId { get; set; }
public List<Status> Statuses { get; set; }
}
This is the domain model
that will contain each status data:
public class Status
{
public int Id { get; set; }
public string Name { get; set; }
}
In my controller's action method I would populate the list of statuses. Normally from a database call, but in this example I am hard coding my list:
public ActionResult Index()
{
TestViewModel model = new TestViewModel();
model.Statuses = new List<Status>();
model.Statuses.Add(new Status { Id = 1, Name = "Status 1" });
model.Statuses.Add(new Status { Id = 2, Name = "Status 2" });
model.Statuses.Add(new Status { Id = 3, Name = "Status 3" });
return View(model);
}
After the list has been populated I pass the view model to the view. The view accepts this view model and creates the drop down list:
@model WebApplication_Test.Models.TestViewModel
@using (Html.BeginForm())
{
<div>
@Html.DropDownListFor(
m => m.StatusId,
new SelectList(Model.Statuses, "Id", "Name", Model.StatusId),
"-- Select --"
)
@Html.ValidationMessageFor(m => m.StatusId)
</div>
<div>
<button type="submit">Submit</button>
</div>
}
I hope this helps.
Upvotes: 1
Reputation: 239280
As others have stated, the preferred method is to use a view model. The big problem with ViewBag
and why its use is discouraged is that it's dynamic. You lose type-safety and are forced to do manual casting, which can lead to bugs. Additionally, since the casting is done in the view, you get runtime bugs, which are like little time-bombs in your code. Ideally, you want errors at compile-time so you can fix them before you deploy. A view model allows you to stay strongly-typed and will break at compile-time if it's going to break at all.
As far as keeping your actions clean, construction of your select lists should be handled either in your view model or in a private/protected method on your controller (if it requires database-access). This way you can easily reuse the same construction elsewhere it may be need. For instance, with GET and POST versions of an action:
public ActionResult Foo()
{
...
PopulateSelectLists(model);
return View(model);
}
[HttpPost]
public ActionResult Foo(FooViewModel model)
{
...
PopulateSelectLists(model);
return View(model);
}
private void PopulateSelectLists(FooViewModel model)
{
// set select list properties
}
Additionally, you want to avoid newing up SelectList
instances. All Razor needs is IEnumerable<SelectListItem>
. It will automatically create the SelectList
instance and set the appropriate item's Selected
property based on the model's state.
Upvotes: 2