Struggling with the razor select helper - asp.net-core

While trying out ASP.NET Core 2.2 MVC with razor, I had a hell of a struggle with this, and I'm still not sure why my prior attempts -- which I thought mimicked what I saw here on stackoverflow -- did not display the selected option, but here's one magic potion that does:
In vwProjectViewModel I have:
public List<SelectListItem> PageNumbers { get; set; }
public int PageNumber { get; set; }
Here's the cshtml view snippet:
#model vwProjectViewModel
<select name="PageNumber" id="PageNumber"
asp-items='new SelectList(#Model.PageNumbers,
"Value", "Text",
Model.PageNumber)'>
</select>
Here's a question: Why does adding asp-for="PageNumber" to the select tag break it so carelessly, making it no longer display the selected option?
Also, why does this simple syntax not display the selected option (didn't I see this here?):
<select asp-for="PageNumber" asp-items="#Model.PageNumbers"></select>
or
<select asp-for="PageNumber" asp-items="Model.PageNumbers"></select>
This also seems to display the selected option, but is considerably less elegant:
<select name="PageNumber" id="PageNumber">
#{
#foreach (SelectListItem #item in #Model.PageNumbers)
{
#if (#Model.PageNumber.ToString() == #item.Value)
{
<option value="#item.Value" selected="selected">#item.Text</option>
}
else
{
<option value="#item.Value" >#item.Text</option>
}
}
}
</select>
Anyone care to shed some light on this quirky tag helper?

<select id="PageNumber" asp-for="PageNumber" asp-items="Model.PageNumbers"> </select>
It will set selected if the value of PageNumber if it has one that matches the value of a SelectListItem:
List<SelectListItem> selectListItems = new List<SelectListItem>();
selectListItems.Add(new SelectListItem() { Value = "1", Text = "1" });
selectListItems.Add(new SelectListItem() { Value = "2", Text = "2" });
And :
vwProjectViewModel mymodel = new vwProjectViewModel();
mymodel.PageNumbers = new List<SelectListItem>();
mymodel.PageNumbers = selectListItems;
mymodel.PageNumber = 2;

Related

First option in Blazor InputSelect displayed but value is null

I have encountered a weird behavior of InputSelect element in Razor component.
On my input form, I have several fields bound with the model (Partner). Some of these fields I placed in form of dropdown selection. Because the bound field's (PartnerCategory) value is the id (integer) I fetch a lookup table from DB with a name corresponding to a selected id.
On a page, I can see all names in the dropdown list. But when I try to insert a record from the form to the database it throws an SQL exception, because InputSelect treats the first value in the list as NULL. Just to be clear - there is no blank value in the dropdown list, and all names are shown. It just takes it's value as NULL. Followingly because the data type is an integer and it converts NULL to zero. And because I don't have an id that is zero in my table, the Insert command fails.
Below is my simplified code
<EditForm Model="#partner">
<InputSelect #bind-Value="partner.PartnerCategoryId">
#if (categoryList != null)
{
#foreach (var item in categoryList.OrderBy(x => x.PartnerCategoryId))
{
<option value="#item.PartnerCategoryId">#item.Name</option>
}
}
</InputSelect>
</EditForm>
#code {
Partner partner = new Partner();
private IEnumerable<PartnerCategory> categoryList;
protected override async Task OnInitializedAsync()
{
categoryList = await CategoryService.GetAllAsync();
}
}
How can I handle this? Does it bind values to a model before it fetches data from DB?
To solve this issue you can add <option value="">Select...</option> in your code like this:
<InputSelect #bind-Value="partner.PartnerCategoryId">
#if (categoryList != null)
{
<option value="">Select...</option>
#foreach (var item in categoryList.OrderBy(x => x.PartnerCategoryId))
{
<option value="#item.PartnerCategoryId">#item.Name</option>
}
}
</InputSelect>
And in your PartnerCategory model define the PartnerCategoryId as required. Note that the type of PartnerCategoryId is nullable: int?
[Required]
public int? PartnerCategoryId {get; set;}
This will prevent the 'submission' of your form unless the user has selected a value
To test the new changes:
Add the OnValidSubmit attribute to your EditForm component and set its value to "HandleValidSubmit"
Add a HandleValidSubmit method, like this:
private void HandleValidSubmit()
{
// Put code here to save your record in the database
}
Add a submit button at the bottom of your EditForm:
<p><button type="submit">Submit</button></p>
Run your app, and hit the "Submit" button...As you can see the form is not "submitted", and the select element's borders are painted red.
Here's a complete version of your code:
<EditForm Model="#partner" OnValidSubmit="HandleValidSubmit">
<InputSelect #bind-Value="partner.PartnerCategoryId">
#if (categoryList != null)
{
<option value="">Select...</option>
#foreach (var item in categoryList.OrderBy(x => x.PartnerCategoryId))
{
<option value="#item.PartnerCategoryId">#item.Name</option>
}
}
</InputSelect>
<p><button type="submit">Submit</button></p>
</EditForm>
#code {
Partner partner = new Partner();
private IEnumerable<PartnerCategory> categoryList;
protected override async Task OnInitializedAsync()
{
categoryList = await CategoryService.GetAllAsync();
}
private void HandleValidSubmit()
{
Console.WriteLine("Submitted");
}
}
In case someone is facing the same issue, here is my code which solved the issue:
<div class="mb-3 form-check">
<label for="category" class="form-label">Select category</label>
<InputSelect TValue="int" #bind-Value="subcategory.CategoryId" class="form-control" id="category">
<option value="">Select...</option>
#foreach(var cate in categories)
{
<option value="#cate.CategoryId">#cate.CategoryName</option>
}
</InputSelect>
<ValidationMessage For="#(()=>subcategory.CategoryId)"/>
</div>

