无法替换 ASP.Core 3 中的默认 JSON 合同解析器

Posted

技术标签:

【中文标题】无法替换 ASP.Core 3 中的默认 JSON 合同解析器【英文标题】:Cannot replace default JSON contract resolver in ASP.Core 3 【发布时间】:2020-01-27 20:05:19 【问题描述】:

在基于 .NET Core 3.0 框架创建基本 Web API 项目后,所有 API 响应都以驼峰形式出现。我从 System.Text.Json 安装了 SwashBuckle Swagger + 内置 JSON 序列化程序,特别是将枚举显示为字符串,一切都像以前一样工作。然后,我决定切换到 NSwag + NewtonSoftJson,因为带有动态和扩展对象的内置序列化程序的一些限制。现在,所有 API 响应都显示在 PascalCase 中,我既不能更改命名策略,也不能创建自定义合同解析器。

示例

https://forums.asp.net/t/2138758.aspx?Configure+SerializerSettings+ContractResolver

问题

我怀疑某些包可能会在幕后覆盖合同解析器。如何确保 API 服务仅使用我在启动时分配的自定义合同解析器并忽略所有其他类似设置?

自定义 JSON 合约解析器

public class CustomContractResolver : IContractResolver

  private readonly IHttpContextAccessor _context;
  private readonly IContractResolver _contract;
  private readonly IContractResolver _camelCaseContract;

  public CustomContractResolver(IHttpContextAccessor context)
  
    _context = context;
    _contract = new DefaultContractResolver();
    _camelCaseContract = new CamelCasePropertyNamesContractResolver();
  

  // When API endpoint is hit, this method is NOT triggered

  public JsonContract ResolveContract(Type value)
  
    return _camelCaseContract.ResolveContract(value);
  

控制器

[ApiController]
public class RecordsController : ControllerBase

  [HttpGet]
  [Route("services/records")]
  [ProducesResponseType(typeof(ResponseModel<RecordEntity>), 200)]
  public async Task<IActionResult> Records([FromQuery] QueryModel queryModel)
  
    var response = new ResponseModel<RecordEntity>();

    return Content(JsonConvert.SerializeObject(response), "application/json"); // NewtonSoft serializer
  

Startup.cs

public void ConfigureServices(IServiceCollection services)

  services
    .AddCors(o => o.AddDefaultPolicy(builder => builder
      .AllowAnyOrigin()
      .AllowAnyHeader()
      .AllowAnyMethod()));

  services
    .AddControllers(o => o.RespectBrowserAcceptHeader = true)
    /*
    .AddJsonOptions(o =>
    
      o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
      o.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
      o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    )
    */
    .AddNewtonsoftJson(o =>
    
      o.UseCamelCasing(true);
      o.SerializerSettings.Converters.Add(new StringEnumConverter());
      //o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver  NamingStrategy = new CamelCaseNamingStrategy() ;
      o.SerializerSettings.ContractResolver = new CustomContractResolver(new HttpContextAccessor());
    );

  services.AddOpenApiDocument(o =>   // NSwag
  
    o.PostProcess = document =>
    
      document.Info.Version = "v1";
      document.Info.Title = "Demo API";
    ;
  );

  DataConnection.DefaultSettings = new ConnectionManager(DatabaseOptionManager.Instance); // LINQ to DB


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

  if (env.IsDevelopment())
  
    app.UseDeveloperExceptionPage();
  

  app.UseCors(o => o.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
  app.UseRouting();
  app.UseAuthorization();
  app.UseEndpoints(o => o.MapControllers());

  app.UseOpenApi();                                // NSwag
  app.UseSwaggerUi3(o => o.Path = "/v2/docs");
  app.UseReDoc(o => o.Path = "/v1/docs");

【问题讨论】:

【参考方案1】:

仍然不明白为什么自定义合约解析器不是由 API 端点触发,但找到了一个适合我将 API 切换为驼峰式的组合。随意解释为什么它会这样工作。

services.AddControllers(o => o.RespectBrowserAcceptHeader = true)

  // Options for System.Text.Json don't affect anything, can be uncommented or removed

  //.AddJsonOptions(o =>
  //
  //  o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
  //  o.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
  //  o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
  //)

  .AddNewtonsoftJson(o =>
  
    o.UseCamelCasing(true);
    o.SerializerSettings.Converters.Add(new StringEnumConverter());

    // This option below breaks global settings, so had to comment it

    //o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver 
    //
    //  NamingStrategy = new CamelCaseNamingStrategy()
    //;
  );

JsonConvert.DefaultSettings = () => new JsonSerializerSettings

  ContractResolver = new CamelCasePropertyNamesContractResolver()
;

想法来自这个article。

NewtonSoft 允许设置全局序列化设置,而不考虑 MVC、Web API 和其他框架。

【讨论】:

之所以有效,是因为 Json.NET 作为一个独立的 nuget 包,有自己的内置全局序列化程序设置,与 asp.net 配置不同。 asp.net 将使用其设置,但是当您通过调用 JsonConvert.SerializeObject(response) 直接使用 Json.NET 时,Json.NET 没有内置的 asp.net 设置知识,而是使用自己的设置。请参阅:Json.net global settings。

以上是关于无法替换 ASP.Core 3 中的默认 JSON 合同解析器的主要内容,如果未能解决你的问题,请参考以下文章

从 asp.core 2.2 迁移到 3.1。 Autofac.Core.DependencyResolutionException

ASP .NET Core 身份登录管理器

Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token

无法替换 php 中的回车符和换行符

java中的lis数组转为json数据

在 POST 数据之前替换 JSON 中的文本