Drop down lists in ASP.NET MVC

This post shows two methods of implementing drop down lists in ASP.NET MVC 4. The code for data access and the general layout of the application should not be considered suitable for anything other than pedagogical purposes.

The provided source code uses entity framework and requires a local database to be running, see the web.config for naming.

The main components of the application are Vehicle and TyreType classes, and controllers for each.
The controllers have the standard Index/Create/Edit/Details/Delete views and actions.
The VehicleController shows two ways of providing a TyreType drop down list, one using the ViewBag and the other using a VehicleViewModel.

The TyreType is as follows.

using System.ComponentModel.DataAnnotations;

namespace Automobile.Models
{
    public class TyreType
    {
        public int TyreTypeID { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public string Material { get; set; }
    }
}

And the Vehicle looks like this.

using System.ComponentModel.DataAnnotations;

namespace Automobile.Models
{
    public class Vehicle
    {
        public int VehicleID { get; set; }
        [Required]
        public string Name { get; set; }
        public string Description { get; set; }
        public double MSRP { get; set; }
        [Required]
        public int TyreTypeID { get; set; }
        public virtual TyreType TypeType { get; set; }
    }
}

The TyreTypeController is a standard controller with text boxes for entering information on the Edit and Create views.

The VehicleController has a drop down list filled with available tyre types as found in the database.

Using the ViewBag

The first method of filling the drop down list is to use the ViewBag.

In the [HttpPost]Edit method we have –

ViewBag.TyreTypeID = new SelectList(db.TyreType, "TyreTypeID", "Name", vehicle.TyreTypeID);

I’ll explain in detail what this line of code is doing –
ViewBag.TyreTypeID – is referencing the ViewBag and dynamically adding an entry called TyreTypeID.

new SelectList(db.TyreType, "TyreTypeID", "Name", vehicle.TyreTypeID) – is doing five things –

  1. new SelectList – creates a new SelectList(this is what drop down lists use)
  2. db.TyreType – passing in the TypeTypes from the database
  3. "TyreTypeID" – specifying that this public property of TyreType will be used for dataValueField
  4. "Name" – specifying that this public property of TyreType will be used for dataTextField
  5. vehicle.TyreTypeID – specifying the selected value in the drop down list, note that it is the same property as the dataValueField

Inside the TyreType Edit view we have –

@model Automobile.Models.Vehicle
…snip…
   <div class="editor-label">
      @Html.LabelFor(model => model.TyreTypeID, "Type Type")
   </div>
   <div class="editor-field">
      @Html.DropDownList("TyreTypeID","--Select a tyre type--")
      @Html.ValidationMessageFor(model => model.TyreTypeID)
   </div>

In this example the "TyreTypeID" specifies both the source of items for the drop down list and the destination property for the selected value that will be sent back to the controller. In the example, the Vehicle is sent to the Edit action, so the Vehicle. TyreTypeID is set with the selected value.

I found this very confusing at first because I wanted to change the name of the ViewBag property for storing the drop down list items, so my selected value was being lost. MVC is doing a lot by convention over coding, if you fight it you will have to learn many rules.

I’m not a big fan of this approach, too many things are happening without my explicit control and if forces me to use the same name for a list of items (the SelectList in the ViewBag) and the selected item (an int).

Using a ViewModel

One alternative is to create a ViewModel for the Vehicle and the values for the drop down list. Note again, I’m not advocating this methodology for data access for production code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Automobile.DataAccess;
using Automobile.Models;

namespace Automobile.ViewModels
{
    public class VehicleViewModel
    {
        private AutomobileContext db = new AutomobileContext();
        public IEnumerable<SelectListItem> TyreTypes { get; set; }
        public Vehicle Vehicle { get; set; }
        public VehicleViewModel(Vehicle vehicle)
        {
            Vehicle = vehicle;
            TyreTypes = PopulateTyreTypes();
        }
        private IEnumerable<SelectListItem> PopulateTyreTypes()
        {
            var tyreTypesQuery = db.TyreType.OrderBy(t => t.Name);
            return new SelectList(tyreTypesQuery,"TyreTypeID","Name");
        }
    }
}

Inside the VehicleController I have

public ActionResult Create()
{
   //we're going to use the view model to send the tyre types to the view here.
   var vehicleViewModel = new VehicleViewModel(new Vehicle());
   return View(vehicleViewModel);
}
   
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Vehicle vehicle)
{
   if (ModelState.IsValid)
   {
      db.Vehicle.Add(vehicle);
      db.SaveChanges();
      return RedirectToAction("Index");
   }
   var vehicleViewModel = new VehicleViewModel(vehicle);
   return View(vehicleViewModel);
}

And the view has

@model Automobile.ViewModels.VehicleViewModel
…snip…
<div class="editor-label">
   @Html.LabelFor(model => model.Vehicle.TyreTypeID, "Tyre Type")
</div>
<div class="editor-field">
   @Html.DropDownListFor(model => model.Vehicle.TyreTypeID, Model.TyreTypes, "--Select a tyre type--" )
   @Html.ValidationMessageFor(model => model.Vehicle.TyreTypeID)
</div>

@Html.DropDownListFor(model => model.Vehicle.TyreTypeID, Model.TyreTypes, "--Select a tyre type--" ) is doing three things –

  1. model => model.Vehicle.TyreTypeID – specifies where the selected value from the DDL is stored
  2. Model.TyreTypes – is the source of SelectListItems for the DDL
  3. "--Select a tyre type--" – is a default value

This is the approach I prefer. Full source code is a attached here.

Leave a Reply

Your email address will not be published. Required fields are marked *