Reputation: 33421
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?
Upvotes: 43
Views: 26464
Reputation: 99
In my case, I had to prevent camelCase property naming policy, at action level only, by sticking to System.Text.Json
.
So, I combined @Kirk's answer and another one from here :
public class NoPropertyNamingPolicyAttribute : ActionFilterAttribute
{
private static readonly SystemTextJsonOutputFormatter SSystemTextJsonOutputFormatter = new SystemTextJsonOutputFormatter(new JsonSerializerOptions
{
// TypeInfoResolver is required in dotnet 8
TypeInfoResolver = new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver(),
// do not apply any policy,
// leave the property names as they are defined in the TestResponse class:
PropertyNamingPolicy = null,
// to apply camelCase policy:
//PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
});
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult objectResult)
{
objectResult.Formatters.Add(SSystemTextJsonOutputFormatter);
}
}
}
Usage:
[HttpGet("test")]
[NoPropertyNamingPolicy]
public TestResponse TestAction()
{
return new TestResponse();
}
Upvotes: 6
Reputation: 89
Follows the majority of the details from 3.0+ section. Although now, NewtonsoftJsonOutputFormatter(JsonSerializerSettings, ArrayPool<Char>, MvcOptions)
is obsolete docs.
The most updated version adds a new parameter on the end as MvcNewtonsoftJsonOptions
. This is consistent with Microsoft working with Newtonsoft. I would assume from Microsoft comment
The Newtonsoft.Json.JsonSerializerSettings. Should be either the application-wide settings (SerializerSettings) or an instance CreateSerializerSettings() initially returned.
...that you need to configure your service controllers to NewtonSoftJson if you're using it. Otherwise adding a null
or new MvcNewtonsoftJsonOptions()
value could be comparable.
servicesCollection.AddControllers()
.AddNewtonsoftJson(options => options.SerializerSettings
.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore);
I find that overriding the ActionFilterAttribute.OnResultExecutionAsync
call is a cleaner route. By injecting in-between the call, you grab it before the result is finalized. Then call it's base to continue the actions it intended to trigger.
await base.OnResultExecutionAsync(ctx, next);
While this is a preference, you can override OnResultExecuting()
to receive similar results, and not needing to call it's base function.
Here's the code:
public class SnakeCaseAttribute : ActionFilterAttribute
{
public async override Task OnResultExecutionAsync(ResultExecutingContext ctx, ResultExecutionDelegate next)
{
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,
new MvcNewtonsoftJsonOptions()));
}
await base.OnResultExecutionAsync(ctx, next);
}
}
Upvotes: 2
Reputation: 60832
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
});
}
}
Upvotes: 8
Reputation: 93233
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:
JsonOutputFormatter
is now NewtonsoftJsonOutputFormatter
.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));
}
}
}
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.
Upvotes: 47
Reputation: 33421
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);
}
Upvotes: 3