Question MudBlazor simple syntax various errors in Cascade Dropdownlists

Pablo

Well-known member
Joined
Aug 12, 2021
Messages
62
Programming Experience
10+
Hello all,
I'm trying to move to MudBlazor starting by the page with the Cascade Dropdownlists, which before worked perfectly, but I know I have to upgrade to MudBlazor.
Here's a screenshot with the errors:
The error message says: Argument 2: Cannot convert from method group to Microsoft.AspNetCore.Components.EventCallback., and it highlights the method "@CountryHasChanged" of the ValueChanged. I can't figure why it just doesn't work like @onchange="CountryHasChanged" from simple pure Blazor. It also mark as an error this:
<MudSelectItem Value="">Please select a country ...</MudSelectItem> ... it doesn't like the empty string as a Value.
And here's the code:
C#:
@page "/"
@using MudBlazorClients.Models
@using MudBlazorClients.Services

<PageTitle>Index</PageTitle>

<h1>PLEASE SELECT A CLIENT</h1>

<div style="background-color:green">
    <label>COUNTRY:</label>
    <br />
    <MudSelect T="string" ValueChanged="@CountryHasChanged" Variant="Variant.Outlined">
        <MudSelectItem Value="">Please select a country ...</MudSelectItem>
        @foreach (var country in Countries)
        {
            <MudSelectItem Value="@country.CountryCode">@country.Name</MudSelectItem>
        }
    </MudSelect>
</div>
<div style="background-color:orange">
    <label>CITY:</label>
    <br />
    <MudSelect T="int" ValueChanged="@CityHasChanged">
        <MudSelectItem Value="0">Please select a city ...</MudSelectItem>
        @foreach (var city in Cities)
        {
            <MudSelectItem Value="@city.Id">@city.Name</MudSelectItem>           
        }
    </MudSelect>
</div>
<div style="background-color:brown">
    <label>CLIENT:</label>
    <br />
    <MudSelect T="int" ValueChanged="@ClientHasChanged">
        <MudSelectItem Value="0">Please select a client ...</MudSelectItem>
        @foreach (var client in Clients)
        {
            <MudSelectItem Value="@client.Id">@client.Name</MudSelectItem>
        }
    </MudSelect>
</div>
<div style="background-color:lightblue">
    <label>SELECTED CLIENT:</label>
    <br />
    <MudTextField Value="@client?.Name" ></MudTextField>
</div>

@code {
    private Client? client;

    private List<Country>? Countries;
    private List<City>? Cities;
    private List<Client>? Clients;

    CountryService countryService = new CountryService();
    CityService cityService = new CityService();
    ClientService clientService = new ClientService();

    protected override void OnInitialized()
    {
        Countries = countryService.GetAllCountries();
        Cities = new List<City>();
        Clients = new List<Client>();
    }

    private void CountryHasChanged(ChangeEventArgs e)
    {
        if (string.IsNullOrEmpty(e.Value.ToString()))
        {
            Cities.Clear();
        }
        else
        {
            LoadCities(e.Value.ToString());
        }
        Clients.Clear();
    }

    private void LoadCities(string countryCode)
    {
        Cities = cityService.GetCitiesByCountry(countryCode);
    }

    private void CityHasChanged(ChangeEventArgs e)
    {
        if (Convert.ToInt32(e.Value.ToString()) == 0)
        {
            Clients.Clear();
        }
        else
        {
            LoadClients(Convert.ToInt32(e.Value.ToString()));
        }
    }

    private void LoadClients(int cityId)
    {
        Clients = clientService.GetClientsByCity(cityId);
    }

    private void ClientHasChanged(ChangeEventArgs e)
    {
        if (Convert.ToInt32(e.Value.ToString()) > 0)
        {
            client = clientService.GetClientById(Convert.ToInt32(e.Value.ToString()));
        }
    }
}

I will highly appreciate your help to continue my web dev learning. Thank you.
Sorry for so silly questions, web dev is a new world for me.
Pablo
 

Attachments

  • index.png
    index.png
    110.2 KB · Views: 50
What happens when you try:
C#:
<MudSelect T="string" ValueChanged="CountryHasChanged" Variant="Variant.Outlined">
 
