2 Best Practices for Returning API Errors in ASP.NET Core

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
how do you return failures in your apis there are two best practices that I like to follow the first one is using the correct status code for the failure the second is returning a problem details response describing what went wrong and I'm going to show you how to implement both of these best practices in today's video there are multiple ways to implement error handling in your applications the approach that I prefer using is the result pattern which gives me an elegant way to express errors in my code so all of my methods return a result object and the result object contains an error inside in this example I'm trying to create a new user and if the email result is invalid we're going to return a failure result then we're going to check if this email is taken and if it is we're going to return an email is not unique error and if we get past this check we're going to end up creating the user in the database and returning a success result now if I go one level up to the API endpoint which is this one the post end point for creating a user you can see how I'm creating a command and sending it and I get back a result object and then if this is a success result I'm going to return a 200 okay result or I'm going to convert the result object into a problem details response now the problem details is a standard for describing your API failures and it contains some Fields helping the API consumer figure out what went wrong the fields that I'm using are the status code the title representing the general description of the error the type which leads to some documentation about this error and then I'm adding an extension which is just a way for me to pass in my result error which contains some detailed information about the failure that occurred unfortunately this implementation isn't flexible and you can see how I'm using this method to return a 400 bad request response and I have another method to return a not found problem details with the 404 not found status code this is because I don't have a way to figure out which error type occurred based on the result object so I'm forced to improvise in my API endpoint let me also show you how the problem details response looks like when you get a failure from your API if I try to get a user that doesn't exist I'm going to get a 404 not found response and the respective problem details response which I got from my extension method that I just show you you can see in the errors collection we have an error object describing in more detail what went wrong and if I try to hit the post end point I'm going to get a response with a different status code in this case 400 bad request but the error is still users not found as in the previous example so maybe the response should also be 4 or four not found in this case so I'm going to show you a way to improve on this design and Implement proper error handling in your apis what I'm going to do is introduce a concept of an error type in my shared kernel and this will be an enum called error type and I'm going to define a few distinct error types in my Eno so the default one will be failure then I'm going to have a validation error type and I'll give it the value of one I'm also going to define a not found error type and a conflict error type what's interesting is that you can start matching these error types to respective status code that you could return from your API so a validation error type could match a 400 bad request not found will match 404 not found and the conflict error type will match a 409 conflict response then I'll need to update my error record to support specifying the error type so I'm going to start by making a few breaking changes I'll get rid of the implicit operator and I'm also going to get R of the Constructor but first let's define the properties that we're going to need to represent an error so I'll still have a code and a description but I'm also adding an error type now I'm going to create a private Constructor for my error and let me copy the primary Constructor here I'll also add the error type as the argument and I'm going to assign this to the respective properties so let's initialize the code description and error type properties and I can move these to have lowercase names because they are now Constructor arguments then I'm going to fix the Constructor errors in my private fields and I'll give them a failure error type by default and what I really wanted to achieve with this approach is to be able to expose Factory methods for creating these specific error types for example I can expose a public static method that returns back an error and let's call it the not found method now I can give it a code and a description as the arguments and in the implementation I'm just going to invoke my Constructor give it the code the description and the not found error type so I'm going to use the same logic to implement a few more error types and I'll use the values that I defined here so let's return a validation error a conflict error and a failure error which is a default one hiding the error Constructor also introduced a bunch of breaking changes in our domain so we have to fix them one by one so if I go to the email errors instead of calling the Constructor here I can replace this with a call to error validation because this is a type of validation error the same stands for the invalid format when the specified string doesn't represent a valid email in the user errors class this one here would obviously be the error not found we also have the not found by email which is another form of not found error and the email is not unique error is very interesting because it actually represents a conflict in the database which means that you can't create create another resource with the same identifier in this case the uniqueness key is the email so what I'm going to do is return a conflict error and then we're going to map this to a concrete response in our API I also need to fix the follower errors trying to follow the same user would be a validation error if you ask me then trying to follow a non-public profile is another validation failure and the already following failure would be another conflict because such resource already exists so this covers the domain errors and if I head back to the command Handler you will see that nothing changes here because we are just using the result however I do have a command Handler where I was using the implicit operator to return a failure result so I'll have to update this luckily all I have to do is just say return a result failure and pass in the error object so let's update this here I can just return the result object directly and everything else is fine so these changes are pretty minimal and they will also work in our queries where I'm returning a not found result and now I'm going to solve how do we map this into a proper problem details so let's go back to our endpoints where we are checking the state of the result and then returning back a problem details response so I'm going to update the get endpoint here to also be calling the two problem details method this is because I'm going to completely remove the not found problem details method and I'm going to handle all of the logic for creating a respective problem details response in this one method and I can do this because now I have access to an error type so for example I can update the title of my problem details response to come from the error type so I'll just say type and then two string alternatively I could map this using a switch expression like I'm going to do for some of the other fields so let's start doing that I'll create a static local function and I'll call this the get status code it will accept an error type argument and we're going to write a switch expression using this error type so I'll say error type switch and then we're going to return the appropriate value if this is a validation error type then I'm going to return status codes and return a 400 bad request if this is a not found error type then I'm going to return status code 404 not found if this is a conflict error type then I'm going to return status codes 409 conflict and if I can't match any concrete error type I'm going to use the wild card to just return an internal server error so I'll say status code 500 internal server error then we're going to call the get status code local function when I'm assigning the status code to the problem details response we can follow the same logic for getting the title of our problem details so I can and say static string get title and it's going to accept an error type as the argument and we can write a switch expression similar to what we did in the previous method so let me copy this and I'm going to update the return statements so instead of returning a 404 bad request I'm just going to write bad request as a string here I'm going to say not found then I'm going to say just conflict but you can decide what you're going to return to your API by clients and for the 500 response I'm going to say server failure and then we just call this method when creating the problem details so let's grab the error type and call the get title method and I'm going to do the same to obtain the error type I already prepared this method ahead of time so we're not going to be typing everything from scratch and let's just call it and assign the appropriate value to our response so now we have one method that's going to take care of converting our resp result object into a complete problem details response which is also going to return the appropriate HTTP status codes from the API so let's see how our API response changes with the new implementation so I'm going to send this post request to our API again and this time I'm expecting to get back a 404 not found response so let's send this request and I placed a breakpoint inside of the two problem details method to see what's going to happen so if I take a look at the result object and more specifically the error that we have you can see the same users not found error but you can also see that the error type is not found which is what we expected and how we assign the values in our domain but we were interested in mapping this to a problem details response so if I return this to postman you'll see that we receive a 404 not found error so the status code is correctly set you can also see the same status code repeated in the problem details response and we are also exposing the error type in the errors array if I go to the get user endpoint it's going to behave the same and if I try to send an invalid request for example specify an invalid email and send this we're going to get a 400 bad request and the error type is one which matches a validation failure and if I try to create a user with a valid email address and send this request we're going to create a user the first time we try this and I'll send the same request again and I expect to get back a complect response and you can see I'm getting a 409 conflict which indicates that a resource like this already exists because we already created a user with the same email and you can see that the user's email is not unique as the error code however if I try to fetch the user with the ID that I just saved we're going to get back an appropriate response I've seen a similar approach to this one being used with exceptions where you would Implement a global exception Handler in asp.net core 8 you can use the I exception Handler AB exraction to implement a global exception Handler and I'm also returning a problem details response in this example however this is only here to take care of any unhandled exceptions in a normal application flow I will only be dealing with results and then handling them using this extension method if I need to expand this in the future I can just add a new error type create a factory method for this error type and then update my extension method to return the correct problem details response if you're wondering how to get started with the result pattern then take a look at this video next where I show you how to implement it from scratch make sure to smash the like And subscribe buttons and until next time stay awesome
Info
Channel: Milan Jovanović
Views: 16,346
Rating: undefined out of 5
Keywords: error handling, global error handling, global error handling in asp.net core web api, global error handling in asp.net core, global error handling in mvc, global error handling in asp.net mvc, global error handler, global exception handler, global exception handling, error handling api, error handling middleware, global error handler middleware, global exception handler c#, error handling api c#, result pattern, result, result monad, functional error handling
Id: YBK93gkGRj8
Channel Id: undefined
Length: 12min 39sec (759 seconds)
Published: Tue Jan 09 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.