This article will learn how to create a cascading dropdown list in blazor in simple steps.

Complete Source Code on Github

If you are new to Blazor then you must see

Creating Application in simple steps

  1. Open Visual Studio 2019
  2. In the search bar of Visual Studio, we are going to enter text blazor to search. The first result which appears is Blazor App we are going to choose it.
  3. Next step, we will configure the project name, the location where we want to store this project, and set the solution name.
  4. Next, we are going to get 2 hosting models options from that. We are going to choose Blazor Server
  5. Application is Created.

After creating the Application, First, Let’s understand what we are going to develop.

Cascading Dropdown means a set of DropDownLists dependent on the parent or previous DropDownList for Binding Values.

The Value of one DropDownList is passed to another DropDownList to bind Data.

In this part, we are going to add 3 dropdowns

  • Countries
  • States
  • Cities

In which State is dependent on Country and Cities are dependent on State.

The data we are going to get from the database is shown below.

Tables which we are going to Use

After we saw the Tables, now let’s first start with adding Models to Project.

Adding Model folder to project and Adding Countries, States, Cities Models in it

Countries

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlazorPlayGround1.Model
{
    [Table("Countries")]
    public class Countries
    {
        [Key]
        public int CountryId { get; set; }
        [MaxLength(150)]
        public string Name { get; set; }
    }
}

States

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlazorPlayGround1.Model
{
    [Table("States")]
    public class States
    {
        [Key]
        public int StateId { get; set; }
        [MaxLength(100)]
        public string Name { get; set; }
        public int CountryId { get; set; }
    }
}

Cities

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlazorPlayGround1.Model
{
    [Table("Cities")]
    public class Cities
    {
        [Key]
        public int CitiesId { get; set; }
        [MaxLength(100)]
        public string Name { get; set; }
        public int StateId { get; set; }
    }
}

Adding Entity Framework Core package from NuGet

This part will learn how to add the Entity Framework Core package to the project from the NuGet package manager.

To add a package from NuGet, right-click on the Main project “BlazorPlayGround1” and select Manage NuGet Packages from the menu. As you choose it, a new dialogue of NuGet Package Manager with a search box will pop up.

In choose browse tab and search “Microsoft.EntityFrameworkCore.SqlServer” and choose version 5.0.8. the latest version of “Microsoft.EntityFrameworkCore.SqlServer” finally clicks on the install button to add the package.

After adding Microsoft.EntityFrameworkCore.SqlServer to project next, we are going to Configuring Connection string in appsettings.json.

Configuring Connection string in appsettings.json

In this appsettings.json file, we have added the ConnectionStrings section inside that we have added the “DatabaseConnection” key, and the other is value.

Notice:- here, I am using SQL based authentication for database connection that why I have added the User Id and password of the SQL server.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
    "DatabaseConnection": "Data Source=DESK\\MSSQLSERVERSTD; initial catalog=ReSearch; user id=sa; password=$$$$$$$$"
  },
  "AllowedHosts": "*"
}

Adding Repository Folder

After adding Repository Folder next, we are going to add DatabaseContext class.

Adding BlazorPlayGroundContext Class and inheriting with DbContext class

We will add BlazorPlayGroundContext class which will inherit DbContext class, and the BlazorPlayGroundContext constructor accepts the DbContextOptions as an argument. The DbContextOptions carries the configuration information needed to configure the DbContext.

public class BlazorPlayGroundContext : DbContext
{
     public BlazorPlayGroundContext(DbContextOptions<BlazorPlayGroundContext> options) : base(options)
     {
     }
}

After adding BlazorPlayGroundContext class, we have defined the DbSet entity for the Countries, States, Cities Model.

using Microsoft.EntityFrameworkCore;
namespace BlazorPlayGround1.Repository
{
    public class BlazorPlayGroundContext : DbContext
    {
        public BlazorPlayGroundContext(DbContextOptions<BlazorPlayGroundContext> options) : base(options)
        {
        }
        public DbSet<Model.Countries> Countries { get; set; }
        public DbSet<Model.States> States { get; set; }
        public DbSet<Model.Cities> Cities { get; set; }
    }
}

After Configuring BlazorPlayGroundContext class, next, we are going to Register BlazorPlayGroundContext as a service.

Register BlazorPlayGroundContext as a service

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();

    var connection = Configuration.GetConnectionString("DatabaseConnection");
    services.AddDbContext<BlazorPlayGroundContext>(options => options.UseSqlServer(connection));
}

