使用 AspNet [FromQuery] 模型绑定中的 EnumMember 值反序列化枚举

ibe*_*dev 7 enums jsonserializer custom-model-binder asp.net-core system.text.json

我在 .NET 6 项目中有一个端点Microsoft.NET.Sdk.Web,它使用标准 [FromQuery] 将查询字符串反序列化为 .NET 对象

[Route("[controller]")]
public class SamplesController
    : ControllerBase
{
    [HttpGet]
    public IActionResult Get([FromQuery]QueryModel queryModel)
    {
        if (!queryModel.Status.HasValue)
        {
            return BadRequest("Problem in deserialization");
        }
        
        return Ok(queryModel.Status.Value.GetEnumDisplayName());
    }
}
Run Code Online (Sandbox Code Playgroud)

该模型包含一个枚举

public class QueryModel
{
    /// <summary>
    /// The foo parameter
    /// </summary>
    /// <example>bar</example>
    public string? Foo { get; init; } = null;
    
    /// <summary>
    /// The status
    /// </summary>
    /// <example>on-hold</example>
    public Status? Status { get; set; } = null;
}
Run Code Online (Sandbox Code Playgroud)

枚举具有EnumMember我想用来反序列化的值的属性。

public enum Status
{
    [EnumMember(Value = "open")]
    Open,
    
    [EnumMember(Value = "on-hold")]
    OnHold
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,.NET 6 不考虑EnumMember反序列化时的情况。

目标是能够发送请求,例如

http://localhost:5000/Samples?Foo=bar&Status=on-hold 
Run Code Online (Sandbox Code Playgroud)

并让控制器的操作使用QueryModel正确的值反序列化Status.OnHoldEnumMember

我尝试过包含转换器的扩展库,但没有成功,但是在使用[FromQuery]. 请参阅https://github.com/Macross-Software/core/issues/30

我添加了一个项目来重现问题并作为沙箱提供解决方案** https://gitlab.com/sunnyatticsoftware/issues/string-to-enum-mvc/-/tree/feature/1-original-problem

注意:我需要一个解决方案,其中 Enum 不需要任何外部依赖项(只需 .NET sdk)。

vic*_*510 5

自定义枚举转换器可能是您的选择。通过利用现有的EnumConverter类,我们需要一个定制的ConvertFrom方法:

public class CustomEnumConverter : EnumConverter
{
    public CustomEnumConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type type) : base(type)
    {
    }

    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
    {
        if (value is string strValue)
        {
            try
            {
                foreach (var name in Enum.GetNames(EnumType))
                {
                    var field = EnumType.GetField(name);
                    if (field != null)
                    {
                        var enumMember = (EnumMemberAttribute)(field.GetCustomAttributes(typeof(EnumMemberAttribute), true).Single());
                        if (strValue.Equals(enumMember.Value, StringComparison.OrdinalIgnoreCase))
                        {
                            return Enum.Parse(EnumType, name, true);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                throw new FormatException((string)value, e);
            }
        }

        return base.ConvertFrom(context, culture, value);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将转换器装饰到您的 Model 类:

[TypeConverter(typeof(CustomEnumConverter))]
public enum Status
{
    [EnumMember(Value = "open")]
    Open,
    
    [EnumMember(Value = "on-hold")]
    OnHold
}
Run Code Online (Sandbox Code Playgroud)

然后我们就可以解析“on-hold”。您可能还想覆盖将值ConverTo()打印EnumMember为 swagger。这有点 hacky,但如果您想要一个纯粹的 .NET 解决方案,这应该是最小可行的解决方案之一。

在此输入图像描述