Don't throw exceptions in C#. Do this instead

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody i'm nikken in this video i'm going to show you why you should probably stop throwing exceptions in your application in many places where you shouldn't be and i'm going to show you what's the alternative and what you gained by going with that alternative if you like them with content and you want to see more make sure you subscribe during the summer notification bell and for more training check out nickchapsters.com now before i move on i want to let you know my brand new course from zero to hero integration testing in asp.net core is now out i'm super proud of it i put a lot of effort in teaching you the right things that you would see in a big company doing things right it applies to apis mvc blazer applications it shows you how you can properly integration test your application and what does it mean to integration test an application what should be replaced what should and how you should deal with that with touching on docker on playwright with touching on so many advanced state-of-the-art things you would see so if you want to buy the course the first 300 of you can use discount code you see on your screen right now and down below i highly recommend you check it out integration testing is such a crucial part of your testing suite and it's as important and in some cases for some people more important than unit testing some people tend to skip unit testing and do integration testing just because of the value that it brings now i think you need both but i can totally see that argument so check the link in the description use the code trust me when i say these run out quickly and i hope you have as much fun taking the course as i had making it now back to the video all right enough of that now let me show you what i have here i have a simple api it is a customers api and if i go to the controller just to show you what i have it is a simple api controller i have my basic crowd stuff here so i can create get get all update and delete a customer exactly as you'd expect and then i have my customer service which has a few dependencies namely my repository because i'm using dapper the i github service because i'm validating that a github username provided by the api is valid then i have a validator which has all the validation about my model in here in this abstract validator so all the rules are laid down here and what happens is if in my service over here the validator determines that the values are invalid that i'm throwing and then later here if github responds with a 404 meaning that the github username doesn't exist then it also throws a validation exception because the github username provided is not valid and the way this is now turned into a bad request is i have this middleware over here this exception middleware which you might have used in the past and i just await the delegate and then i'm catching that exception if it's there and then i'm setting it to a bad request 400 and i'm mapping it to a problem details a validation problem details object so i'm iterating all the errors and returning that back and all that gives me the following experience let me just run this api and as you can see if i try to call this with an empty email it says email must not be empty if i try to call it with an invalid email then it says email is not a valid email so we have proper validation in place for what we have here now raise your hand if you've done this before you throw some exceptions somewhere in your application and then you have some form of middleware or filter that catches that exception and converts it into let's say something like the bad request one that we do here now this excludes catch all exception filters that catches things that are unexpected and it returns a 500 because of that this is more about things you added explicitly part of your business logic or your application's logic now in my opinion you should not be doing that and i'm going to show you the alternative which is actually a borrowed concept from other languages but before i do that i want to run a performance test against this api as it is now even though it has dependencies like database and the github api it's going to throw in return early i want to test how the api responds when a bad request is thrown because remember we are throwing an exception to catch and convert and there's a cost now you might say the cost is minor let's see that so what i'm going to do is i'm going to dotnet run c release so run this api in release mode and i'm going to go back here and what i'm going to run is this file over here this is a k6 performance test i have a video on k6 if you're interested and all it's really doing is it uses 10 virtual users for a minute and it hits that api as hard as it can with this payload over here and then i'm expecting a response pack and this is expected to be 400 bad requests i could validate the body as well but i don't need to so that's what's happening so now i'm going to go back here i'm going to zoom in a little bit and say k6 run and i'm going to run the stress test so k6 is now spamming everything for a minute and i'm going to give it a minute and not have you wait i'm gonna edit and let's see how this api performs in its current form where a validation error is throwing an exception and it's being called and translated in a middleware all right so the test results are back however i'm not going to show you the test results just yet but just know it is up there anyway i'm going to leave this as it is and stop this api and i'm going to refactor it to not have to use that exception throwing mechanism and the way i'm going to do that is with a new type the result type which is very common in other languages like rust or f-sharp well-designed languages and it really makes life easier with handling these types of things now it is a more functional paradigm it's actually a monad we're not gonna go into what a monad is we don't have that sort of time but i'm gonna explain to you how you can easily use that to refactor that exception throwing out of your application and explain why in my opinion it is a better approach now i'm not going to make that type myself even though i could but instead just because i want to show you the result i'm going to use a package called languageextensions.cor great package give it a like in the description down below it doesn't only have the result type but it also has other functional elements to spice c sharp up and it's something i personally use all right so now that i have this type i'll go ahead and remove first and foremost this middleware so no validation exception really where this thing will not be used and now i'm going to go to my icustomer service i'm going to change this create method and also the update method that does the same by the way and remove this validate and throw a call and change it with a var validation results equals validator.validateasync the customer and now i'm going to get the arrows back and i can say that if validation results are not valid in fact this validation result not results is singular then what i want is create a validation exception so still create a validation exception here but not throw it just create it based on the validation result dot errors which is what um fluent validation will actually do behind the scenes to create this validation exception in the first place and i'm not going to throw it instead i'm going to change this type from bull to result which accepts a bull as a generic type and now i'm also going to update of course the interface over here so this will be updated and also let's update the update method as well so both of them have been updated and i'm going to change it here as well and now as you can see my code here hasn't thrown an exception this thing that returns task bool is still accepted by this return type this is because result actually internally if i go all the way down has an implicit operator to automatically convert the object to its result type form now for the exception i actually have to be explicit especially with the task so i have to say var return new result bool and then pass down the validation exception which might look a bit clunky but don't worry about it it would just say we return this exception and we're going to do the same one here instead of throwing this exception here we're just going to return it and that's it and i can go ahead and actually just remove this throw all together now we could also extract some things there and refactor this piece of code and make it even cleaner we're not going to worry about that i just want to show you the change i'm going to do the same here in this update so do this and then i'm going to use return result now here we go and of course now if i go to the controller nothing has broken however this thing over here now returns that result type now let's take a look at what that result type is in the first place so as you can see it is a read only struct so allocation wiser will be very efficient and it has a field for the value itself the good value the actual result and it also has a field for the exception and can have two states it can have a success state so things are good or a faulted state so things are not good i have an exception in me and the way this works as a discriminated union as well is that this thing can be either one or the other it can never be both and then you have to handle both of these different aspects of this type meaning if something is well i'm gonna do something with it if something is bad if if i have an exception effectively and this is faulted then i'm also gonna do something for it so how this looks here well i'm gonna say return result dot match and i can match either of those states as you can see here i can match the success state and the failure state and i can grab those objects in there so in the good state that this item returns a boolean and the boolean indicates created or not on the database layer then i can actually just copy everything as it is here and i'm going to say here's my good path if everything is good then create the user if things are not good however and i have an exception then i can choose to do something about that now in my case i care that exception is validation exception so validation exception and if something happens here i want to handle that what i want to say is return bad request validation exception dot to problem details so i'm actually reusing that mapper that i had over here to return the appropriate object and now because just the bad request and the validation exception is what i care every other exception is going to return a new status code and the status code will be 500 so internal server error now this does not compile however if i go up here and i say this is an i action result then everyone is happy now don't be scared by how much more code this is you can extract that and make an extension method and say result dot to created or to okay or too bad request and refactor it and make it just one line of code with that generic logic in that other method and let me just quickly offscreen also do it for the update method all right here's the update method as well we no longer throw anything we just match and we return the type all right and now if i run this api i can go ahead in postman and i can show you that i have the exact same experience email must not be empty and if it's an invalid email then email is not a valid email without having to throw any exceptions and where's the biggest flaw of the exception throwing other than what we're gonna see in a second well it's actually that it works as a form of go-to call because it can be thrown by anything and anything at any point can throw that type of exception and if the exception is thrown you go to the middleware and you have to then handle that there which is an interruption of the execution flow which i really don't like i said you jump too unless you know it exists it is weird so that way you can follow the code in a way more explicit way and understand how everything flows and how things are handled and just to prove that this doesn't have to be that much code after i show you the next thing i'm going to refactor it but what i want to do now is i want to stop this and i'm going to go here and i'm going to rerun this api now with the new way using the result type and i'm going to run the performance test again and let's see how this performs now without any exception throwing just a result type all right the results are back and what i have here is these requests per second which is what i care about 34.3 000 requests per second with the result type now i'm gonna scroll up and see how much the exception throwing one did 19.4 000 that is worse than the one third it is absurd just for throwing the exception there's a hidden cost in doing this and now this test assumes that every request in that time frame will be a failure which won't be the case and the realistic result depends on how often your users are to fail registration and how many of those requests you get per second but you can't deny that your application will consume significantly more resources to do this for no benefit for west coast that jumps around and you can't really control the flow so in my opinion you should not be doing that now since i did talk about performance i also want to address something that some of you might have noticed by doing this i am creating a closure to the customer object i can actually easily fix that by going over here and making this return result of type customer instead of type boolean so instead of returning that response back i can return the customer back and if i do that let's go ahead and update the interface as well real quick and again this heavily depends on how you design this but if i do all that i can go back here and i can say that this b now is the customer so i can say cast over here and map that here and no longer have to worry about the closure and i'll do the same for the update method as well so i can go here copy that paste it here then go to the implementation paste it here too and bam and bam and just return the customer back and that's it and now what i can do just to show you that this doesn't have to be a multi-line thing i can go here and i can create a new controller extensions so with that i can create a public static class i can say public static i action result to okay in this case and i will need two things i will need the t result which is the domain object if you think about it and i will also need in my case because i'm mapping to an api contract at the contract over here and this will be this extension method on result of type t result so we have the result over here and then i also need a mapper and the mapper is a function that accepts the t result and returns the t contract right so i'm gonna do that and i'm just going to say in fact i'm going to copy exactly as it is this fell over here that doesn't have a closure anymore so everything is self-contained so i'm going to change a few things as i need to so first this is new object result now this is new bad request object result yep here we go and this is new status code result there we go and now this b is the t result type that i can use but all that mapping logic goes away so i no longer need that what i do need is var response equals and then i have the mapper and the mapper accepts the object and returns back the response contract so i'm passing that down here which means that all that now the thing i removed becomes return result dot to ok with that extension method and the mapper is customer so let's say c is c dot to customer response and that's it all those lines that you saw before became this with the exact same experience and now all that is generic you just have to provide the mapper call and the rest will just work for you just to quickly prove that it actually does work i can go here um did i create successfully a user for me i don't know let's let's create one so happy path works absolutely fine going here trying to change it with a bad one and i'm getting the error as you can see going through that flow over here the one where let's just actually debug it why not let's click that it hits that breakpoint it gives me the result back the customer the fluent validation as you can see said that it failed it has an exception here it goes in the to okay method and you will see it goes in the exception block and i'm catching that i'm dealing with it returning the appropriate thing and voila that is handled appropriately so i hope you learned something i hope you saw the impact of exceptions and how you can refactor them out of your code if you want to you don't have to go with that approach but i find it better to work with so it's ultimately up to you but this is what i would do well that's all i had for you for this video thank you very much for watching special thanks to my patreons for making videos possible if you want to support me as well you're going to find the link in the description down below leave a like if you like this video subscribe and locked and like listening the bell as well and i'll see you the next video keep coding
Info
Channel: Nick Chapsas
Views: 154,008
Rating: undefined out of 5
Keywords: Elfocrash, elfo, coding, .netcore, dot net, core, C#, how to code, tutorial, development, software engineering, microsoft, microsoft mvp, .net core, nick chapsas, chapsas, dotnet, .net, throw exception, exception throwing c#, c# exceptions, language extensions, c# result type, result type, result rust, result f#, Don't throw exceptions in C#
Id: a1ye9eGTB98
Channel Id: undefined
Length: 18min 12sec (1092 seconds)
Published: Thu Jun 02 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.