@bind-Value to Country propertry. Have Cities be delivered by a property that uses the Country property as a filter (you're thinking about things in a bit of a "windows forms before binding was invented" kind of way. Blazor is more bind orientated; you bind controls to properties and change the properties, rather than put controls and listen to events and push data round when the events occur )

I'm not at a PC to check but i tjink the root problem with your current code is if you've T="string" your MudSelect then you can't pass a method that takes a ChangeEventArgs for ValueChanged. ValueChanged should accept a string.
If using ValueChanged you should also set Value=
 
Last edited:
Hello @Skydiver,
The same happens, it underlines the CountryHasChanged and throws the same error message.

Hello @cjard,
I did as you say the bind-Value, but it still gets underlined, and I wouldn't know when the value changed if I remove the ValueChanged event, or I would? I would be grateful if you or Skydiver could give me an example MudSelect that works with my circumstances. The examples in the documentation doesn't use DB binding. With help from expert people, I will be able to learn.
I attach the not working bind-Value in the MudSelect header.
Thank you.
Pablo
 

Attachments

  • bl.png
    bl.png
    39.9 KB · Views: 28
I did as you say the bind-Value, but it still gets underlined
When using @bind-Value you do not use Value= or ValueChanged=

using @bind means C# generates these for you

I wouldn't know when the value changed if I remove the ValueChanged event, or I would?
You would (the "set" of the property is called) but you don't need to; if the property that returns cities uses the set value in its filter then the filtering is done on get, not on set like you do here

The examples in the documentation doesn't use DB binding
Not database; binding is rarely if ever directly to a database. Binding in this sense is in c# memory
 
I meant binding to a List<T> in memory, loaded from a DB.
Can you recommend me a web site from where I can study MudBlazor well? I searched the web but there is none that includes a MudSelect in the CRUD. And the documentation doesn't include binding from a List<T>, that is what I need.
Or if you can give me the code of the MudSelect tag I would appreciate it very much, too. I know this Forum is not a code writting service, but it is just a line of code, and I have presented all my attempts here.
 
Hi again,
I have a new issue ... the Index.razor doesn't recognize the MudSelect tag, neither the PageTitle, as you can see in the image. I run the app and for the "/" page there's a letter that says "Sorry, there is nothing at this address". Maybe it would be I'm missing a namespace or is it another problem? Sorry for coming here with so silly questions, the matter is I'm a begginer in web dev, and I suppose you passed through these issues long before.
The code I have by now is (I changed the parameter of the Event Handlers to the type T from the MudSelect, and before it showed the dropdownlist empty and now that I moved the project to a new MudBlazor project, is what you can see, before my issue was the MB logo occupying 3 screens height):
C#:
@page "/"
@using MudBlazorClients.Models
@using MudBlazorClients.Services
@using MudBlazor
@using Microsoft.AspNetCore.Components

<PageTitle>Index</PageTitle>

    <MudSelect T="string" ValueChanged="CountryHasChanged" Variant="Variant.Outlined">
        @foreach (var country in Countries)
        {
            <MudSelectItem Value="@country.CountryCode">@country.Name</MudSelectItem>
        }
    </MudSelect>
    <MudSelect T="int" ValueChanged="CityHasChanged" Variant="Variant.Outlined">
        <MudSelectItem Value="0">Please select a city ...</MudSelectItem>
        @foreach (var city in Cities)
        {
            <MudSelectItem Value="@city.Id">@city.Name</MudSelectItem>           
        }
    </MudSelect>
    <MudSelect T="int" ValueChanged="ClientHasChanged" Variant="Variant.Outlined">
        <MudSelectItem Value="0">Please select a client ...</MudSelectItem>
        @foreach (var client in Clients)
        {
            <MudSelectItem Value="@client.Id">@client.Name</MudSelectItem>
        }
    </MudSelect>
    <MudTextField Value="@client?.Name" ></MudTextField>

@code {
    private Client? client;

    private List<Country>? Countries;
    private List<City>? Cities;
    private List<Client>? Clients;

    CountryService countryService = new CountryService();
    CityService cityService = new CityService();
    ClientService clientService = new ClientService();

    protected override void OnInitialized()
    {
        Countries = countryService.GetAllCountries();
        Cities = new List<City>();
        Clients = new List<Client>();
    }

    private void CountryHasChanged(string code)
    {
        if (string.IsNullOrEmpty(code))
        {
            Cities.Clear();
        }
        else
        {
            LoadCities(code);
        }
        Clients.Clear();
    }

    private void LoadCities(string countryCode)
    {
        Cities = cityService.GetCitiesByCountry(countryCode);
    }

    private void CityHasChanged(int id)
    {
        if (id == 0)
            Clients.Clear();
        else
            LoadClients(id);
    }

    private void LoadClients(int cityId)
    {
        Clients = clientService.GetClientsByCity(cityId);
    }

    private void ClientHasChanged(int id)
    {
        client = clientService.GetClientById(id);
    }
}
Thank you in advance!
Pablo
 

Attachments

  • nms.png
    nms.png
    159.6 KB · Views: 17
A demo of what I mean when I say "you can do the filtering on get rather than set":


Or, can do the filtering on set:


Or, can ignore @bind and write the Value/ValueChanged pair yourself:


Or, something else - these are not the only ways to skin the cat

If I was dynamically loading things out of a DB I'd probably do the third way, as having an async method allows you easy access to using the DB in async/await style - it's harder with properties because they aren't async (but you can InvokeAsync an async delegate that fetches your data and updates the state.. it looks messier though.
If the data was all in mem, and no accessed async style, I'd do one of the first two, or similar, because they're cleaner/less code clutter

None of that above is intended to be a best practices with regards to data containers and models; I wouldn't use a dictionary as they have no inherent order, and I typically wouldn't use a KeyValuePair<(int,int),string> in place of a proper model. None of those things I used are mandatory to make it work, theyre just easy ways to create and init data containing classes and collections for demo purposes; I'm short on time today
 
the Index.razor doesn't recognize
Probably forgot a global import. Install the MB templates from nuget and use them to create new projects instead
 
Hi cjard,
Poor cat! Why do you want to skin him? I'm the best friend of cats here in Buenos Aires city and surroundings. Ha ha ha!
I must express to you my greatest gratitude: this is the first time that a MudSelect works for me. I know the Dictionary and that stuff are just to simulate data loading and that they are not a real case. I chose the 3rd demo because I like it more and also you told me you'd do that way if there's a DB. It went all fine except for one detail (see the image): when I select a country, in the city MudSelect it appears a number and not the city name; then if I open the dropdownlist it shows all the filtered cities and if I select one, it shows it in the MudSelect box.
Here's the new code with the MudSelect working and the described issue. Is there a fix for it?
C#:
@page "/"
@using MudBlazorClients.Models
@using MudBlazorClients.Services

<PageTitle>Index</PageTitle>

<MudText Typo="Typo.h3" GutterBottom="true">Hello, world!</MudText>
<MudText Class="mb-8">Welcome to your new app, powered by MudBlazor!</MudText>
<MudAlert Severity="Severity.Normal">You can find documentation and examples on our website here: <MudLink Href="https://mudblazor.com" Typo="Typo.body2" Color="Color.Inherit"><b>www.mudblazor.com</b></MudLink></MudAlert>

<MudSelect T="string" Value="CountryCode" ValueChanged="CountryHasChanged" Placeholder="Please select a country ..." Variant="Variant.Outlined">
    @foreach (var country in countries)
    {
        <MudSelectItem Value="@country.CountryCode">@country.Name</MudSelectItem>
    }
</MudSelect>
<MudSelect T="int" Value="CityId" ValueChanged="CityHasChanged" Placeholder="Please select a city ..." Variant="Variant.Outlined">
    @foreach (var city in cities)
    {
        <MudSelectItem Value="@city.Id">@city.Name</MudSelectItem>
    }
</MudSelect>
<MudSelect T="int" Value="ClientId" ValueChanged="ClientHasChanged" Placeholder="PLease select a client ..." Variant="Variant.Outlined">
    @foreach (var client in clients)
    {
        <MudSelectItem Value="@client.Id">@client.Name</MudSelectItem>
    }
</MudSelect>
<MudTextField Value="@clientName" Variant="Variant.Outlined"></MudTextField>

@code {
    private string? CountryCode { get; set; }
    private int CityId { get; set; }
    private int ClientId { get; set; }
    private string? clientName { get; set; }
    private List<Country>? countries;
    private List<City>? cities = new List<City>();
    private List<Client>? clients = new List<Client>();

    protected override void OnInitialized()
    {
        CountryService service = new CountryService();
        countries = service.GetAllCountries();
        CountryCode = "";
    }

    private void CountryHasChanged(string code)
    {
        CountryCode = code;
        if (string.IsNullOrEmpty(code))
        {
            cities.Clear();
        }
        else
        {
            CityService service = new CityService();
            cities = service.GetCitiesByCountry(code);
            CityId = cities[0].Id;
        }
    }

    private void CityHasChanged(int id)
    {
        CityId = id;
        if (id == 0)
        {
            clients.Clear();
        }
        else
        {
            ClientService service = new ClientService();
            clients = service.GetClientsByCity(id);
            ClientId = clients[0].Id;
        }
    }

    private void ClientHasChanged(int id)
    {
        ClientId = id;
        if (id == 0)
        {
            clientName = string.Empty;
        }
        else
        {
            ClientService service = new ClientService();
            clientName = service.GetClientById(id).Name;
        }
    }
}
Thank you really much.
Pablo
 

Attachments

  • mudselect.png
    mudselect.png
    18.8 KB · Views: 31
It's not "use the third way if you use a DB", it's "the third way is easier if you use async because properties are inherently syncronous" (and you should be accessing your DB asyncronously - you don't seem to be doing it)

Blazor works better when you use async code wherever possible; internally it's better geared up for processing Tasks and carrying out necessary internal operations when they complete. My code did do the Async thing (and you've taken it out) - you should instead be making your services access the DB asyncronously and making their methods async and awaiting them, or returning Tasks to Blazor

It's showing 0 because the selected cty starts out as 0. The default value for a non-nullable primitive like int, at class level, is 0. Because there is no item in the City list that has 0 for an ID, MudBlazor just shows the ID in the box instead of the name that relates to city 0. In my example I did have list items that were a 0, as placeholders. Switching to int? allows you to set default, which MudSelect uses to know it has to show the placeholder: TryMudBlazor - Write, compile, execute and share Blazor components in the browser

In the below blue, your CityId has to be an `int?` which means other places need to change to follow suit

1691729408906.png


Red circled int becomes int?
Code is missing or needs changing at pink locations cto compare with default, not 0, and set default when collections are cleared

I would inject theose services rather than making new ones myself, but be careful if this is Blazor Server Side and youre using EF - injecting a context into a service can lead to an unreasonably long lifetime for the context. Instead you should inject a contextfactory and use it to manufacture contexts (kinda like what your `new` does, actually :) )

