如何在 Web API 请求的 FromBody ViewModel 中使用带有 EnumMember 属性的枚举?
How to use enums with EnumMember attribute in FromBody ViewModel in Web API Request?
我正在尝试在 ASP.NET Core Web API 项目中使用 [FromBody]
视图模型和枚举实现 HttpPost 方法。过去,使用 [FromBody]
属性绑定视图模型效果很好。
在我的特定场景中,我想提供一个 JSON 端点,我可以在其中将给定值转换为具有不同名称的 C# 枚举。这个例子应该解释我想要实现的目标:
public enum WeatherEnum
{
[EnumMember(Value = "good")]
Good,
[EnumMember(Value = "bad")]
Bad
}
在内部,我想使用 WeatherEnum.Good
和 WeatherEnum.Bad
并且我端点的使用者想要使用小写值。因此,我正在尝试将 JSON 正文中传递的值映射到我的枚举表示形式。
我已经阅读了 EnumMember
属性和 StringEnumConverter
。我从新的 ASP.NET Core Web API 3.0 模板创建了一个最小示例(您需要添加这些 NuGet 包 Microsoft.Extensions.DependencyInjection
、Microsoft.AspNetCore.Mvc.NewtonsoftJson
和 Newtonsoft.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();
}
WeatherForecastController:
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
属性的视图模型中使用枚举吗?
我发布的问题中的代码确实有效。在我的最小示例中,我忘记了在枚举上设置 [Required]
属性。但是,然后我遇到了问题,如果未设置值,该方法应该如何反应。它正确地(?)假定了枚举的默认值,这不是我想要的。
我四处搜索并找到了这个解决方案 枚举可以为 null,这并不理想,但至少我进行了验证,如果缺少该值我会收到一条错误消息
Update/Warning:您可以使用上面提到的解决方案,但是!代码似乎可以编译,但会抛出问题中的错误消息。我进一步将自己的项目与测试项目进行了比较,发现我还需要两个 include 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}");
}
}
我正在尝试在 ASP.NET Core Web API 项目中使用 [FromBody]
视图模型和枚举实现 HttpPost 方法。过去,使用 [FromBody]
属性绑定视图模型效果很好。
在我的特定场景中,我想提供一个 JSON 端点,我可以在其中将给定值转换为具有不同名称的 C# 枚举。这个例子应该解释我想要实现的目标:
public enum WeatherEnum { [EnumMember(Value = "good")] Good, [EnumMember(Value = "bad")] Bad }
在内部,我想使用 WeatherEnum.Good
和 WeatherEnum.Bad
并且我端点的使用者想要使用小写值。因此,我正在尝试将 JSON 正文中传递的值映射到我的枚举表示形式。
我已经阅读了 EnumMember
属性和 StringEnumConverter
。我从新的 ASP.NET Core Web API 3.0 模板创建了一个最小示例(您需要添加这些 NuGet 包 Microsoft.Extensions.DependencyInjection
、Microsoft.AspNetCore.Mvc.NewtonsoftJson
和 Newtonsoft.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();
}
WeatherForecastController:
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
属性的视图模型中使用枚举吗?
我发布的问题中的代码确实有效。在我的最小示例中,我忘记了在枚举上设置 [Required]
属性。但是,然后我遇到了问题,如果未设置值,该方法应该如何反应。它正确地(?)假定了枚举的默认值,这不是我想要的。
我四处搜索并找到了这个解决方案
Update/Warning:您可以使用上面提到的解决方案,但是!代码似乎可以编译,但会抛出问题中的错误消息。我进一步将自己的项目与测试项目进行了比较,发现我还需要两个 include 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}");
}
}