How can I bind radio buttons to a property which is an enum?

I'm working on radio buttons using Blazor. There have to be 2 radio buttons for the salutation of a person. But the salutation of the person is already clear. So for example if it's a man, I need the man radio button to be checked when I load the page. The problem is that I can't use #bind-Value for a radio button. Can anyone help me?
Please model your code after this sample:
#foreach (var choice in new[] { Choices.Red, Choices.Green, Choices.Blue })
{
<label>
<input name="yourColor" type="radio"
value="#choice"
checked="#(currentChoice == choice)"
#onchange="#(() => { currentChoice = choice; })">
#choice.ToString()
</label>
}
<p>You chose: #currentChoice</p>
#code {
enum Choices { Red, Green, Blue };
Choices currentChoice = Choices.Red;
}
Hope this helps...
Source: https://github.com/dotnet/aspnetcore/issues/5579#issuecomment-548061223
As you are specifically asking for a binding solution:
There is no native Blazor binding solution so far... But the project Blazorise offers a pure binding solution for this problem:.
#code{
enum MyEnum
{
A = 0,
B = 1,
C = 2,
D = 3
}
MyEnum checkedValue { get; set; } = MyEnum.B;
}
The code in a .razor file:
<p>Current count: #(checkedValue.ToString())</p>
<RadioGroup TValue="MyEnum" Name="group1" #bind-CheckedValue="#checkedValue">
#foreach (var val in Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()) {
<Radio TValue="MyEnum" Value="#val">#(val.ToString())</Radio>
}
</RadioGroup>

Is there a function of Html.GetEnumSelectList<Enum>() that will set the value of the option to the value of said Enum?

When using the built in Html.GetEnumSelectList() for the following Enum:
public enum Country {
[Display(Name="United States")] US,
[Display(Name="Canada")] CA
}
It generates the following html:
<select id="prefix" class="form-control">
<option value="0">United States</option>
<option value="1">Canada</option>
</select>
Is there a way to have the value set to the value of the enum instead of the index?
I wrote an extension for what I needed, but it is so basic that it feels odd that the C# team missed it, so I'm curious if I did
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace HotelMaven.Extensions {
public static class HtmlExtensions {
public static IEnumerable<SelectListItem> GetEnumSelectList<TEnum>(this IHtmlHelper html, TEnum selectedValue) where TEnum : struct {
var values = Enum.GetValues(typeof(TEnum))
.Cast<TEnum>();
return values.Select(eachValue => new SelectListItem {
Text = eachValue.GetType().GetField(eachValue.ToString()).GetCustomAttribute<DisplayAttribute>()?.Name,
Value = eachValue.ToString(),
Selected = eachValue.Equals(selectedValue)
});
}
public static IEnumerable<SelectListItem> GetEnumSelectList<TEnum>(this IHtmlHelper html, bool isValueUsedForValue) where TEnum : struct {
if (!isValueUsedForValue) return html.GetEnumSelectList<TEnum>();
var values = Enum.GetValues(typeof(TEnum))
.Cast<TEnum>();
return values.Select(eachValue => new SelectListItem {
Text = eachValue.GetType().GetField(eachValue.ToString()).GetCustomAttribute<DisplayAttribute>()?.Name,
Value = eachValue.ToString()
});
}
}
}
Which results in what I'm looking for:
<select id="prefix" class="form-control">
<option selected="selected" value="US">United States</option>
<option value="CA">Canada</option>
</select>
Using:
<select asp-items="Html.GetEnumSelectList<Country>(Country.US)" id="country" class="form-control"></select>
U can Use attribute for enum and get and use it, for example this code get the DisplayAttribute value.
public static string GetDisplayName(this Enum value)
{
if (value == null) return String.Empty;
if (Cache.ContainsKey(value))
return Cache[value];
var enumType = value.GetType();
var enumName = Enum.GetName(enumType, value);
var member = enumType.GetMember(enumName)[0];
var attributes = member.GetCustomAttributes(typeof(DisplayAttribute), false);
var outString = ((DisplayAttribute)attributes[0]).ResourceType != null
? ((DisplayAttribute)attributes[0]).GetName()
: ((DisplayAttribute)attributes[0]).Name;
Cache.Add(value, outString);
return outString;
}
I think u can define attribute for each enum value (by custom or available attribute like display attribute that u used) and store the value in this attribute and use it for value of option or any where.
You should define the extenstion with a different name than GetEnumSelectList which is default
public static class HtmlExtensions
{
public static IEnumerable<SelectListItem> GetEnumValueSelectList<TEnum>(this IHtmlHelper htmlHelper) where TEnum : struct
{
return new SelectList(Enum.GetValues(typeof(TEnum)).OfType<Enum>()
.Select(x =>
new SelectListItem
{
Text = x.GetType().GetField(x.ToString()).GetCustomAttribute<DisplayAttribute>()?.Name,
Value = x.ToString()
}), "Value", "Text");
}
}
And usage:
<select class="form-control" asp-items="Html.GetEnumValueSelectList<Country>()"></select>
you can solve it by JavaScript if you want
For Example:
<select id="status" name="TicketStatus" asp-items="Html.GetEnumSelectList(typeof (TicketSystem.Models.EnumStatus))" class="form-control" >
<option value="-1">All</option>
</select>
<script>
var st = document.getElementById('status');
for (x of st.options) {
x.value = x.innerText;
}
</script>

