Basics of ASP.NET MVC Dropdowns

June 9th, 2017

In our first post in the series, we get back to basics on how to implement dropdowns in ASP.NET MVC.

The Ultimate Guide to Dropdowns in ASP.NET MVC Series

Dropdowns are key user interface (UI) controls.

In ASP.NET WebForms, it was simple to attach a data source to a DropdownList control and display the dropdown on the form.

When ASP.NET MVC entered the picture, there were some...shall we call them differences, between MVC and WebForms.

In our first post of our dropdown series, we'll go through a simple example of a dropdown on the client-side and a dropdown from a database.

Our Simple Dropdown

In ASP.NET MVC, server controls are mostly tucked away in HtmlHelpers (and in Core, they are TagHelpers).

For example, if you want to create a simple TextBox, you use the HtmlHelper extension method in your View.

   @Html.TextBox("MyTextBox", "MyValue", new { @class = "text-right" })

TextBoxes are easy. Let's look at the Dropdown syntax.

   @Html.DropDownList("myDropDown", /*<dropdown list of items>*/, /* HtmlAttributes */)

This is one of the overloaded methods when using a Dropdown HtmlHelper. There are a few more, but for now, we'll focus on this one.

While you can add dropdown list items directly in your View, it doesn't mean you should.

Look at the following Dropdown HtmlHelper.

@Html.DropDownList("Months", new List<SelectListItem>
{
    new SelectListItem {Text = "January - 01", Value="1"  },
    new SelectListItem {Text = "February - 02", Value="2"  },
    new SelectListItem {Text = "March - 03", Value="3"  },
    new SelectListItem {Text = "April - 04", Value="4"  },
    new SelectListItem {Text = "May - 05", Value="5"  },
    new SelectListItem {Text = "June - 06", Value="6"  },
    new SelectListItem {Text = "July - 07", Value="7"  },
    new SelectListItem {Text = "August - 08", Value="8"  },
    new SelectListItem {Text = "September - 09", Value="9"  },
    new SelectListItem {Text = "October - 10", Value="10"  },
    new SelectListItem {Text = "November - 11", Value="11"  },
    new SelectListItem {Text = "December - 12", Value="12"  }
}, new {@class="form-control"})

While this gives us our desired result, it looks a little messy, doesn't it?

Seeing this in a View not only muddies the HTML waters, but it makes it hard to read. This creates what I call the Reese's Cup Dilemma. You are mixing code with Views. The separation of concerns is not there at all.

What happens if you need to use this Month dropdown in multiple Views? Don't tell me you'd cut-and-paste. ;-) I've already been down this path before as noted in my 5 Major MVC mistakes.

We need to pass the list into the View from the Controller through the ViewModel.

What Does A DropDownList Look Like?

A DropDownList is made up of a list of SelectListItems. I've always used a List of SelectListItems to build my dropdowns and it always seems to work.

There are also extension methods I've used in the past to handle this type of "busy work" when converting a list to a DropdownList. I've mentioned this in my DropdownList Cookbook.

Once you have your data, you can plug the data into your ViewModel.

Dropdown in a ViewModel

ViewModels are simply objects holding data to pass into the View. The View is strictly a template and it "plugs" data into certain areas you define in your View.

Here is our sample ViewModel with our month data.

Models/MonthViewModel.cs

public class MonthViewModel
{
    private readonly string[] _months = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
}

We have our Months, but how do we create a DropDownList from it?

We let our ViewModel perform some of the heavy lifting by building the dropdownlist for us as mentioned above (previous post: Make your ViewModels more functional).

Models/MonthViewModel.cs

public class MonthViewModel
{
    private readonly string[] _months = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;

    public IEnumerable<SelectListItem> GetMonthSelectList(int defaultValue)     {         return _months             .Where(month => !string.IsNullOrEmpty(month))             .Select((e, i) => new SelectListItem             {                 Text = e + " - " + (i + 1).ToString(),                 Value = (i + 1).ToString(),                 Selected = (i + 1 == defaultValue)             });     } }