Now we can use BlazorPlayGroundContext class as a service for dependency injection in the entire Application.

Next, we are going to add Interface and Concrete class.

Adding IDropdownService Interface

Here we will add Interface IDropdownService and declare 3 methods for getting countries, states and cities

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace BlazorPlayGround1.Repository
{
    public interface IDropdownService
    {
        List<SelectListItem> ListofCountries();
        List<SelectListItem> ListofStates(int countryId);
        List<SelectListItem> ListofCities(int stateid);
    }
}

Adding DropdownService Class and Implement IDropdownService Interface

In this part, we will add DropdownService Class which will implement the IDropdownService interface, which has 3 methods.

Here we have injected BlazorPlayGroundContext dependency via Constructor injection. The IOC container will resolve BlazorPlayGroundContext dependency at runtime automatically.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;

namespace BlazorPlayGround1.Repository
{
    public class DropdownService: IDropdownService
    {
        private readonly BlazorPlayGroundContext _blazorPlayGroundContext;
        public DropdownService(BlazorPlayGroundContext blazorPlayGroundContext)
        {
            _blazorPlayGroundContext = blazorPlayGroundContext;
        }

        public List<SelectListItem> ListofCountries()
        {
            try
            {
                var listofCountries = (from countries in _blazorPlayGroundContext.Countries.AsNoTracking()
                        select new SelectListItem()
                        {
                            Text = countries.Name,
                            Value = countries.CountryId.ToString()
                        }
                    ).ToList();

                listofCountries.Insert(0, new SelectListItem()
                {
                    Value = "",
                    Text = "---Select---"
                });

                return listofCountries;
            }
            catch (Exception)
            {
                throw;
            }
        }

        public List<SelectListItem> ListofStates(int countryId)
        {
            try
            {
                var listofstates = (from states in _blazorPlayGroundContext.States.AsNoTracking()
                        where states.CountryId == countryId
                        select new SelectListItem()
                        {
                            Text = states.Name,
                            Value = states.StateId.ToString()
                        }
                    ).ToList();
                listofstates.Insert(0, new SelectListItem()
                {
                    Value = "",
                    Text = "---Select---"
                });
                return listofstates;
            }
            catch (Exception)
            {
                throw;
            }
        }

