Change the JSON serialization settings of a single ASP.NET Core controller

12,100

Solution 1

ASP.NET Core 3.0+

You can achieve this with a combination of an Action Filter and an Output Formatter.

Things look a little different for 3.0+, where the default JSON-formatters for 3.0+ are based on System.Text.Json. At the time of writing, these don't have built-in support for a snake-case naming strategy.

However, if you're using Json.NET with 3.0+ (details in the docs), the SnakeCaseAttribute from above is still viable, with a couple of changes:

  1. JsonOutputFormatter is now NewtonsoftJsonOutputFormatter.
  2. The NewtonsoftJsonOutputFormatter constructor requires an argument of MvcOptions.

Here's the code:

public class SnakeCaseAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext ctx)
    {
        if (ctx.Result is ObjectResult objectResult)
        {
            objectResult.Formatters.Add(new NewtonsoftJsonOutputFormatter(
                new JsonSerializerSettings
                {
                    ContractResolver = new DefaultContractResolver
                    {
                        NamingStrategy = new SnakeCaseNamingStrategy()
                    }
                },
                ctx.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
                ctx.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value));
        }
    }
}

ASP.NET Core 2.x

You can achieve this with a combination of an Action Filter and an Output Formatter. Here's an example of what the Action Filter might look like:

public class SnakeCaseAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext ctx)
    {
        if (ctx.Result is ObjectResult objectResult)
        {
            objectResult.Formatters.Add(new JsonOutputFormatter(
                new JsonSerializerSettings
                {
                    ContractResolver = new DefaultContractResolver
                    {
                        NamingStrategy = new SnakeCaseNamingStrategy()
                    }
                },
                ctx.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>()));
        }
    }
}

Using OnActionExecuted, the code runs after the corresponding action and first checks to see if the result is an ObjectResult (which also applies to OkObjectResult thanks to inheritance). If it is an ObjectResult, the filter simply adds a customised version of a JsonOutputFormatter that will serialise the properties using SnakeCaseNamingStrategy. The second parameter in the JsonOutputFormatter constructor is retrieved from the DI container.

In order to use this filter, just apply it to the relevant controller:

[SnakeCase]
public class ControllerB : Controller { }

Note: You might want to create the JsonOutputFormatter/NewtonsoftJsonOutputFormatter ahead of time somewhere, for example - I've not gone that far in the example as that's secondary to the question at hand.

Solution 2

Ended up creating this method that I use on my end points:

{           
    // needed to get the same date and property formatting 
    // as the Search Service:
    var settings = new JsonSerializerSettings
    {
        ContractResolver = new DefaultContractResolver()
        {
            NamingStrategy = new SnakeCaseNamingStrategy()
        },
        DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffZ"
    };

    return Json(result, settings);
}

Solution 3

No need for action filters etc. Just override Json() in your controller and that's it.

public class MyController : Controller
{
    public override JsonResult Json(object data)
    {
        return base.Json(data, new JsonSerializerSettings {
            // set whataever default options you want
        });
    }
}
Share:
12,100
Kees C. Bakker
Author by

Kees C. Bakker

Senior Software Developer and Team Manager for Capital ID - a leading international supplier specialized in automating and managing marketing processes (MRM, MOM), using its software platform ID Manager. Specialties: C# / ASP.Net Html / CSS jQuery / JavaScript (T)SQL Visit my blog: KeesTalksTech.com Follow me: twitter.com/KeesTalksTech LinkedIn: linkedin.com/in/keescbakker

Updated on June 19, 2022

Comments

  • Kees C. Bakker
    Kees C. Bakker almost 2 years

    I'm having two controller controllers: ControllerA and ControllerB. The base class of each controller is Controller.

    The ControllerA needs to return JSON in the default format (camelCase). The ControllerB needs to return data in a different JSON format: snake_case.

    How can I implement this in ASP.NET Core 3.x and 2.1?

    I've tried the startup with:

    services
        .AddMvc()
        .AddJsonOptions(options =>
        {
            options.SerializerSettings.Converters.Add(new StringEnumConverter());
            options.SerializerSettings.ContractResolver = new DefaultContractResolver()
            {
                NamingStrategy = new SnakeCaseNamingStrategy()
            };
        })
        .AddControllersAsServices();
    

    But this will change the serialization for all controllers, not just for ControllerB. How can I configure or annotate this feature for 1 controller?