Global Error Handling | ASP.NET 6 REST API Following CLEAN ARCHITECTURE & DDD Tutorial | Part 4

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today's video is all about global error handling so we're going to deep dive into the different approaches that there are for global error handling we're going to learn about problem details and why this is how you want to return error responses from your service and not to create a custom model and we'll look at some interesting ways to customize the response and how these two can play really really nicely together okay so this is the final result that we'll have in the end of this video so just to recap we have the register and the login endpoints which use the authentication service from the application layer to actually register the user or log in and if something goes wrong then we use exceptions for flow control which all that means is that if something goes wrong then we throw an exception that's instead of taking another approach which i actually prefer and we'll look into in the future okay so what happens when an exception is thrown from the authentication service so at the moment basically what happens is we have the exception which arrives as plain text to the client this isn't good for multiple reasons and it doesn't matter if you're using exceptions for flow control or not so exceptions will be thrown in our system and we don't want the client to see them as is so today we'll make sure that no sensitive information is returned to the client and by the end you'll have a very deep understanding of the different approaches and you can see what's right for your application hi everyone amikai here i do have a quick disclaimer if you're new to the channel so i am a microsoft employee and these are microsoft technologies but i'm not talking on behalf of microsoft these are my personal opinions okay so let's run our service and make a register request so the first request adds the user to the database and when we try to register the same user again with the same email then an exception is thrown and like we said we get it in plain text basically the exception to string here's our message and here's our stack trace okay let's close this and start with the first global error handling solution which is a custom middleware so let's start and explain it as we go so first let's create here a folder middleware over here let's create our error handling middleware so we have our public class error handling middleware which let's see what it's suggesting yeah let's accept this and walk through the what was suggested okay so actually before we get into the implementation let's add it as part of our pipeline so use middleware and yes so the request will arrive you'll reach our middleware and then all we have over here is when we call the next piece of middleware which eventually will invoke our endpoints then we put in a try catch if an exception is thrown then we catch it over here and we handle it in this method over here okay so let's just fix this be this here we want this to be json serializer serialize and instead of the exception message let's just have it an error occurred while processing your request okay let's run our application again and let's put a breakpoint over here and over here great let's attach our debugger f5 and let's attach it and let's make the register request so as we expect we arrive at the middleware now i actually want to put a breakpoint also in the authentication service over here so let's continue and we arrive at the register earn point we don't hit the breakpoint over here that's because an exception wasn't thrown so here we have our response but when we make a request again so we arrive over here and because the user already exists then here we throw an exception and we catch it over here so here as we expect we have the exception that was thrown and we have our custom implementation that we have that just returns this property that says error and the message that we defined okay great so this is the first approach so let's go to our program cs let's comment this out so it won't be used and instead of middleware let's create a filter so let's create folder filters and over here let's create an error handling filter attribute before we continue i just want to say one more thing regarding the middleware throughout the examples today we'll look on how to set up our project for global error handling and we'll revisit the actual logic over here when we replace throwing exceptions like we're currently doing with the actual approach that we'll be taking in the end so back to our filter that's created public class error handling filter attributes which will extend that exception filter attribute so yes let's include what we need to include so what we have over here is basically a filter that we want to apply to all of our controllers and what happens is that when exception is thrown and not handled then this method over here will be invoked and over here in the exception context we have everything we need from getting the exception and defining what response will go back to the client okay so let's look what we have here so the exception comes from yes dot context.exception whatever we put over here in the result is what will actually return to the client so yes for example and after we finish this then we want to set the exception handled to true okay so that's that's it more or less let's just take this and also set here the status code so let's say 500 and this should match the logic that we have in our middleware so let's just split the screen and open our middleware so both of them have the exception that was thrown over here which can be used for logging or whatever other purposes that you need it and in the end we return 500 to the client and some custom object which has a single property that says an error occurred while processing your request okay great so let's close everything over here and go to our controller over here let's add our filter so error handling filter and now this will be applied for the authentication controller now we don't really want to specify it here on every controller so instead let's go to the program cs and over here let's apply it to all of our controllers so options filters dot add and here yes let's specify our attribute great we can actually get rid of the middleware because we're not using it anymore and let's run our program again and see that we still get the same behavior so let's run the project great make a register request first let's attach the debugger again okay so the first request as we expect registers the user and for the second request let's put a breakpoint in our filter so over here and let's make the second request we hit the breakpoint in our filter over here we have the exception which is the exception that we threw and let's continue and over here we can see final result that we have is the same as with the middleware okay great now let's move on to the third part of this video which is the model that we're using for the error result so let's pull this object into a variable let's call it i don't know your results so over here we're defining some object which represents the error but this is something that the client specifically needs to be familiar with to know to expect the error property for example and not to expect other properties which might be present in other responses now of course it doesn't scale while having each back-end define its own error response that's why instead of creating just a custom object over here what we want to do is use the problem details so let's look into that okay so let's get familiar with this error response model so what i did is i took some snippets from the rfc specification okay so let's go over what we have over here so right out of the bat we can see that we have here a different content type not application json anymore but something that's called problem json and over here we have some properties and i'll give you a little spoiler that these four over here are standard properties from the problem details response and over here you have something that's called extensions so over here you can define extra properties if you need i put over here on the right the example that we looked at before i want to go over the standard properties that we have so we understand them first let's start with the type so this thing over here this is a uri that's unique for the specific problem type okay then over here we have a title this title over here should not change between occurrences so for example every time that the user doesn't have credit this is the same title that he should get status is just the status code so it doesn't appear over here but if it did then it would say four or three okay next we have the details which is this over here this as you can see unlike the title is unique for the specific occurrence and last and yes least is the instance this one over here which you can populate and say which resource basically you were working on when this problem happened okay and over here there are some other details so this one over here says use the uri to identify the problem and not the title the next one talks about the detail property and it basically says that if you do decide to populate this property then put your information that explains to the client how you can fix it instead of putting here debug information the last one has to do with the detail versus the extensions so we said these two properties over here are extensions to the problem detailed response and it says that the client shouldn't be parsing this string over here to extract whatever details he needs instead you should put it over here in the extension and then the client can use it okay so now that we're more familiar with it let's go back to our example and think how we can incorporate it right so over here we said we don't want to return this object we want to return the problem details now luckily asp.net core has great support for problem details so problem details equals so we can see we have this object which is part of the asp.net framework and over here for example let's say that the title is yes this then we can replace this error result with the problem details and over here let's also add the status to be internal server error and we can get rid of this because the object result will populate it from the problem details okay so because we're using here a standard error response the object result code has specific logic that says if it's problem details then great pull the status code from this property and populate in the result so we can get rid of the other code that we had before okay so let's go ahead and run the project again and see how this change affects the response so we make the first request and it runs successfully and the second one throws an exception we get here the two properties that we defined and over here we can see that the content type matches the problem details specification okay so this is a good start but if we want to follow the specification then over here in the type we also want to pass a uri with further details about the problem that we encountered this might actually be the right thing let's follow this link yes so thank you github co-pilot this is the right link to the rfc specification of an internal server error but there are other types that we would specify if it wasn't 500. okay so this brings us to the third approach of global error handling which is when an exception occurs to reroute the request to a new endpoint and then we can take advantage of the methods in the controller base which has great support for problem details so this will make more sense when we start implementing it first let's go to the program cs and get rid of this so take it back to what it was before what we want to do and let me just close everything over here and over here is use the following method so use exception handler with some route okay and what this line over here does is it adds a middleware that will catch the exception log it then it will reset the request path and re-execute it to this path over here okay so let's go to our controllers and add a new controller called errors controller public class errors controller which will inherit from the controller base yes okay so this let's have it route and here let's add what we're missing great before we look into this i actually want to run the application and for us to see what the response is without doing anything okay so we didn't define anything yet so all we have and remember these two are commented out so it's as if we don't have anything in our application other than this line over here and this endpoint over here let's stop application and run it using it watch which will hot reload the changes so we don't need to relaunch the application every time we make a change so dotnet watch run and here we need to specify the project so in our case it's the api project okay the application is running and you can see it launched the browser but since we don't want it to launch the browser every time we can go over here to the properties launch settings so let's change this to false and let's rerun it great no browser was launched let's go to the register request first request was sent successfully second request failed and let's look at what we get so remember we didn't do anything in the controller other than use the base method problem just like we have okay embedded request etc and we can see that we get here the problem json content type and we get here this link which we followed before that took us to the correct rfc specification and we get here out of the box an error message status code and even a trace id okay pretty cool now something i want to notice so let's follow this link for a second okay and we see it takes us over here to the same place but if we go back to the controller and we specify over here a different stylus code so let's say 400 and you can see that hot reloaded and we make the request again then we can see that now we get the status code 400 and we get a different link so if we follow the link now then you can see we arrive here at the correct rfc definition for bad request okay now we didn't have to do anything and that's the power of this approach where basically you have this method in the controller base which implements a lot of what we had to implement before by hand for us so you might be asking yourself how do i access the exception that was thrown so because we're inside a controller then we can just go ahead and pull the exception from the http context so var exception equals and here yes so over here we have our exception let's pull it great so here we have our exception and just to demonstrate that we are holding over here the same exception that was thrown in the authentication service let's go ahead and return here the exception dot message okay so we have reload so it should have reloaded let's try again and over here we get the response with user with given email already exists so what happens in most enterprise applications you want your error response to contain other properties other than the default and a trace id so how do you do that well what i want us to do is actually look at how this method is implemented so because we don't have implementation let's look at the source code of the controller base of the framework so let's open a new instance of visual studio code okay and now there are two options so if you only want to look at the implementation then you can use an extension that's called github repositories okay this one over here which what this allows you to do is basically use the command remote repository and then open remote repository you can choose any repository from github so in our case we want the asp.net core right so this one over here we click it and then it simply opens it for us so we can already go to the controller base and look at how stuff are implemented over here but we don't have any navigate to definition and so on because the project isn't compiled so if we want to actually compile the project so what you want to do is go over here to the extensions and search for remote containers so this extension which what it allows you to do is when you go to the run and debug you can click continue working on and then it will give you the option to clone the repository to a container volume if you don't have docker installed then it'll prompt you to install docker and so on follow what it says and within 10 minutes you'll have the source code of asp.net core ready for your investigation great so because i already did this then i'll just open the actual result okay let's go to the controller base look for the problem method so problem great here it is and we can see that the actual implementation uses the problem details factory to create the problem details because let's go to this problem details factory definition let's see who implements it and here we can see the actual problem details factory that is used to create the problem details and here we have some logic which creates the problem details like we did in the filter okay nothing too special over here and there's another method which we can already see it adds the trace id that we saw in the final result and here there is some logic we know magically knows how to take the status code that we define and put the correct type and title right so when we changed it to 400 it put here the right link and the title said bad request okay so what is this client error mapping and what is these options so going back to the constructor you can see here that using the options pattern that we're already familiar with we're injecting the api behavior options which if we look at how it's populated then we can see that over here is populated with all these defaults so the framework out of the box gives us all these mappings with all the correct links to the correct sections all we need to give it is the status code and it'll do everything else for us that's great you might be asking yourself okay so the framework has this defined but when was it actually registered in the ioc container so over here in the add controllers so this is implemented in the mvc service collection extensions okay so over here on the mvc collection extensions and we can search for ad controllers this is the one that we're calling without any other parameters and it calls some core method which caused some other core method somewhere in the end here it is registers the problem details factory great and if you're curious about the options that we looked at before then over here we could see where it's actually being registered so we had our api behavior options which is registered over here using the defaults that we looked at great so we have all this out of the box and it's really convenient what this means is that we can go ahead and change the links over here by just updating the api behavior options and we can also update the factory to use whatever factory that we want okay so let's go ahead and do that okay so over here on the api let's just create a new folder it's called errors and let's create here our own problem details factory so movie donor album details factory what i'm actually going to do is i'm going to copy the default factory so let's go ahead and copy this entire thing and paste it over here let's have this public instead of default let's have it blooper dinner let's include what we need to include again everything here is just part of the framework we fix the name add whatever's missing great so all i did was just copy paste the default probabilities factory and what we can do is go over here to where we're applying the defaults and add whatever we want that so for example problem details dot extensions dot add in here we can add i know some custom property with some custom value and once we have that we can go to the program cs and override the default so we can say builder dot services dot at singleton and here we can say cloud details factory the boober problem details factory and that should be it let's try building it and see if it works great let's run it now let's watch run it why not and let's make the register request the first one works successfully the second one throws an exception and over here we can see that we have the title that we put in the controller and we have here the custom property that's populated from our custom problem details factory now i know this was a lot so let me just recap what we actually have in our project let me close everything over here so all we have in our third approach is the middleware which re-executes the request to the error path then we have the errors controller which receives the error and returns the problem details right now we can get rid of all this debug stuff that we put before and the problem details factory which is a complete copy paste just because for some reason their implementation is internal sealed so we can't derive from it so it's a copy paste which adds this line only that's it that's all we changed so this really isn't a lot of work and if you aren't going to be creating your custom problem details factory then it's much less code actually if you want to take the minimal api approach then what you can do let me just comment this out over here so it's not used then we can define our endpoint over here so app.map and here we can say map the error endpoint to receive the http context here let's pull the exception yes and from here we can return results dot problem which is similar to the problem method in the controller but since there's no dependency injection in play here so it's not using the problem details factory but rather if we look at the signature then we can see that beside the standard properties then it also gets here a dictionary of extensions which over there we can add our custom properties which will be added to the final problem details response so looking at what we did today so we have the exception filter attribute approach which will catch any unhandled exception that was thrown in the controller but one thing to notice is that it will catch any exception that is not an http response exception so that's one caveat to notice unlike the middleware approach which will catch any exception and it also wraps the rest of our pipeline so this is a bit more flexible and then if you haven't noticed this is my personal favorite using two of these together to take full advantage of what the framework provides for us so that's it for this video i really hope you have a better understanding of global error handling in your application and what's right for you of course there are other approaches which are also very common and i haven't demonstrated over here so what we're going to do in the next video is we're going to leave only this approach which i think is the correct approach for our implementation and we'll look at a very interesting way to handle errors in your application that doesn't involve exceptions which will make your application faster and more resilient so if this sounds intriguing then make sure to subscribe and i'll see you in the next one
Info
Channel: Amichai Mantinband
Views: 72,740
Rating: undefined out of 5
Keywords: .net, dotnet, global error handling, csharp, c#, rest api, api, clean architecture, domain driven design, ddd, asp .net
Id: gMwAhKddHYQ
Channel Id: undefined
Length: 24min 8sec (1448 seconds)
Published: Sun Jun 26 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.