Home > Software design >  ASP.NET Core: Binding Dictionary<string, List<string>> to <select> strange result
ASP.NET Core: Binding Dictionary<string, List<string>> to <select> strange result

Time:07-06

My razor page has a property of type Dictionary<string, List<string>> which represents chosen countries by continent. I would like to bind it to the value of a <select> control.

//MyPage.cshtml.cs
public class MyPageModel : PageModel
  {
      [BindProperty]
      public Dictionary<string, List<string>?> ChosenCountries { get; } = 
          new Dictionary<string, List<string>?>()
          {
              ["America"] = null
          };

      public List<string> Countries { get; } = new List<string>
      {
          "Mexico", "Canada", "USA"
      };

      public void OnPost()
      {            
          Console.WriteLine(JsonConvert.SerializeObject(Request.Form, Formatting.Indented));
          Console.WriteLine(JsonConvert.SerializeObject(ChosenCountries, Formatting.Indented));
      }
  }

//MyPage.cshtml
@page
@model MyProject.Pages.MyPageModel

@if (Request.Method == "GET")
{
    <form method="POST">
        <select 
            asp-for="@Model.ChosenCountries["America"]" 
            asp-items=@(new SelectList(Model.Countries))>
        </select>
        <button>Submit</button>
    </form>
} 

When I GET the page, choose a country and submit, then the following is written to console:

[
  {
    "Key": "ChosenCountries[America]",
    "Value": [
      "Mexico"
    ]
  },
  {
    "Key": "__RequestVerificationToken",
    "Value": [
      "CfDJ...."
    ]
  }
]
{
  "America": [
    "Mexico"
  ]
}

The result looks ok.

However, when I GET the page again and submit without choosing any country, then the following is written to console:

[
  {
    "Key": "__RequestVerificationToken",
    "Value": [
      "CfDJ...."
    ]
  }
]
{
  "__RequestVerificationToken": [
    "CfDJ...."
  ]
}

Why is binder creating the token key and deleting the original key in ChosenCountries dictionary when nothing is selected?

According to enter image description here

It will show the dictionary's key and value, But If you don't select any item and submit, The request form will be like:

enter image description here

Browser will not send any information about the dictionary. So no matter how model binding matches, It will not match anything, The ChosenCountries passed into your PageModel is a completely default value instead of a dictionary with key = America. you can write your code like this:

   [BindProperty]
    public Dictionary<string, List<string>?> ChosenCountries { get; set; } = 
        new Dictionary<string, List<string>?>()
        {
            ["America"] = null
        };

public void OnPost()
{  
       if (!ChosenCountries.ContainsKey("America"))
        {
            ChosenCountries = new Dictionary<string, List<string>?>()
            {
                ["America"] = null
            };
        }
        
        Console.WriteLine(JsonConvert.SerializeObject(Request.Form));
        Console.WriteLine(JsonConvert.SerializeObject(ChosenCountries));
}

If you have multi <select/> in your razor page, I suggest you use custom model binding to configure.

CodePudding user response:

Dictionary<string, List<string>?> seems like the wrong type. I believe you actually want a class to represent the Country & Continent relationship. A list to represent the available countries. A SelectList with grouping by Continent for the asp-items="".

It's not exactly clear from your question, but I'm assuming you want the user to select multiple values?

public class Country{
    public string Name { get; set; }
    public string Continent { get; set; }
}
// all available countries
public static List<Country> Countries = new ....;

// model
[BindProperty]
public List<string>? Selected { get; set; }

// view
var items = new SelectList(Countries, nameof(Country.Name), nameof(Country.Name), nameof(Country.Continent), null);

<select asp-for="Selected" asp-items="items" multiple="true"></select>
  • Related