Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.Net 5 MVC 6 - how to return invalid JSON message on POST using FromBody?

I am creating an ASP.Net 5 application with MVC 6, using .Net 4.5.1. I have a POST method that uses a FromBody parameter to get the object automatically.

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

This is just a proof a concept, I won't return only the id on success or the full exception on error.

When a valid JSON is sent everything works fine. However when an invalid JSON is sent, I get an exception during debug:

Exception thrown: 'Newtonsoft.Json.JsonReaderException' in Newtonsoft.Json.dll

Additional information: After parsing a value an unexpected character was encountered: l. Path 'Name', line 2, position 20.

The problem is that after this error, the method is called normally, but with a null parameter, and the exception doesn't propagate.

I could check for null and return a generic message, but that is not as useful as the original message, which has important information, such as the tag name and position of the invalid character.

So I want to capture this exception and return it to the HTTP caller. How do I do that? Something like this:

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

I know I could capture and deserialize the JSON manually inside a try/catch block, but that is not acceptable for me. I want to do it and continue using FromBody, which I find very productive.

like image 290
Ricardo Smania Avatar asked May 17 '16 13:05

Ricardo Smania


2 Answers

The default JsonInputFormatter will in fact return a null model upon encountering an error - but it will populate ModelState with all exceptions.

So you have access to all encountered errors by digging into 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.
}

Output of above is an array of all exception messages, e.g.:

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

JsonInputFormatter - Source

like image 128
ypsilo0n Avatar answered Nov 18 '22 10:11

ypsilo0n


I found myself with exactly the same problem, but was able to find a different solution. I will share my solution here as an alternative to @ypsilo0n's answer.

Instead of checking in every controller the if (!ModelState.IsValid) we can have this middleware filter:

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

Now, the controller never gets called because this middleware runs before and ends the request. (read docs for more information).

When we set a non-null context.Result it means "end the HTTP request here" (the docs) -- not very user friendly/intuitive if you ask me but OK (would expect a return value instead).


using .net core 1.1

like image 23
Vetras Avatar answered Nov 18 '22 09:11

Vetras