SnakeCaseNamingStrategy и JsonPatch в ядре ASP.NET

Есть ли способ зарегистрировать / использовать "глобальный" ContractResolver при использовании пакета ApsNetCore.JsonPatch (2.1.1)?

Я столкнулся с проблемой, когда путь не был решен, потому что свойства в моих моделях находятся в PascalCase, а путь в JsonPatch - в SnakeCase.

В этом случае мне нужно установить ContractResolver для JsonPatchDocument в значение Default/Globally зарегистрированного ContractResolver в файле Startup.cs.

Это работает, но мне придется делать это для каждого патч-маршрута, который я собираюсь реализовать.

Начальная конфигурация:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
  services
    .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver
    {
      NamingStrategy = new SnakeCaseNamingStrategy()
    })
}

контроллер:

[HttpPatch("{id}"]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> Patch(string id,
    [FromBody] JsonPatchDocument<Entity> patchEntity)
{
    ...
    patchEntity.ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    };
    patchEntity.ApplyTo(entity);
    ...

1 ответ

Решение

Похоже, что не существует простого способа повлиять на ContractResolver который используется при создании экземпляра JsonPatchDocument<T>, Экземпляры этого класса создаются TypedJsonPatchDocumentConverter, как этот фрагмент кода показывает:

var container = Activator.CreateInstance(
    objectType,
    targetOperations,
    new DefaultContractResolver());

Здесь ясно, что DefaultContractResolver жестко закодирован как второй аргумент при создании экземпляра JsonPatchDocument<T>,

Одним из вариантов решения этой проблемы при использовании ASP.NET Core MVC является использование фильтра действий, который позволяет вносить изменения в любые аргументы, передаваемые в действие. Вот основной пример:

public class ExampleActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext ctx)
    {
        // Find a single argument we can treat as IJsonPatchDocument.
        var jsonPatchDocumentActionArgument = ctx.ActionArguments.SingleOrDefault(
            x => typeof(IJsonPatchDocument).IsAssignableFrom(x.Value.GetType()));

        // Here, jsonPatchDocumentActionArgument.Value will be null if none was found.
        var jsonPatchDocument = jsonPatchDocumentActionArgument.Value as IJsonPatchDocument;

        if (jsonPatchDocument != null)
        {            
            jsonPatchDocument.ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = new SnakeCaseNamingStrategy()
            };
        }
    }
}

ActionExecutingContext класс, переданный здесь, включает в себя ActionArguments свойство, которое используется в этом примере, чтобы попытаться найти аргумент типа IJsonPatchDocument, Если один найден, мы перезаписываем ContractResolver соответственно.

Чтобы использовать этот новый фильтр действий, вы можете добавить его в контроллер, действие или зарегистрировать его глобально. Вот как зарегистрировать это глобально (есть много ответов для других вариантов, поэтому я не буду углубляться в это здесь):

services.AddMvc(options =>
{
    options.Filters.Add(new ExampleActionFilterAttribute());
});
Другие вопросы по тегам