Do taghelpers currently supports the following scenarios?
I have a feeling that this is currently supported but I'm missing something.
I'm not sure how to use taghelpers in this cases.
_Scenario1:_ radio
enum Gender {
Male,
Female
}
ViewModel {
Gender Gender {get;set;}
}
so I get multiple (two in this case) radio buttons (one for Male and one for Female).
desired html output (doesn't have to be exact):
<label>
<input type="radio" name="Gender" value="0">
Male
</label>
<label>
<input type="radio" name="Gender" value="1">
Female
</label>
and
_Scenario2:_ checkbox
[Flags]
enum Roles {
Admin,
User,
Customer
}
ViewModel {
Roles Roles {get;set;}
}
so I get multiple (three in this case) checkboxes.
desired html output (doesn't have to be exact):
<label>
<input type="checkbox" name="Roles" value="0">
Admin
</label>
<label>
<input type="checkbox" name="Roles" value="1">
User
</label>
<label>
<input type="checkbox" name="Roles" value="2">
Customer
</label>
What exactly is the scenario? A tag helper to render out the entire radio button list or checkbox list? I don't think we have that. I don't think there was ever an MVC HTML Helper for that either.
One reason we haven't built that helper is that making the UI reasonably customizable is quite challenging... so we just didn't do it 馃槃
Oh guess I'm not missing something then.
The scenario is having validation/metadata passed to the view. (via taghelpers or whatever).
Maybe something like Html.GetEnumSelectList but for checkbox/radios.
I'm not sure (yet) but I think the model binder also has problems with flag enum values. (see * below)
posting: name="Gender" value="Male" checked does not populate the underlying enum on the controller.
checkbox is only used for bool values. While this is fine, I think we are missing a lot of value from the input tag. same for radio.
checkbox/radio are very useful to work with collections and not only booleans.
Html.GetEnumSelectList doesn't work with flags (where a multiple select will apply perfectly). [not related to this issue tho]I guess this is becoming more of a feature request than anything else.
What do you think?, would it make sense to have this kind of support on the framework?
@Bartmax Html.GetEnumSelectList() is intended for cases like <select asp-for='ListProperty' asp-items='Html.GetEnumSelectList(...)' ... ></select>. But, yes, it has a designed-in restriction around [Flags] enums because such types have no obvious default <option /> collection.
As @Eilon said, we don't have tag helpers that handle generating checkbox or radio button groups. But the <input/> tag helper would generate the radio buttons for your scenario just fine. However a restriction in the <input/> tag helper (it always generates the false case as a type='hidden' field) means that helper is not appropriate for your scenario. This would at least get you the model metadata / validation attributes for the radio buttons.
From my perspective, a tag helper that be used _in_ a checkbox group would be helpful. But new or enhanced HTML helpers seem more appropriate if we start dealing with templates or otherwise generating the entire checkbox / radio button group in a single call. Nice to be able to almost see exactly the generated HTML when looking at a .cshtml file containing tag helpers.
@Bartmax Html.GetEnumSelectList() is intended for cases like
I made a new issue here: https://github.com/aspnet/Mvc/issues/5096 about that.
Nice to be able to almost see exactly the generated HTML when looking at a .cshtml file containing tag helpers.
I'm not suggesting to make a template html thing with all inputs, I agree that it's nice to see almost exact html.
From the top of my head I imagine something along this lines:
[Flags]
enum EnumFlag {
First,
Second,
Third
}
class ViewModel {
[BindRequired]
public EnumFlag MyEnumFlag {get; set;}
}
<label asp-for="MyEnumFlag"></label>
<label asp-for="MyEnumFlag" asp-item="First"></label>
<input asp-for="MyEnumFlag" asp-item="First">
<label asp-for="MyEnumFlag" asp-item="First"></label>
<input asp-for="MyEnumFlag" asp-item="Second">
<label asp-for="MyEnumFlag" asp-item="First"></label>
<input asp-for="MyEnumFlag" asp-item="Third">
<span asp-validation-for="MyEnumFlag"></span>
will render:
<label for="MyEnumFlag">My enum flag</label>
<label for="MyEnumFlag.First">First</label>
<input type="checkbox" name="MyEnumFlag" id="MyEnumFlag.First" value="First" />
<label for="MyEnumFlag.Second">Second</label>
<input type="checkbox" name="MyEnumFlag" id="MyEnumFlag.Second" value="Second" />
<label for="MyEnumFlag.Third">Third</label>
<input type="checkbox" name="MyEnumFlag" id="MyEnumFlag.Third" value="Third" />
<span> ... the validation here like required ...</span>
If the enum is not flags, the input will switch from checkbox to radio.
To pick the current selected values, using MyEnumFlag.HasFlag on the input should be enough.
Intellisense can be smart to suggest the asp-items value only for the valid enum type.
if asp-item is missing can throw a runtime exception like some taghelpers do with a proper error message like: "_input type for enum flags must provide an asp-item property._"
I think this can also be extended/used for collections. In the case someone want to show a collection as a radio/checkbox on the UI (very useful!). Switching from radio to checkbox would be a matter of the property at hand if it's IEnumerable or not.
I also don't think template-generating multiple inputs is a good idea, but generating proper id attributes for radio buttons and checkboxes is a good idea.
I've actually created a tag helper in my test project for this kind of functionality last week, and have just created a pull request with this functionality here: aspnet/Mvc#5097
It supports appending the radio button value to the id of the input, as well as the for attribute of the label, and also adds support for collection backed checkboxes with the same id generation code
@artiomchi is #5097 a fix for #5084? I suspect #5097 is actually almost completely separate though it touches on the id and name choices mentioned above. We need separate issues to track separate features, decide on the appropriate milestone for them, and so on. Please file the issues you believe need fixes and add a comment to #5097 linking that PR.
@dougbu #5097 is actually semi-related to this issue, mostly around the code sample in hos comment here
The pull request can simplify some of the cshtml code below to auto generate the ids for radio buttons (removing the need to wrap the radio button / checkbox) in the label, as well as actually generating the checkboxes using tag helpers (currently tag helpers will generate labels that * only* post booleans)
What @Bartmax was asking initially is how to generate a radio button list for an enum. He's thinking if a tag helper will generate all the radio options. In my opinion - tag helpers are not partial view generators - they should generate one, at most several tags, not a whole editor layout.
@Bartmax: This is what you're loking for:
@foreach (Gender gender in Enum.GetValues(typeof(Gender)))
{
<label>
<input asp-for="Gender" type="radio" value="@gender" />
@gender
</label>
}
This generates the following:
<label>
<input type="radio" value="Male" data-val="true" data-val-required="The Gender field is required." id="Gender" name="Gender">
Male
</label>
<label>
<input type="radio" value="Female" id="Gender" name="Gender">
Female
</label>
Flag enums work a bit differently..
First of all, you want to assign them specific values.. With your code, Roles.Admin is equal to 0, Roles.User = 1 and Roles.Customer = 2.
What you actually want is:
[Flags]
public enum Roles
{
Admin = 1 << 0, // 1 = 001b
User = 1 << 1, // 2 = 010b
Customer = 1 << 2, // 4 = 100b
}
Now, when you have two checkboxes representing these values, they won't be summed up, they'll be posted as separate values. In practice, only the first checkbox will have it's value stored.
What you can do, though, is change your model to the following:
class ViewModel
{
public Roles[] Roles { get; set; }
public Roles RolesFlags => Roles?.Aggregate((a, e) => a | e) ?? 0;
}
This way, the checkboxes can post the vales to Roles, and your code can get the values from RolesFlags
To render the checkboxes using the current tooling, you can use the following:
@foreach (Roles role in Enum.GetValues(typeof(Roles)))
{
<label>
<input name="Roles" type="checkbox" value="@role" checked="@(Model != null && Model.RolesFlags.HasFlag(role))" />
@role
</label>
}
Nice work on the samples @artiomchi. Only nit: Model?.RolesFlags.HasFlag(role) == true is a bit more readable.
On #5097, we still need the issues you're addressing because that PR does not line up with this one 1:1.
@dougbu oh yeah, of course! Not sure how I missed that :)
Also, you're right, which is why I created #5098. It contains both the details of what I'm trying to improve and some decisions I had to make in the implementation
@artiomchi exactly that! :+1:
Thanks for the samples @artiomchi
Can I suggest a docs topic to handle this scenario? I recently had to create a "check box list" for Flagged Enums... and ended up with something... really quite horrible.
Back to the topic of flags enums...
This flags enum:
``` c#
[Flags]
enum EnumFlag {
First,
Second,
Third
}
Is of course not really a proper flags enum, because the values aren't flags per se. You'd more likely have something like this:
``` c#
[Flags]
enum EnumFlag {
First = 1 << 0,
Second = 1 << 1,
Third = 1 << 2,
}
And now you can combine the flags to create bitmasks. Creating a UI for this flags enum isn't terribly challenging.
But it's quite common for enum types to actually look like this:
c#
[Flags]
enum EnumFlag {
First = 1 << 0,
Second = 1 << 1,
Third = 1 << 2,
All = First | Second | Third,
}
And what UI do you generate for that?
And beyond that, how often does one have an enum that needs to be projected in a UI? Apps I've seen are often _data-driven_ when it comes to generating checkbox lists. E.g. "select how you heard about us" has the options "web, friend, family, advertisement, radio, other". Or "pick your top languages" has the options "C#, C, C++, Rust, JavaScript, Go, Other". These are all data-driven - there's no CLR type or enum that describes these - it comes from a database.
I think the problem we often have when trying to project UIs from CLR types is that the CLR types weren't designed to be automatically projected as a UI. All these cases end up making for very complex logic, which creates challenges when it comes to compatibility, and also becomes a bug farm.
Instead, we often create alternatives, which is to just have simpler building blocks, which can then be used in composition to create a UI.
I think I'd rather see an approach where we create smaller building blocks instead of trying to build one-size-fits-all UI projection components.
@Eilon I agree, enum is not the right tool for the job. I guess I got biased by the lack of other than bool values of checkbox/radio that I got tempted to the enums route.
I think it's fair to say that this issue can/should be closed and leave a sense that asp.net can do more regarding data to checkbox/radio UI (this ain't about enums at all), which @artiomchi has already pointed out and started an issue around that.
since there is valuable information on this thread, I will let you close and/or reference or whatever.
Closing as per the discussion
In case it's useful for anybody else, this is how I solved the issue of binding checkboxes to a [Flags] enum:
In my example, the User class has a Languages property, which is a flags enum. This is in the editor for a user.
Sample HTML:
<input type="checkbox" name="User.Languages" value="Other">
<input type="checkbox" name="User.Languages" value="English" checked="checked">
<input type="checkbox" name="User.Languages" value="Spanish">
<input type="checkbox" name="User.Languages" value="French" checked="checked">
FlagsEnumBinder.cs:
public class FlagsEnumBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
object result;
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == null) return Task.FromResult(ModelBindingResult.Failed());
if (value.Values.Count > 0)
result = Enum.Parse(bindingContext.ModelType, value.Values);
else
result = Activator.CreateInstance(bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
public class FlagsEnumBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsFlagsEnum)
return new FlagsEnumBinder();
return null;
}
}
Startup.cs:
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new FlagsEnumBinderProvider());
});
Now User.Languages will bind to Languages.English | Languages.French as expected from the HTML, instead of just Languages.English.
I'm not sure if it's quite bulletproof, but after spending a few hours on this (mostly the differences between MVC 5 and 6), I hope this helps somebody in the same boat.
Hey, sorry for digging up this relatively old thread, but I've come across the exact same issue.
I tried using @ScottKaye solution, but sadly it didn't work in my case - the binding still didn't respond at all. I was wondering - has anybody found a way to get around this issue?
Thanks for your help in advance.
Most helpful comment
Back to the topic of flags enums...
This flags enum:
``` c#
[Flags]
enum EnumFlag {
First,
Second,
Third
}
And now you can combine the flags to create bitmasks. Creating a UI for this flags enum isn't terribly challenging.
But it's quite common for enum types to actually look like this:
c# [Flags] enum EnumFlag { First = 1 << 0, Second = 1 << 1, Third = 1 << 2, All = First | Second | Third, }And what UI do you generate for that?
And beyond that, how often does one have an enum that needs to be projected in a UI? Apps I've seen are often _data-driven_ when it comes to generating checkbox lists. E.g. "select how you heard about us" has the options "web, friend, family, advertisement, radio, other". Or "pick your top languages" has the options "C#, C, C++, Rust, JavaScript, Go, Other". These are all data-driven - there's no CLR type or enum that describes these - it comes from a database.
I think the problem we often have when trying to project UIs from CLR types is that the CLR types weren't designed to be automatically projected as a UI. All these cases end up making for very complex logic, which creates challenges when it comes to compatibility, and also becomes a bug farm.
Instead, we often create alternatives, which is to just have simpler building blocks, which can then be used in composition to create a UI.
I think I'd rather see an approach where we create smaller building blocks instead of trying to build one-size-fits-all UI projection components.