The debate about what constitutes an 'exception' is one that has been argued about on many a fora! I am actually with Nick on this, but my observation on this is that the vast majority of developers are being extremely lazy in how they handle errors. 'Exceptions' are situations where software is unable to continue: like run out of disk space, run out of memory, hardware failure, network connection lost, database connection lost etc. Situations where a user has entered incorrect data and software is unable to proceed are NOT exceptions. They ARE DATA ERRORS caused by data not conforming to business rules and should be handled as part of a normal result returning mechanism. The fact that a lot of developers functionally overload the exception handling system (which goes against SOLID) to return both exception and normal data errors is laziness and a failure to identify things that are fundamentally different. Myself, I return a standard 'result' object as part of an http response and I adjust the http error code accordingly. This means that the calling application has no need to know whether an exception or general data error was returned: they are both returned as a result with an http error code.
Semantically? Yes. I can't disagree here. However in practice, custom exceptions + handler are safer - as throwing an exception causes code execution to stop unless explicitly handled, which prevents the code from continuing to run in invalid state. On top of that, they result in less boilerplate code - a simple line to throw, and you don't need every caller and their dog to have to worry - you just have a global handler. There honestly are benefits to both. I'll pick less boilerplate and less complex code + standardized way to follow the exceptions. I think it's the better way - just wish it had the less amount of performance hit.
Yes. Have domain-http-ignorant result objects, which then you map to HTTP status codes and ProblemDetails. Code 200 when you get something successfully, 500 when really an exception, 204 when you mutate state successfully, 409 when validation problem. Leave code 400 alone, that's literally for "bad request" - bad url, can't deserialize JSON in request body and so on.
I personally use a custom result object which can either be returned as a success or a failure. When it's a failure I can specify an error code, a message, and an http status code. Then the caller of the method checks if the result is a failure and if yes the caller also returns a failure and passes the original failed result higher. This results in errors bubbling up all the way to the controller where I have the full call stack thanks to the caller attributes which I can now log and I can also convert the result into a DTO which the API will return. It's ultra fast and no exceptions are necessary but it requires you to follow a very specific coding pattern.
been doing it this way for 15 years, one thing to add, which may be obvious, but you'll need to handle various kinds of exceptions or add a "severity"property if you want to return different kidns of status codes so that an unexpected exception doesn't get returned as a bad request for example. Though of course, I don't really throw exceptions for validation, which is handled separately,
How about creating an API Response class that has 3 different methods overload. Each of the method overloads takes a code, a message string and then a data object. Then in your controller, you can return the normal BadRequest and then use the Response class in it. Say return BadRequest(new Response(400, "custom validation error")); In a 200 response return Ok(new Response(200, "Something fetched successfully", data)); This was you can use the Response class for 200, 400, 404 and 500 responses, That way your API Response remains concise and consistent.
If you've separated your application layer from your presentation layer (API), then the validation will likely happen in the application layer, and that will not know anything about HttpResponses. So you're back to the same problem. How does your application layer return an error? Is that via an Exception, or some other tuple/result class. If you're in your controller anyway, you can return whenever you like, using whatever response class you like.
You should use a separate class for each response type and it's okay if they're different or the same. You can repy on the OpenApi schema to know what the response looks like on the client. Error responses should be standardized though for your API, preferably using ProblemDetails
@@SuperLabeled Exceptions should represent an unexpected error. Bad requests are part of normal app behaviour - not at all unexpected, thus not quite "exceptional". At least that's how I see it.
this is great, follow every video you release, I want to buy the unit test course that you have, I'm having a lot of questions on how to create good unit test and correct way to do it, but not sure if it will help me to understand how to create them will all dependencies or scenarios that methods have, what can you recommend me ?
Hey man, great video! Went straight in a implemented in my side project - great! But i have one problem.. when using typedresults to generate a openapi spec with swagger, the documentation says nothing about a possible code 401. Is there someway to generate a nice open api spec with the TypedResults.Problem or TypedResults.ValidationProblem?
Are there worlds that exist where setting a problem type parameter is going to be different from the status code parameter? I know this was a very bare bones example, but I'm just having a hard time not cringing at setting two properties/parameters to what amounts to the same value in different formats (enum vs string). That all being said, this was very eye-opening on a few different fronts and I (don't) look forward refactoring some (all) of our company's apis xD
242 / 5 000 Thanks for showing this, including the exception part. Very useful! Although I am personally against exceptions when the problem can be foreseen, it is not always me who decides. If my client's strategy is to use exceptions, then...
What is the standard way to handle standard ProblemDetails on another API side? Like I call your example API via another API with HttpClient because the result can be the expected object or the ProblemDetails. Is there a good and standard way to do this?
@@dylan8463 Thank you for the answer. I am more interested in the following part. For example, if I use Refit or just the base HttpClient, what should be the return type? Should it be Result, and should I create the result based on the header?
@@Suv3g00 Ah sorry I misinterpreted your question. Personally in a Blazor project I made my own EnsureSuccessStatusCode extension method that parses the problem details response and throws a custom problem details exception, when the response is not 2xx. You could do something similar with the Results pattern too.
Hi Nick, funnily enough, today I got a issue assigned that has is basically "figure out a way to add generic error results to API". How would you go about following this standard, if the API is also a backend for a SPA that needs some kind of errorCode or unique, to translate the real message to a users preferred language?
Very good content but here is my 20 cents! You should throw exceptions only when you don't handle it in the code logic (something unexpected). Also this can lead to a performance issues as throwing exceptions is expensive process. When you validate you should return the object not throw and exception.
RFC 9457 is that type can use a shared registry (most were already doing this), that validation errors has an errors array with description and they tried to be more clear. It's just a continuation of RFC 7807.
If I recall at least ast far back as net7 you would get with if you used standard controllers and the API controller attributes and model state for bad requests and you could return a validation problem manually if you wish (in combination with model state)
cool. How might you pass a domain specific error code as an additional property here? (to avoid leaking internal / api type text messages up to the UI)
The easiest way would probably be to use the Extensions dictionary on the ProblemDetails type and put it there (get the code from your domain exception or whatever you app has for this)
You Could set the Detail in problem details. If want more specific then like Nick did at 5:03, you could add a property using Extensions. For Example I add an array of error strings or a dictionary of error code string, description string. I generally just pass the domain error. So using ErrorOr I set the error code and error description, map that to a dictionary
I use a static class with an enum for error codes like USER_NOT_FOUND then use a (frozen) dictionary for error descriptions, one for each language. this class is in a shared project that the client and server use I'm gonna watch the video and see if there's a better way to do this.
If it's used more than once, usually standardize it in a Dto, extend problem details with custom error message/codes set. If UI needs a custom error message, that's on them, they can use status code and title, and ignore the message (did this for e.g. translation stuff). Most of the time your message text is the same in back-end and front-end though, since validation error will just be "Name is required" or something like that.
You need a monadic type with a succes and an error value often called an 'Either' (in C# another popular name is 'Result'). You then build a monadic pipeline where the input is the request and the output the response. First step is typically to transform the request into a valid request. You then continue with more transformations until you have the response. Notice that the success type can change at each transformation. However, the error type is always the same. In this context the error type can be a problem details or something that can be converted into a problem details. The validation method would take the request as the input and return either a valid request or a problem details (wrapped into the monad). Each subsequent transformation takes the succes type of the previous step as the input and produces a new success type for the next step. By the wonders of the bind operator you can then build a succinct data transformation pipeline using monads. At the end of the pipeline you match the value of the monad into perhaps a 200 OK payload or a problem details with varying HTTP status. Notice that validation often consists of many smaller steps that lends itself to be implemented using monads and doing that gives you nicely composable validation rules. Rules like "string is not empty" and "integer is positive" that return an 'Either' can be composed into more complex rules using the bind operator. Unfortunately, you might try this approach together with async methods and run into problems and that's because 'Task' also is a monad. Nested monads can be troublesome but there are ways to deal with that. It just require a bit more work but the end result can still be great.
@@MartinLiversage What is a bind operator in C#? Is it related to linq's SelectMany? Also, it would be terrific if you can point to a repo that illustrates this approach.
I have a better (dumb) question - how do you use those trace IDs? Everywhere, you can find what they are, what they are used for, and how to add them somewhere, but not how do you use them in debug. Based on that magic number, how do you find where the error occurred in the code?
@@Kwpolska So, in other words, you still need a log with some meaningful text, to see what happened. In that case, why not to use GUID/UUID? What is so special about TraceID if you need to "pair" it with a log?
@@martink.7497 Why invent your own ID if there's already TraceID? Plus, it is passed in HTTP request headers, so that you can correlate requests made from one service to the other.
I basically do the same thing but have added an upfront check of the data model so I don't get into the API for basic validation errors through a service behavior. public static class Behaviors { public static void Register(IServiceCollection services) { services.Configure(options => { options.InvalidModelStateResponseFactory = context => { var badRequest = BadRequestProblem.Create(context.ModelState); return new BadRequestObjectResult(badRequest); }; }); } }
Hey Nick. Please use another capturing phrase for your thumbnails. "People still get this wrong" is just pure clickbait and doesn't help deciding if the high quality content you produce is worth watching. Well, you might argue that the video caption "The Right Way to Return API Errors in .NET" is the second source of information, yet the caption is not shown at the end of a video showing the suggestion for the next video to play. You're better than these clickbaits, aren't you? Thanks for listening.
I personally dont like the idea to use exceptions (and throw them) for validation handling and other control flows. Thats not the idea of exceptions and a mis-usage in my point of view.
@@huszaristvan8246 The alternative is to use IF-THEN-ELSE and check your return codes, rather than relying on the exception handler to bomb out and catch your code because you haven't coded something that you should! Validation handling is checking user data according to rules. If it doesn't match, it is a DATA ERROR that should be handled as part of every-day processing and result responses. It is NOT an exception.
This seems like a slightly worse version of doing try/catch in your own exception middleware. I really like to see what is going on instead of adding options and services that do their thing in the shadows.
It's been a minute since I did a .NET web api, but there's a much simpler way to map exceptions to status codes globally with way less code. I'm not sure why they're adding worse patterns when a better way has been available for 8+ years.
If you want to return more detail while developing locally and then restrict it once deployed to your server, you could inject IHostEnvironment and determine your environment and the level of detail you want to return. I would also suggest injecting ILogger and logging the exception so that once deployed, you can find the problem and the larger detail in your logs.
You also conditionally compile it with compiler directives. DEVs build the debug version that gives them more info and server build the prod version without the details.
I was looking for something like this in the comments, isn't it bad practice to throw exceptions instead of returning something? Even Nick had mentioned it in an older video that it is.
@@dimitrislaliotis504 Yes. I think Nick did a video on it, and found throwing exceptions were about 3 times slower than error handling without exceptions. But that doesn't make it bad practice, it's just a design consideration.
@@dimitrislaliotis504 Throwing exceptions absolutely is a performance hit because the whole execution stack has to be unwound, scopes closed down and stack dumps created. We should not be writing code that uses exceptions as a convenient (lazy) way of bombing out of a call stack instead of exiting the call stack gracefully!
Validation errors should return HTTP 4xx errors, not 5xx. 4xx means an error caused by the client whereas 5xx means an error caused by the server. Invalid input from the client is always considered as an error that was caused by the client.
Logging correlation. If you have multiple systems calling each other you can follow the entire flow. Works best if you use something more than just text files for logging, but even in a text file it is better than nothing.
if the concept of exception still same, exception usually faster but it has cost. exception basically an interrupt, it's order to system/thread pool "stop whatever you are doing, we have problem!". so throw exception for validation, it should faster but it's like saying "all of you, stop what you are doing, this guy send wrong character", it's unnecessary cost to the thread. throwing exception should be critical issue, such attempt access violation, etc.
It does not matter. You optimize the happy path, not the other way around. Unless you actually expect to be bombarded with a lot of bad requests. But then the question is: why is that?
In my testing I had about 5-20ms to return a Result, same request with exceptions took about 80ms on average and on initial throw it went up to 200ms+... for a simple NotFound response.
@hopkientran2534 yh, so I stick with VS Code as my possible (Linux and Windows) until I need some heavy refactoring to do. VS 2022 has also caught up a bit 😊
Define too much RAM usage. Is it causing actual out of memory usage issues for other processes? Lots of programs ask for more RAM than they need and will release it when the system requests it, this avoid fragmentation issues and means you get more efficient GC. There's a toggle for "show memory indicator", you can see use vs allocated, e.g. I am using 1.7GB/3.4GB (+1GB for back-end bit on top), you can lower this via "change memory settings", but 4GB is really not much for heap. If you have
Exception handler is how Laravel does it as well. But then datadog sees tons of errors en exceptions happenings in code which are just false poisitives :(
Datadog should only be handling uncaught exceptions, so stuff like user id doesn't exist on login are common ones and you'd use a key not found exception and should end up being grouped (be it by endpoint or type or both). For us, entire company moved off of datadog as just too expensive for what it is if have entire dev and devops team already to manage open telemetry and grafana dashboards (and can reuse most templates). If you're C#, often hosted on Azure, Azure Insights is also great.
They do... as a matter of fact, the ControllerBase has a PropertyDetailsFactory property which you can use by creating a custom implementation, registering it, and then use the built-in Problem method to invoke it.
should not we be throwing exceptions nick? you always mention in some videos not to throw exceptions but to return a response result. And now you are throwing an exception. i am confused
What if my API uses snake case instead of camel case? Should the problem details also be in snake case (which would be against the standard, I guess?), or leave them as camel case, making it a massive inconsistency?
Ok, all properties included in the standard use one-word-only names (e.g. 'status' instead of 'status_code'), so I guess they did think about this beforehand!
Both, you code to check against the status code if error or not, title to map error if custom message, and if not pass the rest back/up the chain through systems and a human can read it at the end to know what the issue is. Just a 400 is going to be annoying, and lots did that because every error response was different, so couldn't easily map it, this solves that.
Nice tutorial and to be honest I've done something similar in my project some time ago, but then I've watched one of your videos: ruclips.net/video/a1ye9eGTB98/видео.html&ab_channel=NickChapsas which as far as I remember shows some other approach and says that global exception handlers are not the best way, so now I'm a little bit confused. I'm still using the global exception filter and personally I think it's an elegant solution. One one or another, nice video as always ;)
As always Microsoft try to enforce some habits, i don't like that and this one, too much informations we don't need, a personnal returned envelope object, will be nearer my needs. Of course, you are one partner of Microsoft, they give you you give them.
This in fact makes a lot of sense. It is not perse REST but more like RPC. I use it in all my services. When I get a 404, I know that it is infrastructure related, VPN issue, Firewall issue, Company policy, Azure Gateway issue or whathever, but has nothing to do with my service. I use UseCase driven development so my Usecase (like vertical slices) give a Response.success(response object), or Response.error(ERRORS.USER_NOT_FOUND....) which is not aware of infrastructure, so I can use that response to map it to HTTP responses or back to my CLI programm for example.
If You can return it don't throw it. I always think that If you can use return don't throw exception. I think Exception should only be used for when the system is facing unexpected problem and it unable to handle it nor continue to work properly (db connection for example). Not forget to mention that there are a performance hit when throwing an exception.
Sad to see unopinionated devs in 2024 and a mindset of "if you dont use problemdetails,results pattern,clean architecture etc you are bad dev". While CleanArchitecture is something useful the problemdetails is totally useless thing that restricts you how to expose your endpoint results. An API should have its own response contract. Most of the times you will have to expose additional fields in response that this standarized way is just restricting you for no reason. Sad to see blindly follow things and make our life harder.
You don't seem to know what Problem details are then. They're just a standardized format so any system can at least read status and title. It's got extension members as part of the spec (rfc7807 3.2 "Extension Members"), usually add extra stuff there like traceId. The new RFC just adds "type" to a common registry schema, which most organizations were already doing to their own registry. I have yet to find a use-case where ProblemDetails or ValidationProblemDetails doesn't cover every use-case easily, all it's done is standardize that we write e.g. detail instead of details, and errors as error array instead of just error and then the other half uses errors in the same system.
I still fail to understand why people include strings in machine-readable APIs. End users aren't supposed to see these error (heavens forbid you show these errors to the user because they can contain internal details and are non-localized), and you probably want to parse the respond to figure out what's going wrong in the client code anyway, so just dumping the error into the logs is just lazy coding. So why not just return error codes and include relevant context for the error? If we return enums or types for errors in our non-web APIs, and that works just fine, then why can't we do the same for web APIs?
Depends what kind of error codes you return. If you return something like 1337 as error code then you need to document, include as description or have some other method to translate the error code. A named error code "WrongUnit" might be an option. But it is not really an enum but rather an enum name as string. Also the producer might have a lot of error codes which might be changed so logging the error description might be reasonable and not lazy. Anyway I do think it is good to have a unique error code so yuo can translate the error to the user (ithe rin non technical terms or actual translation to user's language)
@@Sayuri998 because the overhead is minimal for most systems, dumping the error into logs is the exact right thing to do with enough info that you can understand the entire error when looking at it later as dev, and you want the error descriptive enough that another dev integrating with your system can understand it/what the correct thing to do is (and avoid the support call).
@@Masterrunescapeer Right, but my argument is how is a web api different from directly calling a library method? We don't return error strings in our error objects. If the error code (a string, not a number) is clearly named, the developer will understand what it means. The first time they see it, they probably have to look it up in the documentation, but that's the same for an error string too. (And I would argue that you SHOULD look up errors when you see them.)
I know its a part of the specs but by doing this you now require your users to handle many paths of responses. Its very painful. Just simplify to always return 200
The client already has to handle many paths - they have to differentiate successful responses from errors. Returning a proper HTTP code makes this easier, as you don't have to parse the response to know whether it's an error or not. This also means that analyzing the error rate is easier, as you can use the web server's logs (which contain the HTTP status code)
Client goes from handling a success message and any number of different error messages to handling just success and problem details. For e.g. front-end can check if not success, check title, if known title (or type with new one) and I want a specific error for that one, or pass through detail or just say error. This avoids the issue of e.g. "error" or "errors" or error being e.g. request param name. RFC9457/7807 both have a JSON object example.
At a lower resolution, sometimes, but for most of us as devs we have dark mode and usually better bandwidth, so much prefer not looking at a flashlight.
Hey man, I’m not a C# dev but I just wanted to say that you make some really good content, and I really respect your hustle. Keep up the good work!!
I am a C# dev, and his content is invaluable!
Nick Chapsas alt account confirmed
The debate about what constitutes an 'exception' is one that has been argued about on many a fora!
I am actually with Nick on this, but my observation on this is that the vast majority of developers are being extremely lazy in how they handle errors. 'Exceptions' are situations where software is unable to continue: like run out of disk space, run out of memory, hardware failure, network connection lost, database connection lost etc. Situations where a user has entered incorrect data and software is unable to proceed are NOT exceptions. They ARE DATA ERRORS caused by data not conforming to business rules and should be handled as part of a normal result returning mechanism. The fact that a lot of developers functionally overload the exception handling system (which goes against SOLID) to return both exception and normal data errors is laziness and a failure to identify things that are fundamentally different.
Myself, I return a standard 'result' object as part of an http response and I adjust the http error code accordingly. This means that the calling application has no need to know whether an exception or general data error was returned: they are both returned as a result with an http error code.
Exactly!! I handle expections and data erros in the same way, because they are not the same!
Semantically? Yes. I can't disagree here.
However in practice, custom exceptions + handler are safer - as throwing an exception causes code execution to stop unless explicitly handled, which prevents the code from continuing to run in invalid state. On top of that, they result in less boilerplate code - a simple line to throw, and you don't need every caller and their dog to have to worry - you just have a global handler.
There honestly are benefits to both. I'll pick less boilerplate and less complex code + standardized way to follow the exceptions. I think it's the better way - just wish it had the less amount of performance hit.
Yes. Have domain-http-ignorant result objects, which then you map to HTTP status codes and ProblemDetails.
Code 200 when you get something successfully, 500 when really an exception, 204 when you mutate state successfully, 409 when validation problem.
Leave code 400 alone, that's literally for "bad request" - bad url, can't deserialize JSON in request body and so on.
I personally use a custom result object which can either be returned as a success or a failure. When it's a failure I can specify an error code, a message, and an http status code. Then the caller of the method checks if the result is a failure and if yes the caller also returns a failure and passes the original failed result higher. This results in errors bubbling up all the way to the controller where I have the full call stack thanks to the caller attributes which I can now log and I can also convert the result into a DTO which the API will return. It's ultra fast and no exceptions are necessary but it requires you to follow a very specific coding pattern.
been doing it this way for 15 years, one thing to add, which may be obvious, but you'll need to handle various kinds of exceptions or add a "severity"property if you want to return different kidns of status codes so that an unexpected exception doesn't get returned as a bad request for example. Though of course, I don't really throw exceptions for validation, which is handled separately,
How about creating an API Response class that has 3 different methods overload. Each of the method overloads takes a code, a message string and then a data object. Then in your controller, you can return the normal BadRequest and then use the Response class in it.
Say
return BadRequest(new Response(400, "custom validation error"));
In a 200 response
return Ok(new Response(200, "Something fetched successfully", data));
This was you can use the Response class for 200, 400, 404 and 500 responses, That way your API Response remains concise and consistent.
If you've separated your application layer from your presentation layer (API), then the validation will likely happen in the application layer, and that will not know anything about HttpResponses. So you're back to the same problem. How does your application layer return an error? Is that via an Exception, or some other tuple/result class.
If you're in your controller anyway, you can return whenever you like, using whatever response class you like.
You should use a separate class for each response type and it's okay if they're different or the same. You can repy on the OpenApi schema to know what the response looks like on the client.
Error responses should be standardized though for your API, preferably using ProblemDetails
@@Dustyy01 yeah this too.
Throwing exceptions for validation errors? Big ugh. We need that code cop guy to review this 😆
Like I said, I'm against it 100% but you know how it is. People still use it so I thought I'd show it
Possible new part 2 showing a better way to do it without throwing exception ? Just an idea 💡
Your operation can't continue because a business rule is not met, how is that not exceptional to so many people?
@@SuperLabeledTo most (including me) it's a violation of a defined requirement, not an unexpected exception. It's also easier to test imo.
@@SuperLabeled Exceptions should represent an unexpected error. Bad requests are part of normal app behaviour - not at all unexpected, thus not quite "exceptional". At least that's how I see it.
As always, many thanks Nick! 😊
We can throw ProblemException from switch expression without equality checking
I wrote this 10 years ago as a middleware...
Error handling with exceptions is nice but its the easy way out.
Easy way out sounds good. Is the harder way out better?
@aliengarden more customizable, and exceptions tend to be memory and flops heavy.
this is great, follow every video you release, I want to buy the unit test course that you have, I'm having a lot of questions on how to create good unit test and correct way to do it, but not sure if it will help me to understand how to create them will all dependencies or scenarios that methods have, what can you recommend me ?
Hey man, great video! Went straight in a implemented in my side project - great!
But i have one problem.. when using typedresults to generate a openapi spec with swagger, the documentation says nothing about a possible code 401.
Is there someway to generate a nice open api spec with the TypedResults.Problem or TypedResults.ValidationProblem?
Somebody should send your video to SAP
Don't go too fast, they're still busy with s4hana migration
Are there worlds that exist where setting a problem type parameter is going to be different from the status code parameter? I know this was a very bare bones example, but I'm just having a hard time not cringing at setting two properties/parameters to what amounts to the same value in different formats (enum vs string). That all being said, this was very eye-opening on a few different fronts and I (don't) look forward refactoring some (all) of our company's apis xD
242 / 5 000
Thanks for showing this, including the exception part. Very useful! Although I am personally against exceptions when the problem can be foreseen, it is not always me who decides. If my client's strategy is to use exceptions, then...
What is the standard way to handle standard ProblemDetails on another API side? Like I call your example API via another API with HttpClient because the result can be the expected object or the ProblemDetails. Is there a good and standard way to do this?
A ProblemDetails response should also set the Content-Type header to "application/problem+json" so you'll know response is a problem details response.
@@dylan8463 Thank you for the answer. I am more interested in the following part. For example, if I use Refit or just the base HttpClient, what should be the return type? Should it be Result, and should I create the result based on the header?
@@Suv3g00 Ah sorry I misinterpreted your question. Personally in a Blazor project I made my own EnsureSuccessStatusCode extension method that parses the problem details response and throws a custom problem details exception, when the response is not 2xx. You could do something similar with the Results pattern too.
Wait. Not many videos ago we were complaining about throwing exceptions and how expensive they were.
What changed?
Hi Nick, funnily enough, today I got a issue assigned that has is basically "figure out a way to add generic error results to API".
How would you go about following this standard, if the API is also a backend for a SPA that needs some kind of errorCode or unique, to translate the real message to a users preferred language?
@nickchapsas when you add the requestId and traceId to the ProblemDetails, did you accidently flip the keys, or am I missing something?
I think he did swap them accidentally
Very good content but here is my 20 cents! You should throw exceptions only when you don't handle it in the code logic (something unexpected). Also this can lead to a performance issues as throwing exceptions is expensive process. When you validate you should return the object not throw and exception.
Correction, this does not adhere to the new RFC 9457. It's the same old RFC 7807 implementation that we've had for years, as can be seen in 7:46.
Also as of this comment, RFC-9457's status is still 'PROPOSED STANDARD'. So currently 7807 is still the standing standard.
RFC 9457 is that type can use a shared registry (most were already doing this), that validation errors has an errors array with description and they tried to be more clear. It's just a continuation of RFC 7807.
I literally just yesterday opened a PR matching this pattern fuck yeah
Well I did some things different but still
Same here, ironically enough
If I recall at least ast far back as net7 you would get with if you used standard controllers and the API controller attributes and model state for bad requests and you could return a validation problem manually if you wish (in combination with model state)
cool. How might you pass a domain specific error code as an additional property here? (to avoid leaking internal / api type text messages up to the UI)
The easiest way would probably be to use the Extensions dictionary on the ProblemDetails type and put it there (get the code from your domain exception or whatever you app has for this)
You Could set the Detail in problem details. If want more specific then like Nick did at 5:03, you could add a property using Extensions. For Example I add an array of error strings or a dictionary of error code string, description string. I generally just pass the domain error. So using ErrorOr I set the error code and error description, map that to a dictionary
I use a static class with an enum for error codes like USER_NOT_FOUND then use a (frozen) dictionary for error descriptions, one for each language.
this class is in a shared project that the client and server use
I'm gonna watch the video and see if there's a better way to do this.
If it's used more than once, usually standardize it in a Dto, extend problem details with custom error message/codes set. If UI needs a custom error message, that's on them, they can use status code and title, and ignore the message (did this for e.g. translation stuff). Most of the time your message text is the same in back-end and front-end though, since validation error will just be "Name is required" or something like that.
Homie went from "we just launched a brand new course" to "we just launched 23 new courses"
Best place is to use it in your global error API handling middleware class
I'm really curious to know how you validate a request using functionnal programming and monads
You need a monadic type with a succes and an error value often called an 'Either' (in C# another popular name is 'Result'). You then build a monadic pipeline where the input is the request and the output the response. First step is typically to transform the request into a valid request. You then continue with more transformations until you have the response. Notice that the success type can change at each transformation. However, the error type is always the same. In this context the error type can be a problem details or something that can be converted into a problem details. The validation method would take the request as the input and return either a valid request or a problem details (wrapped into the monad). Each subsequent transformation takes the succes type of the previous step as the input and produces a new success type for the next step. By the wonders of the bind operator you can then build a succinct data transformation pipeline using monads. At the end of the pipeline you match the value of the monad into perhaps a 200 OK payload or a problem details with varying HTTP status. Notice that validation often consists of many smaller steps that lends itself to be implemented using monads and doing that gives you nicely composable validation rules. Rules like "string is not empty" and "integer is positive" that return an 'Either' can be composed into more complex rules using the bind operator. Unfortunately, you might try this approach together with async methods and run into problems and that's because 'Task' also is a monad. Nested monads can be troublesome but there are ways to deal with that. It just require a bit more work but the end result can still be great.
@@MartinLiversage What is a bind operator in C#? Is it related to linq's SelectMany? Also, it would be terrific if you can point to a repo that illustrates this approach.
Is there any way to add request id to not catched exceptions?
Hi Nik, how to return exact same format from Model Validation?
I have a better (dumb) question - how do you use those trace IDs?
Everywhere, you can find what they are, what they are used for, and how to add them somewhere, but not how do you use them in debug.
Based on that magic number, how do you find where the error occurred in the code?
TraceId is used to track the flow of requests through multiple services, not for debugging individual application code.
They are used to follow the specific request in logs. The exception and stack trace are in your logs, alongside the trace ID.
@@Kwpolska So, in other words, you still need a log with some meaningful text, to see what happened. In that case, why not to use GUID/UUID? What is so special about TraceID if you need to "pair" it with a log?
@@martink.7497 Why invent your own ID if there's already TraceID? Plus, it is passed in HTTP request headers, so that you can correlate requests made from one service to the other.
I basically do the same thing but have added an upfront check of the data model so I don't get into the API for basic validation errors through a service behavior.
public static class Behaviors
{
public static void Register(IServiceCollection services)
{
services.Configure(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var badRequest = BadRequestProblem.Create(context.ModelState);
return new BadRequestObjectResult(badRequest);
};
});
}
}
Poland mentioned, Polska gurom!!!!
Jokes aside, keep up great work
Hey Nick. Please use another capturing phrase for your thumbnails. "People still get this wrong" is just pure clickbait and doesn't help deciding if the high quality content you produce is worth watching. Well, you might argue that the video caption "The Right Way to Return API Errors in .NET" is the second source of information, yet the caption is not shown at the end of a video showing the suggestion for the next video to play. You're better than these clickbaits, aren't you? Thanks for listening.
It's remind me Salesforce where it returns 200 on everything and inside the response you've got 400
I personally dont like the idea to use exceptions (and throw them) for validation handling and other control flows. Thats not the idea of exceptions and a mis-usage in my point of view.
God forbid explaining why do you think that and what better alternative you have.
@@huszaristvan8246 The alternative is to use IF-THEN-ELSE and check your return codes, rather than relying on the exception handler to bomb out and catch your code because you haven't coded something that you should!
Validation handling is checking user data according to rules. If it doesn't match, it is a DATA ERROR that should be handled as part of every-day processing and result responses. It is NOT an exception.
Returning 200 OK with { errors: [///] } is the OG way
Go wash your mouth out with SOAP 😊
Agreed otherwise your force the integrator to handle multiple paths and parsing. Auth, validation, errors, and actual result
eeewww I hate APIs that do this. It's the worse
@@dylan8463 you probably dont handle all the possibilities in the ones you love
Unironically, yes. This standard basically adds nothing useful.
It should be mentioned in REST API course
It will on the update
Warsaw ❤ hello from Poland 🎉
This seems like a slightly worse version of doing try/catch in your own exception middleware. I really like to see what is going on instead of adding options and services that do their thing in the shadows.
It's been a minute since I did a .NET web api, but there's a much simpler way to map exceptions to status codes globally with way less code. I'm not sure why they're adding worse patterns when a better way has been available for 8+ years.
Is this work with controllers
the right way is always subjective
What if you don't want to help a malicious actor by returning correct response for every request?
then you're just hurting whoever is using the API, and they'll hunt and find you
Malicious actors aren't deterred by that. Only clients.
If you want to return more detail while developing locally and then restrict it once deployed to your server, you could inject IHostEnvironment and determine your environment and the level of detail you want to return. I would also suggest injecting ILogger and logging the exception so that once deployed, you can find the problem and the larger detail in your logs.
You also conditionally compile it with compiler directives. DEVs build the debug version that gives them more info and server build the prod version without the details.
I wonder how many got trigger when you threw the Exception instead of returning it with a Result.
I was looking for something like this in the comments, isn't it bad practice to throw exceptions instead of returning something? Even Nick had mentioned it in an older video that it is.
@@dimitrislaliotis504 It's opinion, not bad practice.
@vonn9737 some say it's performance hit also
@@dimitrislaliotis504 Yes. I think Nick did a video on it, and found throwing exceptions were about 3 times slower than error handling without exceptions. But that doesn't make it bad practice, it's just a design consideration.
@@dimitrislaliotis504 Throwing exceptions absolutely is a performance hit because the whole execution stack has to be unwound, scopes closed down and stack dumps created.
We should not be writing code that uses exceptions as a convenient (lazy) way of bombing out of a call stack instead of exiting the call stack gracefully!
Validation errors should return HTTP 4xx errors, not 5xx. 4xx means an error caused by the client whereas 5xx means an error caused by the server. Invalid input from the client is always considered as an error that was caused by the client.
What are the uses of requestId and traceId?
Logging correlation. If you have multiple systems calling each other you can follow the entire flow. Works best if you use something more than just text files for logging, but even in a text file it is better than nothing.
Awesome!
how is the performance? throwing exceptions is possible not the fastest way to get a response.
if the concept of exception still same, exception usually faster but it has cost. exception basically an interrupt, it's order to system/thread pool "stop whatever you are doing, we have problem!". so throw exception for validation, it should faster but it's like saying "all of you, stop what you are doing, this guy send wrong character", it's unnecessary cost to the thread. throwing exception should be critical issue, such attempt access violation, etc.
It does not matter. You optimize the happy path, not the other way around.
Unless you actually expect to be bombarded with a lot of bad requests. But then the question is: why is that?
In my testing I had about 5-20ms to return a Result, same request with exceptions took about 80ms on average and on initial throw it went up to 200ms+... for a simple NotFound response.
I have a problem when using rider on windows, that is rider uses too much ram, do you have any way to fix it?
It's even more terrible on Linux, especially with the watcher stuff
@@geraldmaale that's terrible
@hopkientran2534 yh, so I stick with VS Code as my possible (Linux and Windows) until I need some heavy refactoring to do. VS 2022 has also caught up a bit 😊
Define too much RAM usage. Is it causing actual out of memory usage issues for other processes? Lots of programs ask for more RAM than they need and will release it when the system requests it, this avoid fragmentation issues and means you get more efficient GC.
There's a toggle for "show memory indicator", you can see use vs allocated, e.g. I am using 1.7GB/3.4GB (+1GB for back-end bit on top), you can lower this via "change memory settings", but 4GB is really not much for heap. If you have
no link for the code this time?
Exception handler is how Laravel does it as well. But then datadog sees tons of errors en exceptions happenings in code which are just false poisitives :(
Datadog should only be handling uncaught exceptions, so stuff like user id doesn't exist on login are common ones and you'd use a key not found exception and should end up being grouped (be it by endpoint or type or both).
For us, entire company moved off of datadog as just too expensive for what it is if have entire dev and devops team already to manage open telemetry and grafana dashboards (and can reuse most templates). If you're C#, often hosted on Azure, Azure Insights is also great.
Can be done in multi language?
Yeah every language should implement it in some way
@@nickchapsas I think they meant different human languages, not programming languages
this applies to minimalapis but i wonder if controller based guys als have the same concept behind
They do... as a matter of fact, the ControllerBase has a PropertyDetailsFactory property which you can use by creating a custom implementation, registering it, and then use the built-in Problem method to invoke it.
Make a video about garnet
should not we be throwing exceptions nick? you always mention in some videos not to throw exceptions but to return a response result. And now you are throwing an exception. i am confused
Hahaha I see what you did, taunting poor commenters with this approach :)))
"I identify as a problem".
With your example you send 400 even if the correct code it's different (e.g. 401/403/409)
Result pattern makes your code tedious,
Exceptions works better and infact Microsoft uses a lot on the framework you're coding on
What if my API uses snake case instead of camel case? Should the problem details also be in snake case (which would be against the standard, I guess?), or leave them as camel case, making it a massive inconsistency?
Ok, all properties included in the standard use one-word-only names (e.g. 'status' instead of 'status_code'), so I guess they did think about this beforehand!
Nick why you should use Result pattern for validation errors also Nick throw exception on validation
Seems like a long-winded way to return a 400 status code. Who's reading the message - human or machine?
Both, you code to check against the status code if error or not, title to map error if custom message, and if not pass the rest back/up the chain through systems and a human can read it at the end to know what the issue is.
Just a 400 is going to be annoying, and lots did that because every error response was different, so couldn't easily map it, this solves that.
Nice tutorial and to be honest I've done something similar in my project some time ago, but then I've watched one of your videos:
ruclips.net/video/a1ye9eGTB98/видео.html&ab_channel=NickChapsas
which as far as I remember shows some other approach and says that global exception handlers are not the best way, so now I'm a little bit confused.
I'm still using the global exception filter and personally I think it's an elegant solution.
One one or another, nice video as always ;)
As always Microsoft try to enforce some habits, i don't like that and this one, too much informations we don't need, a personnal returned envelope object, will be nearer my needs.
Of course, you are one partner of Microsoft, they give you you give them.
http code: 200
response:
{success:false,
errorCode:401,description:unauthorized}
This is the coolest way possible
I've ran in this kind of shit so many times, I'm impressed how much effort is taken to develop this kind of shit
This in fact makes a lot of sense. It is not perse REST but more like RPC. I use it in all my services. When I get a 404, I know that it is infrastructure related, VPN issue, Firewall issue, Company policy, Azure Gateway issue or whathever, but has nothing to do with my service. I use UseCase driven development so my Usecase (like vertical slices) give a Response.success(response object), or Response.error(ERRORS.USER_NOT_FOUND....) which is not aware of infrastructure, so I can use that response to map it to HTTP responses or back to my CLI programm for example.
@@richard-t7c5o I'm very glad I'm not a consumer of your services then
If You can return it don't throw it.
I always think that If you can use return don't throw exception.
I think Exception should only be used for when the system is facing unexpected problem and it unable to handle it nor continue to work properly (db connection for example).
Not forget to mention that there are a performance hit when throwing an exception.
More overcomplicating a simple problem
Comedy of Errors
Sad to see unopinionated devs in 2024 and a mindset of "if you dont use problemdetails,results pattern,clean architecture etc you are bad dev". While CleanArchitecture is something useful the problemdetails is totally useless thing that restricts you how to expose your endpoint results. An API should have its own response contract. Most of the times you will have to expose additional fields in response that this standarized way is just restricting you for no reason. Sad to see blindly follow things and make our life harder.
You don't seem to know what Problem details are then. They're just a standardized format so any system can at least read status and title. It's got extension members as part of the spec (rfc7807 3.2 "Extension Members"), usually add extra stuff there like traceId. The new RFC just adds "type" to a common registry schema, which most organizations were already doing to their own registry.
I have yet to find a use-case where ProblemDetails or ValidationProblemDetails doesn't cover every use-case easily, all it's done is standardize that we write e.g. detail instead of details, and errors as error array instead of just error and then the other half uses errors in the same system.
Get out of my head Chapsas!
I still fail to understand why people include strings in machine-readable APIs. End users aren't supposed to see these error (heavens forbid you show these errors to the user because they can contain internal details and are non-localized), and you probably want to parse the respond to figure out what's going wrong in the client code anyway, so just dumping the error into the logs is just lazy coding. So why not just return error codes and include relevant context for the error? If we return enums or types for errors in our non-web APIs, and that works just fine, then why can't we do the same for web APIs?
In case of error you can log the "description" so you don't have to go all the time to some manual with the mapping of codes-description.
@@GeorgeGeorge-u5k A well-named error code (aka string, enum) will tell you all you need without a description. We do it all the time in our code.
Depends what kind of error codes you return. If you return something like 1337 as error code then you need to document, include as description or have some other method to translate the error code. A named error code "WrongUnit" might be an option. But it is not really an enum but rather an enum name as string. Also the producer might have a lot of error codes which might be changed so logging the error description might be reasonable and not lazy. Anyway I do think it is good to have a unique error code so yuo can translate the error to the user (ithe rin non technical terms or actual translation to user's language)
@@Sayuri998 because the overhead is minimal for most systems, dumping the error into logs is the exact right thing to do with enough info that you can understand the entire error when looking at it later as dev, and you want the error descriptive enough that another dev integrating with your system can understand it/what the correct thing to do is (and avoid the support call).
@@Masterrunescapeer Right, but my argument is how is a web api different from directly calling a library method? We don't return error strings in our error objects. If the error code (a string, not a number) is clearly named, the developer will understand what it means. The first time they see it, they probably have to look it up in the documentation, but that's the same for an error string too. (And I would argue that you SHOULD look up errors when you see them.)
I know its a part of the specs but by doing this you now require your users to handle many paths of responses. Its very painful. Just simplify to always return 200
Then they will not know what happened or what to do
@dimitrislaliotis504 having a wrapper class of Response with a bool IsSuccess, T Data, and string Message is better imo
The client already has to handle many paths - they have to differentiate successful responses from errors. Returning a proper HTTP code makes this easier, as you don't have to parse the response to know whether it's an error or not. This also means that analyzing the error rate is easier, as you can use the web server's logs (which contain the HTTP status code)
@Daniel15au and 404 and why 304 how about 201. Why not just map every conceivable outcome so you can be as rest complaint as possible.
Client goes from handling a success message and any number of different error messages to handling just success and problem details.
For e.g. front-end can check if not success, check title, if known title (or type with new one) and I want a specific error for that one, or pass through detail or just say error.
This avoids the issue of e.g. "error" or "errors" or error being e.g. request param name.
RFC9457/7807 both have a JSON object example.
Suggestion: In my opinion, dark mode makes videos harder to see
I strongly disagree my friend. Dark mode is essencial on the screen age
@@afonsocarvalho3124 I agree with @mmuekk, dark mode is so much difficult, specially if it is sunny.
I would stop watching if he did light mode since i can't control that on my end in any way
At a lower resolution, sometimes, but for most of us as devs we have dark mode and usually better bandwidth, so much prefer not looking at a flashlight.
Result pattern makes your code tedious,
Exceptions works better and infact Microsoft uses a lot on the framework you're coding on