Also, clientName should be called ClientName and perhaps all your collections should not be nullable, but instead be inited as empty. There is certainly no need for cities and clients to be nullable as theyre never null in this code (unless GetXBy returns a null list, but avoid that, because you'll crash your page
 
Last edited:
Hi @cjard
If I use nullable integers it produces a runtime error (I suppose because mi loading methods use non nullable ints as id parameters).
I modify the code as follows and it seems to work, can you please tell me if it is a bad practice? I changed all the methods to async Task, too, as you told me.
C#:
@page "/"
@using MudBlazorClients.Models
@using MudBlazorClients.Services

<PageTitle>Index</PageTitle>

<MudText Typo="Typo.h3" GutterBottom="true">Hello, world!</MudText>
<MudText Class="mb-8">Welcome to your new app, powered by MudBlazor!</MudText>
<MudAlert Severity="Severity.Normal">You can find documentation and examples on our website here: <MudLink Href="https://mudblazor.com" Typo="Typo.body2" Color="Color.Inherit"><b>www.mudblazor.com</b></MudLink></MudAlert>

<MudSelect T="string" Value="CountryCode" ValueChanged="CountryHasChangedAsync" Placeholder="Please select a country ..." Variant="Variant.Outlined">
    @foreach (var country in countries)
    {
        <MudSelectItem Value="@country.CountryCode">@country.Name</MudSelectItem>
    }
</MudSelect>
<MudSelect T="int" Value="CityId" ValueChanged="CityHasChangedAsync" Placeholder="Please select a city ..." Variant="Variant.Outlined">
    @if (cities.Count == 0)
    {
        <MudSelectItem Value="0">Please select a city ...</MudSelectItem>
    }
    @foreach (var city in cities)
    {
        <MudSelectItem Value="@city.Id">@city.Name</MudSelectItem>
    }
</MudSelect>
<MudSelect T="int" Value="ClientId" ValueChanged="ClientHasChangedAsync" Placeholder="PLease select a client ..." Variant="Variant.Outlined">
    @if (clients.Count == 0)
    {
        <MudSelectItem Value="0">Please select a client ...</MudSelectItem>
    }
    @foreach (var client in clients)
    {
        <MudSelectItem Value="@client.Id">@client.Name</MudSelectItem>
    }
</MudSelect>
<MudTextField Value="@ClientName" Variant="Variant.Outlined"></MudTextField>

@code {
    private string? CountryCode { get; set; }
    private int CityId { get; set; }
    private int ClientId { get; set; }
    private string? ClientName { get; set; }
    private List<Country> countries = new List<Country>();
    private List<City> cities = new List<City>();
    private List<Client> clients = new List<Client>();

    protected override async Task OnInitializedAsync()
    {
        CountryService service = new CountryService();
        countries = await service.GetAllCountries();
        CountryCode = "";
    }

    private async Task CountryHasChangedAsync(string code)
    {
        CountryCode = code;
        if (string.IsNullOrEmpty(code))
        {
            cities.Clear();
        }
        else
        {
            CityService cityService = new CityService();
            cities = await cityService.GetCitiesByCountry(code);
            CityId = cities[0].Id;
            ClientService clientService = new ClientService();
            clients = await clientService.GetClientsByCity(CityId);
            ClientId = clients[0].Id;
            ClientName = clients[0].Name;
        }
        await InvokeAsync(StateHasChanged);
    }

    private async Task CityHasChangedAsync(int id)
    {
        CityId = id;
        if (id == 0)
        {
            clients.Clear();
        }
        else
        {
            ClientService service = new ClientService();
            clients = await service.GetClientsByCity(id);
            ClientId = clients[0].Id;
            ClientName = clients[0].Name;
        }
        await InvokeAsync(StateHasChanged);
    }

    private async Task ClientHasChangedAsync(int id)
    {
        ClientId = id;
        if (id == 0)
        {
            ClientName = string.Empty;
        }
        else
        {
            ClientService service = new ClientService();
            ClientName = (await service.GetClientById(id)).Name;
        }
    }
}
Thanks.
Pablo
 
The try.MudBlazor example I posted uses nullable integers and does not crash (as far as I know); reproduce your crash on try.MudBlazor and I'll fix it. You don't need to have a DB; some simple collection inits that are equivalent to what your queries return will be sufficient
 
Hi, @cjard
First, sorry for the delay in answering.
I tried to change my code to make it the most similar possible to your code which uses nullable ints, as you told me so you can fix it, and I zipped it, because it would be too tedious (if possible) to simulate my queries, there are too many classes and various queries.
It produces a runtime error just after selecting a country.
I hope you don't mind testing it and I thank you very much in advance for your help.
Pablo
 
it would be too tedious (if possible) to simulate my querie

It's too tedious for you to make my life easier by changing this:

C#:
        public List<City> GetAllCities()
        {
            List<City> cities = new List<City>();
            SqlCommand cmd = new SqlCommand("SELECT * FROM City", con);
            con.Open();
            var rdr = cmd.ExecuteReader();
            while (rdr.Read())
            {
                cities.Add(new City() { Id = rdr.GetInt32(0), Name = rdr.GetString(1), CountryCode = rdr.GetString(2) });
            }
            rdr.Close();
            con.Close();
            return cities;
        }

Into this:

C#:
        public List<City> GetAllCities()
        {
            return new List<City>{
              new City() { Id = 1, Name = "City1", CountryCode = "CC" },
              new City() { Id = 2, Name = "City2", CountryCode = "CC" }
            };
        }

?

Hmm..

Well..

You want the help, and providing me with something I can just run and work with without having to spend my time fixing up code that won't run because it references a non-existent database is kinda a pre-requisite of you getting that help. Someone else might take on the challenge of getting your code running so it can be fixed though..

I took a quick look at Index.razor and couldn't see any nullable ints anywhere. The other pages didn't seem to contain any MudXXX components at all... Does this code really reproduce the issue?
 
Last edited:

Latest posts

Back
Top Bottom