-
Notifications
You must be signed in to change notification settings - Fork 709
Allow to favor throwing Exceptions instead of swallowing and formatting them directly to Response #886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Apologies if it sounds completely pedantic, but HTTP cannot throw an exception. Problems Details are the established mechanism to provide additional information for an error response in accordance with RFC 7808. A scenario such as an invalid API version isn't exceptional, but it is a bad request (e.g. HTTP status code Exceptions are not free and come at a cost. They should be reserved for things that are truly exceptional. Furthermore, if an exception is thrown and unhandled, it will result in HTTP status code Fear not! That doesn't mean you can't change or alter the behavior. Problem Details are can be defined a couple of different ways. Although MVC (Core) provides the Problem Details will not let you change the response, only the error details. There is no way to change the response behavior - by design; however, it is possible to change whether parsing of an API Version is valid (or not). I'm not sure that's what you're looking for, but it's possible. I think you just want more control over the error response provided, which can be achieved via custom |
I am aware that HTTP perse cannot throw exceptions, but I do believe your library should have the option to throw exceptions instead of interacting directly with the response pipeline as it is currently the case. I am also aware that there is no such thing as a free lunch; but the performance penalty involved around exception handling is not something that concerns me, and as I proposed, it could be an option you provide - maybe for inspiration, it could be in a similar way that Microsoft did there implementation on HttpResponseMessage where they introduced EnsureSuccessStatusCode(): https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage.ensuresuccessstatuscode?view=net-6.0 Again, I like the flexibility of your framework - and I admit I am probably the rule to the exception (no pun intended), but I do believe firmly in all errors should be treated as exceptions, and not being wrapped in delivery objects. That said, I will look further into both your code and the feedback you provided here, and see if I can find a way to bypass your direct interaction with the response pipeline. Lastly, to give some insight on my profile, I have been working with REST APIs for a decade now and has delivered many API with HATEOAS maturity level 2; a few POC with maturity level 3; all exceptions thrown by an application will be translated into their respective response - and if in fact unhandled, of course result in a 500 (which is fine when it is in fact the app/server that has an issue). Anyway, this was just to establish that I - to some extend - know what I am doing and what I am proposing :-) |
REST assured, I'm sure you know what you're doing. 😉 An invalid or unspecified API version on the server side isn't an unhandled error to the server; it's a client mistake. Client errors (e.g. This particular case is in the gray area. In terms of code, what you're looking for is ApiVersionPolicyJumpTable.GetDestination. The routing system uses a direct graph under the hood and this is the place to determine how to jump to the next node. Speaking of exceptions, there used to be a single gap exposed via If you have some more details about your scenario and setup, I might have some additional suggestions. It's important to note that it is intended that once you opt into versioning, there is no such thing as unversioned anymore. Even if you don't explicitly provide a version, the It may be possible to use some middleware or so other facility to achieve your desired result without specifically introducing exceptions. |
Thank you for your thorough walkthrough of the code - as well as your viewpoint. I have great respect for both you and the code you are writing, and in many ways, your code stands out for an inspiration to everybody. That said, we both have some dogmatic viewpoint and practices; and although I agree strongly to your 400 BadRequest, I also believe that this is an exception that should be raised to the client. A common and consistent way to raise a problem, that in return can be translated into lets say RFC 7807 or "my" way of having a consistent way of reporting errors to the consuming client. I would argue that client or server specific errors are still that; errors that needs to be communicated clearly to the consumer. Be that in the 400 or 500 range is irrelevant .. 400 range is just particular good in explaining why the server has chosen to respond the way it did, e.g., the more common 400 (invalid syntax), 401, 403 to the more sophisticated 409, 412, 428, 429 or even custom non-standard statuses. Problem with your approach is, that a lot extra code now needs to be written to support a consistent way of serving error messages to the consuming part of an API; so they don't end up having different error experiences - one way from your framework - another way from my error handlers. I am pro following standards - its just the implementation I question. This is actually a good example the one we are having. I could choose to implement a middleware component that translates all exceptions into lets say RFC 7807 - because I encourage and uses exceptions consistently. I could also choose to follow the path I have taken - and provide even more details for developers that surpasses the said mentioned standard .. or I could do adapt something third. With exceptions this is easy; with different framework (yours being one of them), I could end up having to many chefs cooking there own error handling directly to the Response pipeline rendering a consistent way to communicate faults impossible. Common denominator is proper exception handling within your ASP.NET application. Anyway, we have different viewpoints and I respect that .. I will continue to embrace your work as I have done since 2019 .. Keep up the good work - and thank you for taking time to respond. For reference (and I am sure you know about it): https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions I also encourage to read through the Framework Design Guidelines - all editions .. they have shaped me into favoring consistency and usability .. in some areas maybe wrongfully .. I reckon that I might move in the area of control flow :-) Feel free to close this proposal if you are not convinced that your framework should allow for opt-in to default exception handling. |
Healthy discussions are welcome here. There have been plenty of heated knives out debates. 😉 There is usually a reckoning, but sometimes we agree to disagree. I've followed the published design guidelines in all its many forms over the past 20 years, including reviewing the many framework sources (even with decompilation before the OSS days). Per your cited guidelines, "Design classes so that exceptions can be avoided". Exceptions should be exceptional. The client is the one calling the server. The server is not a client of this library. By opting into this library, or even ASP.NET Core for that matter, there are certain behaviors you sign up for. Many behaviors can be customized or extended, but not all. The goal is to have the 80% fall into The Pit of Success and give the 20% the tools to tweak knobs. A concerted effort is made so that people can't go over the rails. I might make it difficult for you to do some custom things (albeit sometimes intentionally), but it's pretty rare that I would provide no hook whatsoever. Those that really want to see what happens after the red pill, I let them. A bit of history... Before Implementing https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-errors Oddly, API Versioning's behavior with respect to client errors during route processing is congruent with what ASP.NET Core does. You will not get a It makes little difference to me how you want you want to present your errors. If you don't like |
I truly enjoy your thorough comments; thank you for taking the time for that - even though I can imagine you have a busy schedule. I agree to disagree - and I will continue to have my Matrix-love for your fine library; I must say that it was not without a challenge to tweak your code to my "vision". As you mentioned, most of it was fairly simple - I managed to do that by making a custom implementation you can view here: https://github.com/gimlichael/Cuemon/blob/development/src/Cuemon.Extensions.Asp.Versioning/RestfulProblemDetailsFactory.cs The challenging part was your endpoint serving 415; this does not use the IProblemDetailsFactory as you decided to opt-out of content. Here I had to go to the creative corner and force my hand to make this extension (with the sole purpose of catching client side faults: https://github.com/gimlichael/Cuemon/blob/development/src/Cuemon.Extensions.Asp.Versioning/ApplicationBuilderExtensions.cs So, as I mentioned earlier, consistency is key for me; so if an API adapted RFC 7807 all is good and dandy and they can go on with their lives. BUT, if for whatever reason, someone adapted a different path, they can now fairly easy adapt their own customization with those two proposals. I learned a trick or two by examining your library in depth - so thank you for that - and thank you again for your perspective - it is much appreciated. I will follow you closely and see if I can keep up with your continued maintenance of your library; keep up the good work. |
Nice! Looks like you were largely able to achieve your goals with minimal effort. 😲 You are correct on I did notice from this PR, that ASP.NET Core is fixing their issue across the board. There are a bunch of changes. I suspect I will have to refactor to come into alignment, but the net effect will be identical or very, very close. You might want to peek at them since it would affect your strategy as well. RFCs and spec aren't for everyone, but I've found them grounding and useful as a guide against my own dogma and bias. Deprecation has long been supported, but conveying policy has been missing. Stumbling across RFC 8594 provided a clear and sensible way to do so. This inspired the feature to convey a Sunset Policy (>= |
A short musing... as I recall, I didn't add Since you are knowledgeable in this area, should it only be Today, everything is |
To further clarify... after updating some unit tests I realized how I got to plain old Scenarios:
I was originally thinking it was good to have parity, but if more information is available, why not add it? |
Reading about it, I think I would opt-in for 406 (GET) and keep 415 (PUT, POST). As a follow up; maybe provide an option for users to decide themself? I like the strict version (eg. 406 and 415), but other might like a more relaxed "policy", hence always return 415. Just some seeds for you to grow; as always - keep up the good work! |
The status shouldn't be based on method because you could have something like: POST /session HTTP/2
Host: api.server.com
Accept: application/json; v=2.0 This should return There are some ways people can change this behavior. It's way down in the routing system so it's not particularly easy to change. Furthermore, this has little consequence on the server side. There is some variability on the client side, but it's not really a recoverable error; the client is calling the server wrong. A client will almost certainly handle any Over-configuration is always a concern. I'm hesitant to add configuration for something that no one will probably ever change. Once it's added, it's hard or impossible to remove. There are escape hatches for those that are really motivated to change it. These are status codes inline with the HTTP spec, so I think that is good enough - for now. I have something working. Expect it in the next release.
|
Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
So when I was browsing my API using your excellent framework i noticed this "exception":
Problem is, that this is never thrown as an exception .. you are overtaking the Response pipeline and providing a "friendly" error message.
I would really like to have full control of exception handling - and I strongly believe a framework such as yours, should support this, and not try to take control of the Response pipeline and hereby interfere with expected outcome.
The "exception" is triggered because both Edge and Chrome has an odd version in their default accept header.
Describe the solution you'd like
I hope you will consider allowing throwing exceptions instead of swallowing them and transform them on my behalf.
This should be done while staying true to your intent of not breaking backward compatibility.
In this case, I would expect (by looking at your code) a FormatException which I could choose to ignore or format as I see fit.
It is vital that APIs has full control over exception handling - or in this case, the lack of it.
Additional context
Areas of interest I have found in your code:
https://github.com/dotnet/aspnet-api-versioning/blob/main/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs
https://github.com/dotnet/aspnet-api-versioning/blob/main/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionParser.cs
The text was updated successfully, but these errors were encountered: