6

My application requires the (almost default) JSON serialization settings:

services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddJsonOptions(options =>
            {
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                options.SerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
                options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
            });

For one controller only, I need to use a different naming strategy for both input (where I use model binding with [FromBody] myComplexObject and output with

options.SerializerSettings.ContractResolver = new DefaultContractResolver();

My question is virtually identical to Web API: Configure JSON serializer settings on action or controller level with the exception that I'm asking for AspNet Core 2.2+, in which IControllerConfiguration is no longer existent.

The Core 2.1+ equivalent question has a response here: Configure input/output formatters on controllers with ASP.NET Core 2.1

The answers there appear slightly fragmented or incomplete - it's hard to imagine that there is no easier way to achieve this. Would anyone have an idea on how to use a DefaultContractResolver for all input and output within a single controller?

ExternalUse
  • 2,053
  • 2
  • 25
  • 37

3 Answers3

9

The answer you link works well enough, but you can extend it further by wrapping it in an attribute that you can apply to any action or controller. For example:

public class JsonConfigFilterAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is ObjectResult objectResult)
        {
            var serializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new DefaultContractResolver()
            };

            var jsonFormatter = new JsonOutputFormatter(
                serializerSettings, 
                ArrayPool<char>.Shared);

            objectResult.Formatters.Add(jsonFormatter);
        }

        base.OnResultExecuting(context);
    }
}

And just add it to action methods or controllers:

[JsonConfigFilter]
public ActionResult<Foo> SomeAction()
{
    return new Foo
    {
        Bar = "hello"
    };
}
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • 1
    Thanks @DavidG - that won't take care of the input formatter for `[FromBody] object` though, or would it? I thought that is the missing piece in the original question as well. – ExternalUse May 14 '19 at 10:07
  • Hmm you may be right. In that case you may be better off with a custom model binder. – DavidG May 14 '19 at 10:19
  • 1
    @ExternalUse Have you tried the input side of this? I don't think the JSON input formatter cares too much about the case inside of the request, so it should work with `SomeProperty` and `someProperty` without any changes. It also works with `SOMePRoperTY` - if you provide multiple variations in the JSON the last one wins. – Kirk Larkin May 14 '19 at 11:08
  • The answer provided works fine (thank you!); it is worth making a note of @KirkLarkin 's comment above. The input filter does indeed appear to ignore the casing. – ExternalUse May 14 '19 at 13:02
  • I expect `objectResult.Formatters` to include mvc OutputFormatters added in mvc configuration, but it's always empty. `IControllerConfiguration.Initialize` had `HttpControllerSettings settings` parameter which has all the mvc outputformatters added. That's a bit different. – uygar donduran Dec 04 '20 at 12:36
  • .NET Core version of the action filter: https://stackoverflow.com/a/73866496/379279 – xhafan Sep 27 '22 at 11:02
  • @xhafan This is already a .NET Core version. Your code just implements the lower-level interface. – DavidG Sep 27 '22 at 11:12
  • @DavidG ok, I remember now - I needed something for all controllers, thanks for clarification. – xhafan Sep 27 '22 at 12:21
1

For Global Setting in Startup.cs, having installed Newtonsoft.json, you will have this

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

For Individual Controller, you can override the global setting below

 public JsonResult GetStates()
    {
        var model = new List<StateObject>();
        if (!string.IsNullOrEmpty(id))
        {
            var schedule = _settingsService.GetStates().ToList();
            return Json(new SelectList(schedule, "StateCode", "Name"), new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() });
        }
        else
            return Json(new SelectList(model, "StateCode", "Name"), new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() });
    }

Let me know if this solves your problem, or you need further Assitance.

Samuel Akosile
  • 331
  • 1
  • 2
  • 9
  • 1
    Thanks @Samuel, that takes care of the Output problem but not about ModelBinding on a POST with a `[FromBody] object` parameter. The global setting in the Startup class is not an option since for most other controllers, the default setting is correct. Only one controller requires a `DefaultContractResolver` – ExternalUse May 14 '19 at 10:05
  • This kind of solved the issue. But for each request $id is getting appended. For example, for first request $id starts with 1, but for all subsequent requests $id starts with 27,55... https://stackoverflow.com/questions/59832635/is-there-a-way-to-reset-id-in-json-response-for-each-http-request Can anybody help me here? – Nainesh Patel Jan 21 '20 at 00:36
1

Just override Json() in your controller

public class MyController : Controller
{
    public override JsonResult Json(object data)
    {
        return base.Json(data, new JsonSerializerSettings {
            // set whataever options you want
        });
    }
}
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149