ASP.Net 5 MVC 6 - 如何使用FromBody在POST上返回无效的JSON消息?

时间:2016-05-17 13:21:39

标签: c# asp.net-mvc asp.net-core-mvc

我正在使用.Net 4.5.1创建一个使用MVC 6的ASP.Net 5应用程序。我有一个POST方法,它使用FromBody参数自动获取对象。

[HttpPost]
public IActionResult Insert([FromBody]Agent agent)
{    
    try
    {
        var id = service.Insert(agent);
        return Ok(id);
    }
    catch (Exception ex)
    {
        return HttpBadRequest(ex);
    }
}

这只是一个证明一个概念,我不会只返回成功时的id或错误时的完整异常。

当发送有效的JSON时,一切正常。但是,当发送无效的JSON时,我在调试期间收到异常:

  

抛出异常:'Newtonsoft.Json.JsonReaderException'中   Newtonsoft.Json.dll

     

其他信息:在解析值后出现意外字符   遇到了:l。路径'名称',第2行,第20位。

问题是在此错误之后,该方法被正常调用,但是使用null参数,并且异常不会传播。

我可以检查null并返回一条通用消息,但这并不像原始消息那样有用,它包含重要信息,例如标记名称和无效字符的位置。

所以我想捕获此异常并将其返回给HTTP调用者。我怎么做?像这样:

{"error": "After parsing a value an unexpected character was encountered: l. Path 'Name', line 2, position 20"}

我知道我可以在try / catch块中手动捕获和反序列化JSON,但这对我来说是不可接受的。我想这样做并继续使用FromBody,我觉得非常高效。

2 个答案:

答案 0 :(得分:10)

默认JsonInputFormatter在遇到错误时实际上会返回空模型 - 但它会填充ModelState所有例外。

通过挖掘ModelState

,您可以访问所有遇到的错误
[HttpPost]
public IActionResult Insert([FromBody]Agent agent)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelState
            .SelectMany(x => x.Value.Errors, (y, z) => z.Exception.Message);

        return BadRequest(errors);
    }

    // Model is valid, do stuff.
}

上面的输出是所有异常消息的数组,例如:

[
    "After parsing a value an unexpected character was encountered: l. Path 'Name', line 2, position 20",
    "Another exception message..."
]

JsonInputFormatter - Source

答案 1 :(得分:5)

我发现自己遇到了完全相同的问题,但能够找到不同的解决方案。 我将在这里分享我的解决方案,作为@ ypsilo0n答案的替代方案。

而不是检查每个控制器if (!ModelState.IsValid)我们可以使用此中间件过滤器:

public class FooFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var modelState = context.ModelState;

        if (modelState != null && modelState.IsValid == false)
        {
            // this class takes the model state and parses 
            // it into a dictionary with all the errors
            var errorModel = new SerializableError(modelState);

            context.Result = new BadRequestObjectResult(errorModel);
        }
    }
}

现在,控制器永远不会被调用,因为此中间件在请求之前运行并结束请求。 (阅读docs了解更多信息)。

当我们设置一个非空context.Result时,它意味着“在这里结束HTTP请求”(文档) - 如果你问我但不是非常用户友好/直观,那么(希望返回值)

使用.net核心1.1