.NET Core new SelectList returns null

I am writing a .netcore webapp and I am using globalization to populate a list of countries this is in my controller class:
public IActionResult GetCountry()
{
List<string> CountryList = new List<string>();
CultureInfo[] cInfoList = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
foreach (CultureInfo cInfo in cInfoList)
{
RegionInfo r = new RegionInfo(cInfo.LCID);
if(!(CountryList.Contains(r.EnglishName)))
{
CountryList.Add(r.EnglishName);
}
}
//sort list into order
CountryList.Sort();
ViewBag.CountryList = CountryList;
return View(CountryList);
}
I have the following in my model class:
[Display(Name ="Country of Origin")]
public string GetCountry { get; set; }
and finally in my cshtml I have the following:
<div class="form-group">
<label asp-for="GetCountry"></label>
<select asp-for="GetCountry" asp-items="new SelectList(ViewBag.CountryList)"></select>
</div>
I am not sure where I have gone wrong but it will give me the following message during runtime.
ArgumentNullException: Value cannot be null.
Parameter name: items
Microsoft.AspNetCore.Mvc.Rendering.MultiSelectList..ctor(IEnumerable items, string dataValueField, string dataTextField, IEnumerable selectedValues, string dataGroupField)
I've probably done something silly but any help will be appreciated.
You missed # in asp-items
<div class="form-group">
<label asp-for="GetCountry"></label>
<select asp-for="GetCountry" asp-items="#(new SelectList(ViewBag.CountryList))"></select>
</div>
The code that you are using, does correctly populate the list of cultures.
It's more than likely that your .cshtml file is not populated with the correct view model.
At the top of the page, you'd normally see something like:
#model MyApplication.Models.Country.CountryListModel;
ViewBag.CountryList = Model.CountryList;

Filling in the SelectList with a for loop

Ok, my drop down used to be made like so:
<select id="Level" onchange="showChoice(this.value);">
<option id="0" value="0">Pick a Level</option>
#for (int i =1; i <= Model.ExamplesCount; i++ )
{
<option id="#i" value="#i">Level #i</option>
}
</select>
because the ExamplesCount number changes per user. But I need to use the Html.ValidationMessageFor() thing, which I can't get to work with this.
I need one of two solutions.
Can I make Html.ValidationMessageFor() work with this select tag?
or if not,
2.Can I use Html.DropDownListFor() but fill it in with a similar for loop?
For example,
#Html.DropDownListFor(
m => m.Level,
new SelectList(
new List<Object> {
new {value = 0, text ="Pick a Level"},
new { value = 1, text = "Level 1"},
new { value = 2, text = "Level 2" },
new { value = 3, text = "Level 3" },
new { value = 4, text = "Level 4" },
new { value = 5, text = "Level 5" }
},
"value", "text", null))
#Html.ValidationMessageFor(model => model.Level)
The above code works, but where I am hard coding all the SelectList values, I'd like to have an for loop that does it for me.
What about creating a object in your model that will content all the items you want an then pass that to your view? Example:
In your Model.
public class Model{
...other properties
public List<ListItemSource> myLevels { get; set; }
[Required(ErrorMessage = #"*Required")]
public string Level { get; set; }
}
In your controller:
public ActionResult YourAction(Model myModel)
{
var myModel = new Model
{
myLevels =methodToGetLevels()
};
return view(myModel);
}
In your View:
#Html.DropDownListFor(x => x.Level,
new SelectList(Model.myLevels, "Value", "Text"))
#Html.ValidationMessageFor(model => model.Level)
Where x.Level will hold the selected value and Model.myLevels is the collection of levels.
I hope this help to solve your problem.