Как использовать перечисления с атрибутом EnumMember в FromBody ViewModel в запросе веб-API? - PullRequest
0 голосов
/ 07 октября 2019

Я пытаюсь реализовать метод HttpPost в проекте ASP.NET Core Web API с моделью представления [FromBody] и перечислениями. В прошлом связывание моделей представления с атрибутом [FromBody] работало хорошо.

В моем конкретном сценарии я хочу предложить конечную точку JSON, где я преобразую данное значение в перечисление C # с другими именами. Этот пример должен объяснить, чего я хочу достичь:

    public enum WeatherEnum
    {
        [EnumMember(Value = "good")]
        Good,

        [EnumMember(Value = "bad")]
        Bad
    }

Внутренне я хочу использовать WeatherEnum.Good и WeatherEnum.Bad, а потребитель моей конечной точки хочет использовать строчные значения. Поэтому я пытаюсь отобразить значения, которые будут переданы в теле JSON, в мое представление Enum.

Я прочитал об атрибуте EnumMember и StringEnumConverter. Я создал минимальный пример из нового шаблона ASP.NET Core Web API 3.0 (вам необходимо добавить эти пакеты NuGet Microsoft.Extensions.DependencyInjection, Microsoft.AspNetCore.Mvc.NewtonsoftJson и Newtonsoft.Json)

ConfigureServices :

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
    }
}

Моя конечная точка вызывается из Почтальона со следующим телом

{
  "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."
        ]
    }
}

Такое ощущение, что я пропускаю только одну строку где-то. Можно использовать Enums в моделях представления с атрибутом FromBody?

1 Ответ

1 голос
/ 11 октября 2019

Код из вопроса, который я написал, действительно имеет значение. В моем минимальном примере я забыл установить атрибут [Required] для перечисления. Однако тогда у меня была проблема, как метод должен реагировать, если тогда значение не установлено. Он правильно (?) Принял значение по умолчанию перечисления, которое не то, что я хотел.

Я искал вокруг и нашел это решение https://stackoverflow.com/a/54206737/225808 Перечисление имеет значение nullable, что не идеально, но по крайней мере яполучить подтверждение, и я получаю сообщение об ошибке, если значение отсутствует

Обновление / Предупреждение : Вы можете использовать решение, указанное выше, но! Кажется, что код скомпилируется, но вместо этого выдает сообщение об ошибке из вопроса. Далее я сравнил свой собственный проект с тестовым и заметил, что мне также нужно два пакета с двумя NuGet, чтобы все работало:

  • Microsoft.AspNetCore.Mvc.NewtonsoftJson
  • Newtonsoft.Json

Кажется, что Microsoft.AspNetCore.Mvc.NewtonsoftJson переопределяет поведение по умолчанию? Если кто-то может пролить некоторый свет на это, я буду рад это оценить.

Обновление 2 : Я также обновил указанное решение, чтобы проанализировать значение перечисления на основе 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}");
    }
}
...