1

I am using

  • .NET Framework 4.6.1
  • Microsoft.AspNet.WebApi 5.2.4
  • ASP.NET uses Newtonsoft.Json 11.0.2

In Global.asax I specify that I want to use the SnakeCaseNamingStrategy for my JSON serialization:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        ...

        var formatters = GlobalConfiguration.Configuration.Formatters;

        // Remove the xmlformatter
        formatters.Remove(formatters.XmlFormatter);

        // Ignore reference loops
        formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
            = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

        // Use snake_case
        formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver()
        {
            NamingStrategy = new SnakeCaseNamingStrategy()
        };
    }
}

This works great when returning OK and most other success indicating status codes (200-300):

[HttpGet, Route("api/Test")]
public IHttpActionResult Test()
{
    return Ok(new { Message = "Hello World!" });
}

Returns:

{
    "message": "Hello World!"
}

But, when returning any error code or exception, ASP.NET seems to ignore any formatter settings:

[HttpGet, Route("api/Test")]
public IHttpActionResult Test()
{
    return BadRequest("Hello World!");
}

Returns:

{
    "Message": "Hello World!" // 'Message' is not snake_case
}

And

[HttpGet, Route("api/Test")]
public IHttpActionResult Test()
{
    var runtimeErroredArray = new int [2];
    runtimeErroredArray[2] = 5; // runtime error
    return Ok();
}

Returns:

{
    "Message": "An error has occurred.",
    "ExceptionMessage": "Index was outside the bounds of the array.", // Should be 'exception_message'
    "ExceptionType": "System.IndexOutOfRangeException", // "System.IndexOutOfRangeException" can stay the same obviously, but 'ExceptionType' should be 'exception_type'
    "StackTrace": "---"
}

I don't understand why this happens, but I would mainly like to know how to solve it.

The question:

Is there a way I can force exception messages and error code messages follow the GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver I set in Global.asax (using SnakeCaseNamingStrategy)?

EDIT:

I have tried setting JsonConvert.DefaultSettings in Global.asax, but that seemed to be ignored, even when returning an Ok response now.

New code in Global.asax:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        ...

        // Overwrite the default settings
        JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
        {
            // Ignore reference loops
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            // Use snake_case
            ContractResolver = new DefaultContractResolver()
            {
                NamingStrategy = new SnakeCaseNamingStrategy()
            }
        };
    }
}

return Ok(new { Message = "Hello World!" }); (like in the first example) in action Test now returns:

{
    "Message": "Hello World!"
}

JsonConvert.DefaultSettings Seems to be ignored completely by ASP.NET.

Joas
  • 1,796
  • 1
  • 12
  • 25

1 Answers1

2

After quite some research I pinpointed some differences in the ASP.NET Mvc source code (see https://github.com/aspnet and https://github.com/ASP-NET-MVC) regarding returning success codes and failure codes.

These differences all had one crucial similarity which pointed me to the solution of this problem.

Explanation

In ASP.NET Mvc there are 2 extension methods for creating HttpResponseMessage's

After looking at both methods I didn't think I saw anything that could cause returning error codes or throwing exceptions serialize incorrectly. But after looking at BadRequestErrorMessageResult, I found out CreateErrorResponse and BadRequestErrorMessageResult use the HttpError class to pass their information to the serializer.

Due to HttpError extending System.Collections.Generic.Dictionary<string,object> the serializer did not serialize the HttpError with the settings I specified, because Dictioneries are treated differently during serialization.

TL;DR

Whenever ASP.NET Mvc returns an error; it will use the HttpError class, which is a Dictionary<string, object>. Dictionaries have separate settings for serialization.

The Answer

You have to specify you want to parse Dictionary keys in your serialization settings:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        // ...

        var formatters = GlobalConfiguration.Configuration.Formatters;

        // Remove the xmlformatter
        formatters.Remove(formatters.XmlFormatter);

        // Ignore reference loops
        formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
            = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

        // Use snake_case
        formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver()
        {
            NamingStrategy = new SnakeCaseNamingStrategy()
            {
                ProcessDictionaryKeys = true // <--- Use SnakeCaseNamingStrategy for Dictionaries (So HttpError's)
            }
        };
    }
}
Joas
  • 1,796
  • 1
  • 12
  • 25