The ViewModel delivers our data, but also helps in sending over a pre-built Dropdown list to our View.

If we look at our list in the View (Html), you'll see we have a cleaner dropdownlist syntax.

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @class = "form-horizontal" }))
{
    <div class="form-group">
        @Html.Label("Month", "Month:", new { @class = "col-sm-2 control-label" })
        <div class="col-sm-3">
            @Html.DropDownList("Months",
                Model.GetMonthSelectList(DateTime.UtcNow.Month),
                new { @class = "form-control" })
        </div>
    </div>
}

As a result of passing your data in, your HTML becomes cleaner.

Let's get Strong...Typed

After a user selects the month, we want to save it, but when we post the data back, our ViewModel won't reconstruct the DropdownList. It only passes back the value.

But where is it stored?

We need to add another property to our ViewModel to hold our value.

public class MonthViewModel
{
    private readonly string[] _months = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;

    public int SelectedMonth { get; set; }
    public IEnumerable<SelectListItem> GetMonthSelectList(int defaultValue)     {         return _months             .Where(month => !string.IsNullOrEmpty(month))             .Select((e, i) => new SelectListItem             {                 Text = e + " - " + (i + 1).ToString(),                 Value = (i + 1).ToString(),                 Selected = (i + 1 == defaultValue)             });     } }

So we added a SelectedMonth property to our ViewModel and this provides a strong-typed View using the ViewModel.

Our previous HTML had a "magic-string" called Months that would hold our value.

Since we have a place to store our value in the SelectedMonth property, we modify our DropDownList syntax to become more strong-typed.

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @class = "form-horizontal" }))
{
    <div class="form-group">
        @Html.Label("Month", "Month:", new { @class = "col-sm-2 control-label" })
        <div class="col-sm-3">
            @Html.DropDownListFor(model => model.SelectedMonth,
                Model.GetMonthSelectList(DateTime.UtcNow.Month),
                new { @class = "form-control" })
        </div>
    </div>
}

This DropDownListFor gives MVC's ModelBinder a clue as to where to store the value on a postback: in the model's SelectedMonth property.

I prefer this method because if I "fat-finger" a property name, I won't know at run-time. I'll know at design-time.

Getting Data From A Database

If you're retrieving data from a database, this effort simply adds another step: make a database call using ADO or Entity Framework.

Once you assign the months list to your ViewModel, you are done.

The ViewModel, when requested, will build your dropdownlist for you when it reaches the View using our GetMonthSelectList.

Models/MonthViewModel.cs

public class MonthViewModel
{
    public string[] MonthData { get; set; }

    public int SelectedMonth { get; set; }
    public IEnumerable<SelectListItem> GetMonthSelectList(int defaultValue)     {         return MonthData             .Where(month => !string.IsNullOrEmpty(month))             .Select((e, i) => new SelectListItem             {                 Text = e + " - " + (i + 1).ToString(),                 Value = (i + 1).ToString(),                 Selected = (i + 1 == defaultValue)             });     } }

Controllers/HomeController.cs

public class HomeController : Controller
{
    private MonthRepository _repository = new MonthRepository(
        ConfigurationManager.ConnectionStrings["DropdownDatabase"].ConnectionString);

    public ActionResult Index()     {         // We only want a list of months in a single array.         var model = new MonthViewModel         {             MonthData = _repository                 .GetAll()                 .Select(item => item.MonthName).ToArray()         };
        return View(model);     } }

The repository is simply an ADO call to pull data from the table.

Since everything is done on the C# side, our HTML in our View doesn't need modified at all.

Conclusion

As you can see, using dropdowns aren't difficult when integrating them into your ASP.NET MVC application.

Once you determine what kind of data you want in your dropdown and where it comes from, your list of SelectListItem's becomes easier to work with in your application.

Have any questions about dropdowns? Post you comments below and we'll see if we can address them in this series.

The Ultimate Guide to Dropdowns in ASP.NET MVC Series