        public List<SelectListItem> ListofCities(int stateid)
        {
            try
            {
                var listofCities = (from cities in _blazorPlayGroundContext.Cities.AsNoTracking()
                        where cities.StateId == stateid
                        select new SelectListItem()
                        {
                            Text = cities.Name,
                            Value = cities.CitiesId.ToString()
                        }
                    ).ToList();

                listofCities.Insert(0, new SelectListItem()
                {
                    Value = "",
                    Text = "---Select---"
                });
                return listofCities;
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

Registering Service in ConfigureServices method in Startup class

Here we are Registering IDropdownService Service as a Transient.

public void ConfigureServices(IServiceCollection services)
 {
     services.AddRazorPages();
     services.AddServerSideBlazor();
     services.AddSingleton<WeatherForecastService>();

     var connection = Configuration.GetConnectionString("DatabaseConnection");
     services.AddDbContext<BlazorPlayGroundContext>(options => options.UseSqlServer(connection));

     services.AddTransient<IDropdownService, DropdownService>();
 }

Now Service can be used for dependency injection.

Next, let’s we will adding ViewModel.

Adding ViewModel

We are going to add the ViewModel folder, and inside it, we will add ViewModel with the name CascadingDropdownViewModel.

This ViewModel we are going to use for a component page which we will add in the upcoming part.

In CascadingDropdownViewModel, we have added 3 strings to get the selected value and 3 collections for binding Dropdown with data.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace BlazorPlayGround1.ViewModel
{
    public class CascadingDropdownViewModel
    {
        [Required(ErrorMessage = "Country is Required")]
        public string CountryId { get; set; }
        public List<SelectListItem> ListofCountries { get; set; }

        [Required(ErrorMessage = "State is Required")]
        public string StateId { get; set; }
        public List<SelectListItem> ListofState { get; set; }

        [Required(ErrorMessage = "City is Required")]
        public string CityId { get; set; }
        public List<SelectListItem> ListofCity { get; set; }
    }
}

Next, the step we are going to add Component.

Adding Component

We are going to add a Component with the name CascadingDropdownComponent.razor

Adding Base Class for CascadingDropdownComponentBase Component.

We will add a class with the name CascadingDropdownComponentBase, and then we will inherit it with ComponentBase.

Next, we are going to add the CascadingDropdownViewModel property in the CascadingDropdownComponentBase component.

using BlazorPlayGround1.ViewModel;
using Microsoft.AspNetCore.Components;

namespace BlazorPlayGround1.Pages
{
    public class CascadingDropdownComponentBase : ComponentBase
    {
        protected   CascadingDropdownViewModel CascadingVM { get; set; } = newCascadingDropdownViewModel();
    }
}

After adding a property, next, we are going to Injecting the dependencies of IDropdownService.

Injecting dependencies in Component

In CascadingDropdownComponentBase, we have to get data of countries, State and cities to bind. For that, we have to inject IDropdownService Service.

For injecting dependencies in Component, we are going to decorate Service with the [Inject] attribute.

[Inject] private IDropdownService DropdownService { get; set; }
using BlazorPlayGround1.Repository;
using BlazorPlayGround1.ViewModel;
using Microsoft.AspNetCore.Components;

namespace BlazorPlayGround1.Pages
{
    public class CascadingDropdownComponentBase : ComponentBase
    {
        [Inject] public IDropdownService DropdownService { get; set; }
        protected CascadingDropdownViewModel CascadingVM { get; set; } = new CascadingDropdownViewModel();
    }
}

Next step, we are going to initialize Dropdown inside the OnInitialized method.

Initializing Dropdown List on OnInitialized Method

If you come from webforms background, you must have seen lifecycle events of the page such as onload event similar we have OnInitialized event which is invoked when the Component is initialized.

Here we have initialized ListofCountries, ListofState, ListofCity

 protected override void OnInitialized()
 {
            CascadingVM.ListofCountries = DropdownService.ListofCountries();
            CascadingVM.CountryId = "";
            CascadingVM.ListofState = new List<SelectListItem>()
            {
                new SelectListItem()
                {
                    Text = "Select",
                    Value = ""
                }
            };
            CascadingVM.StateId = "";

            CascadingVM.ListofCity = new List<SelectListItem>()
            {
                new SelectListItem()
                {
                    Text = "Select",
                    Value = ""
                }
            };
  }

Now we have initialized Dropdown, but there are not dependent on each other. For handling that event, we are going to add 3 methods.

OnCountryChange(string value)  

On OnCountryChange event will get called on change on country, and it will get countryId using which we will bind states.

protected void OnCountryChange(string value)
{

    if (value != null)
    {
        CascadingVM.CountryId = value.ToString();
        CascadingVM.StateId = "";
        CascadingVM.CityId = "";
        CascadingVM.ListofCity = new List<SelectListItem>()
        {
            new SelectListItem()
            {
                Text = "Select",
                Value = ""
            }
        };

        CascadingVM.ListofState = DropdownService.ListofStates(Convert.ToInt32(CascadingVM.CountryId));
        this.StateHasChanged();
    }
}

OnStateChange(string value)

On OnStateChange event will get called on change on State, and it will get stateId using which we are going to bind cities.

protected void OnStateChange(string value)
{
    if (value != null)
    {
        CascadingVM.CityId = "";
        CascadingVM.StateId = value.ToString();
        CascadingVM.ListofCity = DropdownService.ListofCities(Convert.ToInt32(CascadingVM.StateId));
    }
}

OnCityChange(string value)          

OnCityChange event can be used for dependency if someone has to decide on the last dropdown value.

 protected void OnCityChange(string value)
 {
     if (value != null)
     {
         CascadingVM.CityId = value.ToString();
     }
 }

Next, we will add the Event Callback method FormSubmitted which will be invoked when a form is successfully submitted without any validation error.

Adding Event Call Method FormSubmitted

This method will get called when the form is submitted without any validation error.

Here we get all values of the selected dropdown list, which we can store in the database.

protected async void FormSubmitted()
{
    var selectedCountry = CascadingVM.CountryId;
    var selectedState = CascadingVM.StateId;
    var selectCity = CascadingVM.CityId;
}

Next, we are going to work on CascadingDropdownComponent.razor page, which we have added earlier.

CascadingDropdownComponent.razor page

  1. Going to add the @page Razor directive and specify the route for Component (CascadingDropdown)
  2. Adding @inherits directive and set base class for Component (CascadingDropdownComponentBase).
  3. Adding EditForm Component Which Renders a form element and setting Model (Model=”@CascadingVM”) and OnValidSubmit  (OnValidSubmit=”@FormSubmitted”) Properties of it.

Adding country InputSelect

Adding InputSelect for Countries and setting ValueChanged Call as “OnCountryChange“, ValueExpression as CountryId, Value as CountryId, which are used for 2-way data binding and using the foreach loop, we are going to add Options to InputSelect.

<InputSelect id="Countries" class="form-control"
             ValueExpression="@(() => CascadingVM.CountryId)"
             Value="@CascadingVM.CountryId"
             ValueChanged="@((string value) => OnCountryChange(value))">

    @foreach (var state in CascadingVM.ListofCountries)
    {
        <option value="@state.Value">@state.Text</option>
    }
</InputSelect>

Adding State InputSelect

Adding InputSelect for States and setting ValueChanged Call as “OnStateChange“, ValueExpression as StateId, Value as StateId, which are used for 2-way data binding and using the foreach loop, we are going to add Options to InputSelect.

<InputSelect id="State" class="form-control"
             ValueExpression="@(() => CascadingVM.StateId)"
             Value="@CascadingVM.StateId"
             ValueChanged="@((string value) => OnStateChange(value))">

    @if (CascadingVM.ListofState != null)
    {
        @foreach (var state in CascadingVM.ListofState)
        {
            <option value="@state.Value">@state.Text</option>
        }
    }
</InputSelect>

Adding City InputSelect

Adding InputSelect for Cities and setting ValueChanged Call as “OnCityChange“, ValueExpression as CityId, Value as CityId, which are used for 2-way data binding and using the foreach loop, we are going to add Options to InputSelect.

<InputSelect id="City" class="form-control"
               ValueExpression="@(() => CascadingVM.CityId)"
               Value="@CascadingVM.CityId"
               ValueChanged="@((string value) => OnCityChange(value))">

      @if (CascadingVM.ListofCity != null)
      {
          @foreach (var city in CascadingVM.ListofCity)
          {
              <option value="@city.Value">@city.Text</option>
          }
      }
</InputSelect>

Completed Source code of CascadingDropdownComponent.razor page


@page "/CascadingDropdown"
@inherits CascadingDropdownComponentBase
<div class="col-md-12">
    <EditForm Model="@CascadingVM" OnValidSubmit="@FormSubmitted">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title">Cascading Dropdown</h5>
            </div>
            <div class="card-body">
                <div class="form-row">
                    <div class="form-group col-md-4">
                        <label for="Countries">Countries</label>
                        <InputSelect id="Countries" class="form-control"
                                     ValueExpression="@(() => CascadingVM.CountryId)"
                                     Value="@CascadingVM.CountryId"
                                     ValueChanged="@((string value) => OnCountryChange(value))">

                            @foreach (var state in CascadingVM.ListofCountries)
                            {
                                <option value="@state.Value">@state.Text</option>
                            }
                        </InputSelect>
                        <ValidationMessage For="@(() => CascadingVM.CountryId)" />
                    </div>
                    <div class="form-group col-md-4">
                        <label for="State">State</label>
                        <InputSelect id="State" class="form-control"
                                     ValueExpression="@(() => CascadingVM.StateId)"
                                     Value="@CascadingVM.StateId"
                                     ValueChanged="@((string value) => OnStateChange(value))">

                            @if (CascadingVM.ListofState != null)
                            {
                                @foreach (var state in CascadingVM.ListofState)
                                {
                                    <option value="@state.Value">@state.Text</option>
                                }
                            }
                        </InputSelect>
                        <ValidationMessage For="@(() => CascadingVM.StateId)" />

                    </div>
                    <div class="form-group col-md-4">
                        <label for="City">City</label>
                        <InputSelect id="City" class="form-control"
                                     ValueExpression="@(() => CascadingVM.CityId)"
                                     Value="@CascadingVM.CityId"
                                     ValueChanged="@((string value) => OnCityChange(value))">

                            @if (CascadingVM.ListofCity != null)
                            {
                                @foreach (var city in CascadingVM.ListofCity)
                                {
                                    <option value="@city.Value">@city.Text</option>
                                }
                            }
                        </InputSelect>
                        <ValidationMessage For="@(() => CascadingVM.CityId)" />

                    </div>
                </div>
            </div>
        </div>
    </EditForm>
</div>

In the Next Step, Let Press F5 to run the project, and you will see the output.

By Saineshwar Bageri

I am Microsoft MVP | C# Corner MVP | Code Project MVP | FULL STACK .NET Developer and working on .Net Web Technology (Asp.net, Asp.net Core,.Net Core, C#, Sqlserver, MVC, Windows, Console Application, javascript, jquery, json, ORM Dapper) and also a freelance developer.