Introduction
A short article that describes how to wire up DropDownLists in MVC Razor pages to dynamically update and also populate the page with PartialViews.
Background
Being new to MVC and Web Development, I struggled to get my head around JavaScript talking to my Controller Actions. After having scoured the web and asked multiple inane questions, this is a simple solution that I have cobbled together and found useful.
If you don't know about the MVC pattern and/or Entity Framework then this may not be the article for you as I will not go in depth explaining how to wire these up.
Using the code
A couple of things are required here. I am using the Entity Framework - Model First approach, so we need to set up a simple database.
This could be done with static models but this is just as trivial. A script is provided, but the schema is simple:
Create Table ProductType(
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
Name VarChar(50) UNIQUE NOT NULL
)
Create Table ProductCategory(
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
ProductTypeId INT NOT NULL FOREIGN KEY REFERENCES ProductType(Id),
Name VARCHAR(50) NOT NULL ,
)
Create Table Product(
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
ProductCategoryId INT NOT NULL FOREIGN KEY REFERENCES ProductType(Id),
Name VARCHAR(50) NOT NULL,
Description (200) NOT NULL,
Price Decimal(12,4) NOT NULL
)
We then Add a new ADO.NET Entity Data Model Item to our Models folder. I have called this ProductDataEntities
.
Your Solution should look similar to this:
Notice that Visual Studio created a raft of folders to organize your project. The ones we will be interested in are:
- Controllers
- Models
- Views
Let's look at the Controller:
First off we need an Action to display our page. In this case we are going to get a list of ProductType
objects and pass a reference to the Entity Model we created earlier to our .cshtm page:
public ActionResult Index(){
var productTypes = db.ProductTypes.OrderBy(p=>p.Name);
ViewBag.ProductTypes = productTypes;
return View();
}
We also require a JsonResult
to pass the ProductCategory
data back to the second DropDownList
:
[AllowVerbs(HtmlVerbs.Get)]
public JsonResult GetCategoryList(string ptypeid){
if (string.IsNullOrEmpty*ptypeid)
return Json(HttpNotFound());
var categoryList = GetCategoryList(Convert.ToInt32(Ptypeid));
var categoryData = categoryList.Select(m => new SelectListItem(){
Text = m.Name,
Value = m.Id.ToString()
});
return Json(categoryData, JsonRequestBehavior.AllowGet);
}
We also need a small helper method to get a List of objects for the categoryData
argument in the JsonResult
:
private IList<ProductCategory> GetCategoryList(int ptypeid){
return db.ProductCategory.OrderBy(c=>c.Name).Where(c=>c.ProductTypeId == ptypeid).ToList();
}
And last but not least an ActionResult
that will render our PartialView
:
public ActionResult GetProducts(string Id)
{
int i = Convert.ToInt32(Id)
var products = db.Products.OrderBy(p=>p.Name).Where(p=>p.ProductCategoryId == id);
return PartialView(products);
}
Now that we have completed the C# side of things (yes thi
s really all the code that is required) we can move on to the View:
The View:
Our Index
View simply contains a couple of DropDownList
objects and an empty div
element named ddlproducts
.
@Model ProductData.Models.ProductDataEntities
...
<div> <h4> Product Types </h4>
<p>@Html.DropDownListFor(model => model.ProductTypes,
new SelectList(@ViewBag.ProductTypes, "Id", "Name"),
"--select a Product Type--",
new {
id = "ddlProductTypes"
})</p>
<h4>Product Category</h4>
<p>@Html.DropDownList("Id", new SelectList(
Enumerable.Empty<SelectListItem>(), "Id", "Name"),
"-- select a Product Category --",
new
{
id = "ddlProductCategorys",
data_url = Url.Action("GetProducts", "Home")
})</p>
</div>
<div id="ddlproducts"></div>
Notice the data_url = Url.Action("GetProducts", "Home")
argument in the second DropDownList
. This tells our script what method to call on the change
event. The GetProducts
Action returns a PartialView
that is loaded into our empty div
element.
The PartialView
List our returned Product objects:
@model IEnumerable<CascadingDDL_MVC.Models.Product>
@Html.ActionLink("Create New", "Create")
<table><tr
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Description)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th><
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
</table>
Now all this C# and CSHTML is well and good, but at this stage, our sample doesn't do much. Sure, the ProductType
DropDownList will be populated with data, but when you select an item, nothing happens. This is where the JavaScript Voodoo comes in.
JavaScript
Disclaimer - I find JavaScript frustrating. It is hard to read and often looks like the output from so many monkeys. It is however, a very useful tool for manipulating the DOM and therefore I urge you to learn up on it. Thankfully, our script is very straight-forward and should be relatively easy to follow. That said, JavaScript still hurts my eyes!
Be aware that attention to spelling is VERY important. Do yourself a favor and cut and paste your arguments.
At the very bottom of the Index.cshtml file...
<script type="text/javascript"
src="../../Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
$(function () {
$("#ddlProductTypes").change(function () {
var typeid = $(this).val();
$('#products').html("");
$.getJSON("../Home/LoadCategorys", { typeid: typeid },
function (categoryData) { var select = $("#ddlProductCategorys");
select.empty();
select.append($('<option/>', {
value: 0,
text: "-- select a category --"
$.each(categoryData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text
}));
});
});
});
$(function () { $('#ddlProductCategorys').change(function () {
var url = $(this).data('url');
var value = $(this).val();
$('#products').load(url, { id: value });
});
});
});
</script>
The script waits until a change event
$("#ddlProductTypes").change(function () {
which is fired when we select an item in the ProductType
(ddlProductType
) DropDownList. This in turn calls our JsonAction
method, LoadCategorys
which returns the list of ProductCategory
data (categoryData
) to be loaded into the second DropDownList (ddlProductCategorys
).
Next we use another Action method which we assigned to the DropDownList's data attribute
data_url = Url.Action("GetProducts", "Home")
by assigning the result to the <div id="products>
element's load
arguments.
$('#products').load(url, { id: value });
This calls the GetProducts
ActionMethod in the Home
Controller which returns the markup to be loaded.
Simples.