C#ASP.NET错误响应未使用GlobalConfiguration.Configuration.Formatters设置

时间:2018-07-16 15:01:49

标签: c# asp.net asp.net-web-api

我正在使用

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

Global.asax中,我指定要对我的JSON序列使用SnakeCaseNamingStrategy:

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()
        };
    }
}

当返回OK以及大多数其他成功指示状态码(200-300)时,此方法非常有用:

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

返回:

{
    "message": "Hello World!"
}

但是,当返回任何错误代码或异常时,ASP.NET似乎会忽略任何格式化程序设置:

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

返回:

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

还有

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

返回:

{
    "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": "---"
}

我不明白为什么会这样,但是我主要想知道如何解决它。

问题:

有没有办法强制异常消息和错误代码消息遵循我在GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver中设置的Global.asax(使用SnakeCaseNamingStrategy)?

编辑:

我尝试在JsonConvert.DefaultSettings中设置Global.asax,但是即使现在返回Ok响应,这似乎也被忽略了。

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!" });中的

Test(如第一个示例中所示)返回:

{
    "Message": "Hello World!"
}

JsonConvert.DefaultSettings似乎被ASP.NET完全忽略。

1 个答案:

答案 0 :(得分:1)

经过大量研究,我指出了ASP.NET Mvc源代码(请参见https://github.com/aspnethttps://github.com/ASP-NET-MVC)在返回成功代码和失败代码方面的一些区别。

这些差异都有一个至关重要的相似性,这使我找到了解决此问题的方法。

说明

在ASP.NET Mvc中,有2种扩展方法可用于创建HttpResponseMessage

查看了这两种方法后,我认为没有发现任何可能导致返回错误代码或引发异常序列化的错误。但是看了BadRequestErrorMessageResult之后,我发现CreateErrorResponseBadRequestErrorMessageResult使用HttpError类将其信息传递给序列化器。

由于HttpError扩展了System.Collections.Generic.Dictionary<string,object>,因此序列化程序未使用我指定的设置对HttpError进行序列化,因为在序列化过程中对字典的处理不同。

TL; DR

每当ASP.NET Mvc返回错误时;它将使用HttpError类,即Dictionary<string, object>。字典具有单独的序列化设置。

答案

您必须在序列化设置中指定要解析字典关键字:

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)
            }
        };
    }
}