1

Let's say I have this action on my PotatoController working as an api in my .NET Core 3.1 web-app:

[HttpPost]
    public async Task<IActionResult> CreatePotatoAsync([FromBody] Potato potato)
{
// Code to create potato
}

Now let's say that the user when trying to create a new potato, sends and invalid json like so:

{
"potatoName": "testPotato",
"potatoAge": 7,
}

What would be the best way for my api to return not an ugly 500 error from System.Text.Json library, but instead a nice custom 400 created by me?

shazam
  • 11
  • 2
  • .NET Framework ? Or .NET Core? – Adrian Dec 13 '21 at 16:01
  • It's .NET Core 3.1, I'll update the question to make it more clear. – shazam Dec 13 '21 at 16:15
  • Have you checked this out?: https://stackoverflow.com/questions/38630076/asp-net-core-web-api-exception-handling – Adrian Dec 13 '21 at 16:19
  • What is invalid about the JSON in your question? You've not told us what a _valid_ request looks like to create a `Potato` (or did you just mean the trailing comma?) – Jamiec Dec 13 '21 at 17:28
  • @Jamiec I haven't because it's not really relevant here, my point was that the json itself is invalid because of the final comma. This will trigger an exception before reaching whatever code that it on the controller. – shazam Dec 13 '21 at 17:32
  • @Adrian I hadn't, but that looks indeed promissing. – shazam Dec 13 '21 at 17:32

2 Answers2

0

When I create a .Net 3.1 test API project and use the code that you posted, the response I get is a 400 BadRequest response with this body:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|57182e0e-445086f96bddb4e8.",
    "errors": {
        "$.potatoAge": [
            "The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $.potatoAge | LineNumber: 3 | BytePositionInLine: 0."
        ]
    }
}

When I take off the [ApiController] attribute I get a null Potato object. I cannot replicate your behaviour of getting a 500 from System.Text.Json

So it seems it does what you're looking for out of the box. Are you using a custom JSON serializer or other model binding options? Could you post more of your code?

SBFrancies
  • 3,987
  • 2
  • 14
  • 37
-1

I prefer returning a 200 and handling the error at the application level

If I do not need to return any value

{
    "status": "ok"
}

If I need to return a value

{
    "status": "ok",
    "result": [
        {
            "Name": "Fabio"
        },
        {
            "Name": "Laura"
        }
    ]
}

And in case an error occurs

{
    "status": "ko",
    "exceptionMessage": "Something went wrong!"
}

Consider including the following methods in the base class of your controllers

/// <summary>
/// Processes the request using a method that does not return any value 
/// </summary>
/// <param name="action">The method to be used to process the request</param>
protected JsonResult ProcessRequest(Action action)
{
    return ProcessRequest(() =>
    {
        action();
        return null;
    });
}

/// <summary>
/// Processes the request using a method that returns a value 
/// </summary>
/// <param name="func">The method to be used to process the request</param>
protected JsonResult ProcessRequest(Func<object> func)
{
    JsonResult jsonResult = new JsonResult();

    try
    {
        object result = func();

        if (result != null)
        {
            jsonResult.Data = new { status = "ok", result = result, };
        }
        else
        {
            jsonResult.Data = new { status = "ok", };
        }
    }
    catch (Exception e)
    {
        string message = e.Message;
        jsonResult.Data = new { status = "ko", exceptionMessage = message, };
    }

    return jsonResult;
}

Then your action methods would look like the following

public ActionResult DoThat()
{
    return ProcessRequest(() =>
    {
        // Do something 
    });
}

public ActionResult ReturnPersonList()
{
    return ProcessRequest(() =>
    {
        return new List<Person> { new Person { Name = "Fabio" }, new Person { Name = "Laura" } };
    });
}

Fabio Scagliola
  • 338
  • 2
  • 11
  • @Jamiec, well, now I know where I will rest in peace :) No kidding: I believe this approach has a bunch of benefits because it allows for error handling at the application level. Just to mention a few: [LTI](https://en.wikipedia.org/wiki/Learning_Tools_Interoperability), [SCORM](https://en.wikipedia.org/wiki/Sharable_Content_Object_Reference_Model), and –last but not least!– [SOAP](https://en.wikipedia.org/wiki/SOAP) are protocols that adopt this approach – Fabio Scagliola Dec 13 '21 at 17:46