如何在 Web API 请求的 FromBody ViewModel 中使用具有 EnumMember 属性的枚举?

Posted

技术标签:

【中文标题】如何在 Web API 请求的 FromBody ViewModel 中使用具有 EnumMember 属性的枚举?【英文标题】:How to use enums with EnumMember attribute in FromBody ViewModel in Web API Request? 【发布时间】:2020-02-04 18:56:57 【问题描述】:

我正在尝试使用 [FromBody] 视图模型和枚举在 ASP.NET Core Web API 项目中实现 HttpPost 方法。过去,使用[FromBody] 属性绑定视图模型效果很好。

在我的特定场景中,我想提供一个 JSON 端点,我将在其中将给定值转换为具有不同名称的 C# 枚举。这个例子应该解释我想要实现的目标:

公共枚举 WeatherEnum [枚举成员(值 = “好”)] 好的, [枚举成员(值 = “坏”)] 坏的

在内部,我想使用WeatherEnum.GoodWeatherEnum.Bad,而我的端点的消费者想使用小写值。因此,我试图将在 JSON 正文中传递的值映射到我的 Enum 表示。

我已经阅读了EnumMember 属性和StringEnumConverter。我从新的 ASP.NET Core Web API 3.0 模板创建了一个最小示例(您需要添加这些 NuGet 包 Microsoft.Extensions.DependencyInjectionMicrosoft.AspNetCore.Mvc.NewtonsoftJsonNewtonsoft.Json

配置服务

public void ConfigureServices(IServiceCollection services)

    services.AddMvc(options =>
    
    ).SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
    .AddNewtonsoftJson(json =>
    
        json.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        json.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
    );
    services.AddControllers();

WheatherForecastController

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Runtime.Serialization;

namespace WebAPITestEnum.Controllers

    [ApiController]
    [Produces("application/json")]
    [Consumes("application/json")]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    
        [HttpPost]
        [Route("method")]
        public ActionResult<QueryResponseClass> TestMethod([FromBody] QueryRequestClass request)
        
            // do something with the request ...

            return new QueryResponseClass()
            
                Foo = "bar"
            ;
        
    

    public class QueryRequestClass
    
        public WeatherEnum Weather  get; set; 
    

    public class QueryResponseClass
    
        public string Foo  get; set; 
    


    [JsonConverter(typeof(StringEnumConverter))]
    public enum WeatherEnum
    
        [EnumMember(Value = "good")]
        Good,

        [EnumMember(Value = "bad")]
        Bad
    

Postman 使用以下正文调用我的端点


  "Weather": "good"

导致此错误:


    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|245d862e-4ab01d3956be5f60.",
    "errors": 
        "$.Weather": [
            "The JSON value could not be converted to WebAPITestEnum.Controllers.WeatherEnum. Path: $.Weather | LineNumber: 1 | BytePositionInLine: 18."
        ]
    

感觉好像我只是在某个地方漏掉了一行。可以在具有FromBody 属性的视图模型中使用枚举吗?

【问题讨论】:

我刚刚将您的代码复制并粘贴到一个 .NET 核心 WebApi 应用程序中,从我的角度来看,它运行没有问题。我所做的只是dotnet new webapi,复制并粘贴你上面的代码,然后dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson --version 3.0.0。它反序列化您拥有的 JSON 有效负载,断点显示天气是“好”或“坏”……取决于请求正文的内容。 我测试了您的代码,它使用参数Good 成功运行。那么您是否进入该操作并获取FromBody 参数?您可以通过设置断点来检查它。如果可以成功进入行动,我建议你可以提供更多关于你的TestMethod的细节。 【参考方案1】:

我发布的问题中的代码确实有效。在我的最小示例中,我忘记在枚举上设置 [Required] 属性。但是,如果没有设置值,那么我遇到了该方法应该如何反应的问题。它正确地(?)假定了枚举的默认值,这不是我想要的。

我四处搜索并找到了这个解决方案https://***.com/a/54206737/225808枚举是可空的,这并不理想,但至少我有验证,如果值丢失,我会收到一条错误消息

更新/警告:您可以使用上面提到的解决方案,但是!似乎代码会编译,但会从问题中抛出错误消息。我进一步将我自己的项目与测试项目进行了比较,发现我还需要两个包含 2 个 NuGet 包才能使一切正常运行:

Microsoft.AspNetCore.Mvc.NewtonsoftJson Newtonsoft.Json

似乎 Microsoft.AspNetCore.Mvc.NewtonsoftJson 会覆盖默认行为?如果有人能对此有所了解,我将不胜感激。

更新2:我还更新了引用的so解决方案,根据EnumMemberAttribute解析枚举值:

[JsonConverter(typeof(CustomStringToEnumConverter<WeatherEnum>))]
public enum WeatherEnum

    [EnumMember(Value = "123good")]
    Good,

    [EnumMember(Value = "bad")]
    Bad


public class CustomStringToEnumConverter<T> : StringEnumConverter

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        if (string.IsNullOrEmpty(reader.Value?.ToString()))
        
            return null;
        
        try
        
            return EnumExtensions.GetValueFromEnumMember<T>(reader.Value.ToString());
        
        catch (Exception ex)
        
            return null;
        
    


public static class EnumExtensions

    public static T GetValueFromEnumMember<T>(string value)
    
        var type = typeof(T);
        if (!type.IsEnum) throw new InvalidOperationException();
        foreach (var field in type.GetFields())
        
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(EnumMemberAttribute)) as EnumMemberAttribute;
            if (attribute != null)
            
                if (attribute.Value == value)
                    return (T)field.GetValue(null);
            
            else
            
                if (field.Name == value)
                    return (T)field.GetValue(null);
            
        
        throw new ArgumentException($"unknow value: value");
    

【讨论】:

不检查 type.IsEnum,从 C# 7.3 开始,您可以检查 public static T GetValueFromEnumMember&lt;T&gt;(string value) where T : Enum

以上是关于如何在 Web API 请求的 FromBody ViewModel 中使用具有 EnumMember 属性的枚举?的主要内容,如果未能解决你的问题,请参考以下文章

同时使用 [FromUri] 和 [FromBody] 绑定复杂的 Web Api 方法参数

Web API HttpDelete - 如何调用删除 API 方法并在 [FromBody] 中发送模型

ASP.NET Post, FromBody 接参总是null 空值. Web api 前端传递是有值的,怎么回事?

Web Api如何传递POST请求

使用 HttpClient 和 Web API 方法 [FromBody] 参数发布到 Web API 最终为空

Web API Post 方法 frombody 为空