ASP.NET MVC Cascading DropDownLists

Creating DropDownLists using WebForms is easy enough, for those who are venturing into the the world of MVC this may not be as straight forward.

I’ll be using the classic scenario of Cars and their related makes and models.

CarController.cs

public class CarFormViewModel
{
MakeModelRepository makeModelRepository =
    new MakeModelRepository();

private string _carMake = "";

public Car Car { get; private set; }
public SelectList CarMakes { get; private set; }
public SelectList CarModels { get; private set; }
public string CarMake
{
get { return _carMake; }
set { _carMake = value; }
}

// Constructor
public CarFormViewModel(Car car)
{
Car = car;
CarMakes = new SelectList(makeModelRepository.
        GetCarMakes(), "Value", "Text");

if (car.CarMakeModelId > 0)
CarMake = car.CarMakeModel.CarMake;

CarModels = new SelectList(makeModelRepository.
        GetCarModels(CarMake), "Value", "Text", Car.CarMakeModelId);
}
}

Something to be aware of is the use of valueSelected when defining your SelectList, make sure your view items e.g. DropDownList names match your database field names. I found the selected value was not being set correctly when editing a records.

CarForm.aspx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl
<Vehicles.Controllers.CarFormViewModel>" %>

<script type="text/javascript">
$(function() {
$("#CarMake").change(function() {
var carMake = $("#CarMake > option:selected").attr("value");
var urlAction = "<%= Url.Action("FindCarModels", "Car") %>";
$.getJSON(urlAction, { carMake: carMake }, function(data) {
$("#CarMakeModelId").addItems(data);
});
});
});
</script>

<%= Html.ValidationSummary("Edit was unsuccessful. Please correct
the errors and try again.") %>
<%
using (Html.BeginForm())
{%>
<fieldset>
<
legend>Car</legend>
<
p>
<
label for="CarMake">
Make:</label>
<%= Html.DropDownList("CarMake", Model.CarMakes) %>
<%= Html.ValidationMessage("CarMake", "*") %>
</p>
<
p>
<
label for="CarMakeModelId">
Model:</label>
<%= Html.DropDownList("CarMakeModelId", Model.CarModels) %>
<%= Html.ValidationMessage("CarMakeModelId", "*") %>
</p>
<
p>
<
input type="submit" value="Save" />
</
p>
</
fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>

Make sure to change the inherits property to reference your new ViewModel. Note the use of the Url.Action helper, this little snippet helps you creates the correct path to your controller action without the use of those dots e.g. …/<controller>/<action>/

jHelper.js

Borrowed from Steve Michelotti.

$.fn.clearSelect = function() {
return this.each(function() {
if (this.tagName == 'SELECT')
this.options.length = 0;
});
}

$.fn.addItems = function(data) {
return this.clearSelect().each(function() {
if (this.tagName == 'SELECT') {
var dropdownList = this;
$.each(data, function(index, optionData) {
var option = new Option(optionData.Text,
                         optionData.Value);

if ($.browser.msie) {
dropdownList.add(option);
}
else {
dropdownList.add(option, null);
}
});
}
});
}

Little helper that clears and adds items to DropDownLists.

Something I found frustrating is when in debug mode a script reference would work find but when running using IIS the script would not load correctly. What you had to do was use those good old dots to get it loading correctly. My tip is to use the ResovleUrl helper.

<script src="<%= ResolveUrl("~/Scripts/jquery-1.3.1.js") %>" 
type="text/javascript"></script>

<
script src="<%= ResolveUrl("~/Scripts/jHelper.js") %>"
type="text/javascript"></script>

There are some workarounds I’ve seen that don’t break intellisense but look a bit messy. I figure I’ve lived without it for this long.

Here is the source code, if there is anything that is unclear or you have any questions feel free to email me.