Blazor: Cascading DropDownList – Numeric InputSelect

In this post we will see how to implement a Cascading DropDownList in Blazor. When we talk about a Cascading DropDownList (or dependent DropDownList), we mean when the options of a DropDownList are filtered by another DropDownList.

For example, if we have the Country and State DropDownLists, then the States to be displayed will be filtered according to the selected Country.

In this video I show how to implement this step by step:

In this post I’ll summarize the content of the video:

Note: This code was done with ASP.NET Core 3.1.

Repository: https://github.com/gavilanch/BlazorCascadingDropDownList

For our example, what we did was to create the Country and State entities, and we added a StateId property in a Person class, because we want to cascade the DropDown in this entity:

public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public List<State> States { get; set; }
}
public class State
{
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
public class Person
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Biography { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "You must select a state")]
public int StateId { get; set; }
public State State { get; set; }
}

view raw
entities.cs
hosted with ❤ by GitHub

Then, I create the tables corresponding to these entities in my database. We can also use Data Seeding to fill these entities with test data.

The next step is to place the DropDownLists in a form. However, at the time of writing this entry, the InputSelect component of Blazor does not support ints (integers) as a value, therefore, we have to create a component that does this. We can create it as a class, since we are only going to extend the InputSelect functionality to handle ints (I think this code was written by Steve Sanderson):

public class InputSelectNumber<T> : InputSelect<T>
{
protected override bool TryParseValueFromString(string value, out T result, out string validationErrorMessage)
{
if (typeof(T) == typeof(int))
{
if (int.TryParse(value, out var resultInt))
{
result = (T)(object)resultInt;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage = "The chosen value is not a valid number.";
return false;
}
}
else
{
return base.TryParseValueFromString(value, out result, out validationErrorMessage);
}
}
}

view raw
InputSelectNumber.cs
hosted with ❤ by GitHub

With this we can use the InputSelect with numbers. In this case we use “countryId” as a component field, because our EditForm model does not bring a Country property (we only put Status in the Person class). We also use ValueChanged to execute a method when the value of the Country DropDownList is changed. This way we will execute the State filter:

<div class="form-group">
<label>Country:</label>
<div>
<InputSelectNumber class="form-control"
ValueChanged="@((int value) => CountryHasChanged(value))"
ValueExpression="@(() => countryId)"
Value="@countryId">
<option value="0">–Select a country–</option>
@foreach (var item in Countries)
{
@if (item.Id == countryId)
{
<option selected value="@item.Id">@item.Name</option>
}
else
{
<option value="@item.Id">@item.Name</option>
}
}
</InputSelectNumber>
</div>
</div>

view raw
PersonForm.razor
hosted with ❤ by GitHub

In the case of the State DropDownList, we do almost the same, except that since we do not need to use ValueChanged, we can simplify the code:

<div class="form-group">
<label>State:</label>
<div>
<InputSelectNumber class="form-control"
@bind-Value="Person.StateId"
>
<option value="0">–Select a state–</option>
@foreach (var item in States)
{
@if (item.Id == Person.StateId)
{
<option selected value="@item.Id">@item.Name</option>
}
else
{
<option value="@item.Id">@item.Name</option>
}
}
</InputSelectNumber>
<ValidationMessage For="@(() => Person.StateId)" />
</div>
</div>

view raw
PersonForm.razor
hosted with ❤ by GitHub

In the C # code of the component we implement the CountryHasChanged method, where we call the LoadStates method, which is responsible for carrying out the filter:

@code {
[Parameter] public Person Person { get; set; }
[Parameter] public string ButtonText { get; set; } = "Save Person";
[Parameter] public EventCallback OnValidSubmit { get; set; }
private List<Country> Countries = new List<Country>();
private List<State> States = new List<State>();
private int countryId = 0;
protected override async Task OnInitializedAsync()
{
if (Person.State != null)
{
countryId = Person.State.CountryId;
await LoadStates(countryId);
}
Countries = await http.GetJsonAsync<List<Country>>("api/countries");
}
private async Task CountryHasChanged(int value)
{
Person.StateId = 0;
countryId = value;
if (value == 0)
{
States.Clear();
}
else
{
await LoadStates(value);
}
}
private async Task LoadStates(int countryId)
{
States = await http.GetJsonAsync<List<State>>($"api/countries/{countryId}/states");
}
}

view raw
PersonForm.razor
hosted with ❤ by GitHub

Finally, in the country controller we look for the list of countries and the states filtered by country:

[ApiController]
[Route("api/[controller]")]
public class CountriesController: ControllerBase
{
private readonly ApplicationDbContext context;
public CountriesController(ApplicationDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<ActionResult<List<Country>>> Get()
{
return await context.Countries.OrderBy(x => x.Name).ToListAsync();
}
[HttpGet("{countryId}/states")]
public async Task<List<State>> GetStates(int countryId)
{
return await context.States.Where(x => x.CountryId == countryId)
.OrderBy(x => x.Name).ToListAsync();
}
}

view raw
PersonForm.razor
hosted with ❤ by GitHub

These are the steps to implement a cascade DropDownList.

Summary

  • With cascading DropDownLists we can filter the values of a DropDownList according to the values of another DropDownList
  • We use the ValueChanged attribute to execute a functionality when the value of a component changes
  • We create the InputSelectNumber component to make the InputSelect component handle integers.

Course

If you want to learn step by step how to build Web Applications from Scratch using Blazor, check out my full course: https://www.udemy.com/course/programming-in-blazor-aspnet-core/?referralCode=8EFA9D9FF38E3065DF0C

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s