1

I have an enum with EnumMemberAttribute specified for its values:

public enum HireStrategy
{
    [EnumMember(Value = "30-days")]
    Days30,
    [EnumMember(Value = "60-days")]
    Days60,
    [EnumMember(Value = "90-days")]
    Days90
}    

I want to be able to use the values 30-days, 60-days, 90-days as input parameters for my api. Here is services configuration:

services
    .AddControllers()
    .AddNewtonsoftJson(config =>
    {
        config.SerializerSettings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy)));
    });

services
    .AddMvc();            

And my action:

[HttpPost("test")]
public async Task<IActionResult> Test(HireStrategy type)
{
    return Ok();
}        

However, when I try to use the request (via postman) https://localhost:44329/api/v1/users/test?type=60-days I get validation error, i.e. it doesn't even hit my action:

{
    "errors": {
        "type": [
            "The value '60-days' is not valid."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|b5f15582-4dc901ce029dfb30."
}

When I pass "days60" as value, it works fine. Is there anything I'm missing? Thanks!


UPDATE #1: I tried to disable ApiController validation to see if newtonsoft handle request:

services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});            

But the parameter gets mapped to default value, which is Days30

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Alex K.
  • 784
  • 5
  • 12
  • 1
    `StringEnumConverter` only applies when deserializing **JSON** from the **post body**, because Json.NET is a JSON serializer. It doesn't apply when binding from any other [source](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0#sources). Your postman request puts the `type` in form fields not the post body. – dbc Aug 02 '21 at 15:57
  • 1
    For `FromForm` enums I found this: [Is there any alternates Attribute for asp.net core enum model binding instead of EnumMemberAttribute?](https://stackoverflow.com/q/59131659/3744182). – dbc Aug 02 '21 at 16:04
  • Thank you @dbc. Vary good point about json.net working for body only. Do you think Implementing my own IValueProviderFactory work in my case? That's what I want to try – Alex K. Aug 02 '21 at 16:08
  • 1
    Not sure, give it a try and let us know. I think a custom typeconverter might work also. See the [previously linked question](https://stackoverflow.com/q/59131659/3744182) and also e.g. [Model Binding Custom Type](https://stackoverflow.com/q/2646048/3744182). – dbc Aug 02 '21 at 16:16
  • 1
    I ended up implementing EnumConverter just like the one in your link. Thanks a lot for your help! – Alex K. Aug 02 '21 at 17:48

1 Answers1

4

Based on the answer here that was provided by @dbc in the comments. The only thing I changed is used CamelCaseNamingStrategy to make sure either pascal or camel cases are treated as valid values.

public class EnumMemberConverter<T> : EnumConverter
{
    private readonly CamelCaseNamingStrategy _camelCaseNamingStrategy;

    public EnumMemberConverter(Type type) 
        : base(type) 
    {
        _camelCaseNamingStrategy = new CamelCaseNamingStrategy();
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        var type = typeof(T);

        foreach (var field in type.GetFields())
        {
            if (Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)) is EnumMemberAttribute attribute
                && value is string enumValue
                && _camelCaseNamingStrategy.GetPropertyName(attribute.Value, false) == _camelCaseNamingStrategy.GetPropertyName(enumValue, false))
            {
                return field.GetValue(null);
            }
        }          

        return base.ConvertFrom(context, culture, value);
    }
}    
[TypeConverter(typeof(EnumMemberConverter<HireStrategy>))]
public enum HireStrategy
{
    [EnumMember(Value = "30-days")]
    Days30,
    [EnumMember(Value = "60-days")]
    Days60,
    [EnumMember(Value = "90-days")]
    Days90
}    
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Alex K.
  • 784
  • 5
  • 12