Search code examples
c#asp.net-coreasp.net-core-mvcasp.net-core-webapi

Specify JsonSerializerOptions for HttpResults in ASP.NET Core MVC


With ASP.NET Core MVC, it's possible to return a result by multiple ways.

Controller action return types in ASP.NET Core web API :

  • Specific type
  • IActionResult
  • ...
  • HttpResult

But when the controller action returns HttpResult, ASP.NET Core MVC serialization options are ignored.

Example

Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddJsonOptions(
    o => o.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseUpper
);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();

app.Run();

FoosController.cs:

using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace WebApplication.Controllers;

[Route("foos")]
public class FoosController
{
    [HttpGet("raw")]
    public Foo Raw() => new Foo();

    [HttpGet("action-result")]
    public IActionResult Action() => new OkObjectResult(new Foo());

    [HttpGet("http-result")]
    public IResult Http() => Results.Ok(new Foo());

    [HttpGet("typed-result")]
    public Ok<Foo> Typed() => TypedResults.Ok(new Foo());
}

public class Foo
{
    public int Id { get; set; }
    public string Label { get; set; } = "";
}

The endpoints /raw and /action-result return :

{
  "ID": 0,
  "LABEL": ""
}

But the endpoints /http-result and /typed-result return :

{
  "id": 0,
  "label": ""
}

How can I specify serialization options for HttpResult?


Solution

  • MCV has Microsoft.AspNetCore.Mvc.JsonOptions to configure serialization options, HttpResult has Microsoft.AspNetCore.Http.Json.JsonOptions.

    You can configure serialization options for HttpResult like :

    builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(
        o => o.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseUpper
    );
    

    Trick to mutualise the configuration of serialization options between MVC and HttpResult :

    builder.Services.AddControllers().AddJsonOptions(
        o => o.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseUpper
    );
    builder.Services.AddSingleton(p =>
    {
        var mOpt = p.GetRequiredService<IOptions<Microsoft.AspNetCore.Mvc.JsonOptions>>().Value;
        var hOpt = new Microsoft.AspNetCore.Http.Json.JsonOptions();
        hOpt.SerializerOptions.AllowTrailingCommas = mOpt.JsonSerializerOptions.AllowTrailingCommas;
        hOpt.SerializerOptions.DefaultBufferSize = mOpt.JsonSerializerOptions.DefaultBufferSize;
        hOpt.SerializerOptions.NumberHandling = mOpt.JsonSerializerOptions.NumberHandling;
        hOpt.SerializerOptions.PreferredObjectCreationHandling = mOpt.JsonSerializerOptions.PreferredObjectCreationHandling;
        hOpt.SerializerOptions.PropertyNameCaseInsensitive = mOpt.JsonSerializerOptions.PropertyNameCaseInsensitive;
        hOpt.SerializerOptions.PropertyNamingPolicy = mOpt.JsonSerializerOptions.PropertyNamingPolicy;
        hOpt.SerializerOptions.ReadCommentHandling = mOpt.JsonSerializerOptions.ReadCommentHandling;
        hOpt.SerializerOptions.ReferenceHandler = mOpt.JsonSerializerOptions.ReferenceHandler;
        hOpt.SerializerOptions.TypeInfoResolver = mOpt.JsonSerializerOptions.TypeInfoResolver;
        hOpt.SerializerOptions.UnknownTypeHandling = mOpt.JsonSerializerOptions.UnknownTypeHandling;
        hOpt.SerializerOptions.UnmappedMemberHandling = mOpt.JsonSerializerOptions.UnmappedMemberHandling;
        hOpt.SerializerOptions.WriteIndented = mOpt.JsonSerializerOptions.WriteIndented;
        hOpt.SerializerOptions.TypeInfoResolverChain.Clear();
        mOpt.JsonSerializerOptions.TypeInfoResolverChain.ToList().ForEach(r => hOpt.SerializerOptions.TypeInfoResolverChain.Add(r));
        hOpt.SerializerOptions.Converters.Clear();
        mOpt.JsonSerializerOptions.Converters.ToList().ForEach(c => hOpt.SerializerOptions.Converters.Add(c));
        return Options.Create(hOpt);
    });
    

    Have to think about adapting it to each evolution of JsonSerializerOptions.