Bind flags enum in Razor Pages handler

102 views Asked by At

I'm using ASP.NET Core Razor Pages 7.

A flags enum:

[Flags]                                                      // <--- important
public enum Animals { None=0, Dog=1, Cat=2, Rat=4, All=8 }

In a page's code-behind:

[BindProperty] public Animals Animals { get; set; }

In the page's markup:

<input type="checkbox" name="Animals" value="@Animals.Dog" />
<input type="checkbox" name="Animals" value="@Animals.Cat" />
<input type="checkbox" name="Animals" value="@Animals.Rat" />

Problem: when I submit the form, the Animals property is always bound to the first checked flag, rather than all the flags. For example, if I check "Dog" and "Cat", the property will be bound only to Animals.Dog.

However I can test that the data is posted correctly, because:

HttpContext.Request.Form["Animals"].ToArray() // = { "Dog", "Cat" }

Where is my mistake? Or must I write a custom model binder for [Flags] enums? (If so, can you point me to some sample code to get me started quickly.)

1

There are 1 answers

1
Poul Bak On

This is the IModelBinder class I use:

public class EnumModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
        {
            ValueProviderResult values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (values.Any() && bindingContext.ModelType.IsEnum)
            {
                object result;
                try
                {
                    if (TypeDescriptor.GetConverter(bindingContext.ModelType) != null)
                    {
                        result = TypeDescriptor.GetConverter(bindingContext.ModelType).ConvertFrom(values.ToString());
                    }
                    else
                    {
                        result = Enum.Parse(bindingContext.ModelType, values.ToString());
                    }
                    bindingContext.Result = ModelBindingResult.Success(result);
                }
                catch
                {
                }
            }
            else
            {
                return Task.CompletedTask;
            }
        }

        return Task.CompletedTask;
    }
}

Now all you have to do is: Add: [ModelBinder(BinderType = typeof(EnumModelBinder))] to the Animals enum difinition.

If you have created a TypeConverter for the type, that will be used to convert, otherwise standard enum conversion.

I supports <input type="checkbox" (they must have the same name) and it supports <select multiple.

Here's how to declare the Animals enum:

[ModelBinder(BinderType = typeof(EnumModelBinder))]
[Flags}
public enum Animals
{
    None,
    Mammel,
    Cat
    Dog
}

(And yo're right: it's surprisingly simple).