Enum сериализация в маршрутах - PullRequest
0 голосов
/ 13 ноября 2018

У нас есть веб-API, который использует сериализацию в виде змеи для всех свойств, включая перечисления, чтобы включить его, мы использовали это при запуске:

services
    .AddMvcCore()
    .AddJsonOptions(opt =>
    {
        opt.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
        opt.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
        opt.SerializerSettings.ContractResolver = new DefaultContractResolver
        {
            NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = true }
        };
        opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        opt.SerializerSettings.Converters.Add(new StringEnumConverter());
    })
    .AddApiExplorer()
    .AddJsonFormatters(j => j.ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() { ProcessDictionaryKeys = true } })

Это прекрасно работает со свойствами, но у нас есть проблемы с маршрутизацией и перечислениями, например, у нас есть это перечисление (мы также пробовали с JsonProperty, но терпим неудачу таким же образом):

[DataContract(Name = "document_type")]
public enum DocumentType
{
    [EnumMember(Value = "passport")]
    Passport,

    [EnumMember(Value = "proof_of_address")]
    ProofOfAddress,
}

и мы пытаемся искать документы по типу, поэтому у нас есть этот маршрут:

/clients/{clientId:guid/documents/{documentType}

и это в контроллере:

[HttpGet]
[Route("/clients/{clientId:guid}/documents/{documentType}")]
public async Task<IActionResult> FindClientDocuments([FromRoute] Guid clientId, [FromRoute] DocumentType documentType)

с этим маршрутом все отлично работает:

/ клиенты / 60a00cd4-59e2-4f52-871a-4029370f6dd8 / документы / ProofOfAddress

но не работает с этим:

клиенты / 60a00cd4-59e2-4f52-871a-4029370f6dd8 / документы / proof_of_address

В последнем случае перечисление всегда является значением по умолчанию, или, если мы добавляем фильтр действий, возникает ошибка «Значение« proof_of_address »недопустимо».

Есть ли способ заставить этот сценарий работать, кроме как пытаться преобразовать значение самостоятельно с помощью фильтра?

Спасибо

1 Ответ

0 голосов
/ 14 ноября 2018

Я думаю, что этот вопрос можно разбить на 2 части,

  1. Пользовательская строка для преобразования в Enum с использованием EnumMemberAttribute
  2. Указание MVC использовать это пользовательское преобразование для привязки входов модели.

Я собираюсь ответить на 2 часть, и, надеюсь, первая часть должна быть относительно простой для реализации / исследования. Я включил ссылку на вопрос непосредственно о первой части.

Указание MVC использовать пользовательское преобразование перечисления для привязки входов модели.

Получение значений от клиента всегда осуществляется посредством привязки модели, тогда как сериализация json в основном связана с форматированием данных ответа в виде json для вывода. Таким образом, ваше решение должно рассмотреть возможность связывания модели десериализации перечислений, которую вы хотите использовать. (Реализация по умолчанию преобразования строк enum <=> не рассматривает атрибуты).

Я попробовал следующее пользовательское связующее для моего enum, и оно работало хорошо. FooType мое перечисление:

public enum FooType
{
    [Description("test")]
    TestFoo,
    [Description("another")]
    AnotherFooType
}

Я пропустил атрибут [EnumMember] и выбрал атрибут [Description] только потому, что хотел использовать Утилита Enum от Humanizer , но вы можете реализовать свой собственный способ получения значений перечисления из EnumMember атрибут и просто заменить мой вызов DeHumanizeTo<FooType>(), пример можно найти на этот вопрос .

    public class FooTypeBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext) => Task.Run(() => this.BindModel(bindingContext));

        private void BindModel(ModelBindingContext bindingContext)
        {
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (valueProviderResult.Length == 0)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, "No value was provided for enum");
                return;
            }

            var stringValue = valueProviderResult.FirstValue;
            try
            {
                bindingContext.Model = this.FromString(stringValue);
            }
            catch (NoMatchFoundException ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
            }
        }

        private FooType FromString(string input)
        {
            //Here you should implement your custom way of checking the [EnumMember] attribute, and convert input into your enum.
            if (Enum.TryParse(typeof(FooType), input, true, out object value))
            {
                return (FooType)value;
            }
            else return input.DehumanizeTo<FooType>();
        }
    }

В моем контроллере я тогда говорю MVC использовать это связующее для привязки моих параметров следующим образом:

  [HttpGet("{fooType}")]
    public IActionResult GetFooItems([ModelBinder(BinderType = typeof(FooTypeBinder))] FooType fooType)
    {
        // Do your thing, your enum fooType is bound correctly
        return this.Ok(fooType);
    }

Надеюсь, это поможет.

...