0

After creating basic Web API project based on .NET Core 3.0 framework, all API responses were coming in camel case. I installed SwashBuckle Swagger + built-in JSON serializer from System.Text.Json, specifically, to display enums as strings, everything worked as before. Then, I decided to switch to NSwag + NewtonSoftJson, because of some limitations of built-in serializer with dynamic and expando objects. Now, all API responses are displayed in PascalCase and I cannot change neither naming policy, nor even create custom contract resolver.

Example

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

Question

I suspect that maybe some package overrides contract resolver behind the scene. How to make sure that API service uses ONLY custom contract resolver that I assign at startup and ignores all other similar settings?

Custom JSON contract resolver:

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

Controller:

[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 Answer 1

1

Still don't understand why custom contract resolver is not triggered by API endpoint, but found a combination that works for me to switch API to camel case. Feel free to explain why it works this way.

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()
};

Idea was taken from this article.

NewtonSoft allows to set global serialization settings disregarding MVC, Web API, and other frameworks.

Sign up to request clarification or add additional context in comments.

1 Comment

This works because Json.NET, as a standalone nuget package, has its own built-global serializer settings that are distinct from the asp.net configuration. asp.net will use its settings but when you use Json.NET directly by calling JsonConvert.SerializeObject(response) then Json.NET has no built-in knowledge of asp.net settings and uses its own settings. See: Json.net global settings.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.