For me I usually send back an HttpResponseException
and set the status code accordingly depending on the exception thrown and if the exception is fatal or not will determine whether I send back the HttpResponseException
immediately.
At the end of the day it's an API sending back responses and not views, so I think it's fine to send back a message with the exception and status code to the consumer. I currently haven't needed to accumulate errors and send them back as most exceptions are usually due to incorrect parameters or calls etc.
An example in my app is that sometimes the client will ask for data, but there isn't any data available so I throw a custom NoDataAvailableException
and let it bubble to the Web API app, where then in my custom filter which captures it sending back a relevant message along with the correct status code.
I am not 100% sure on what's the best practice for this, but this is working for me currently so that's what I'm doing.
Update:
Since I answered this question a few blog posts have been written on the topic:
https://weblogs.asp.net/fredriknormen/asp-net-web-api-exception-handling
(this one has some new features in the nightly builds)
https://docs.microsoft.com/archive/blogs/youssefm/error-handling-in-asp-net-webapi
Update 2
Update to our error handling process, we have two cases:
For general errors like not found, or invalid parameters being passed to an action we return a HttpResponseException
to stop processing immediately. Additionally for model errors in our actions we will hand the model state dictionary to the Request.CreateErrorResponse
extension and wrap it in a HttpResponseException
. Adding the model state dictionary results in a list of the model errors sent in the response body.
For errors that occur in higher layers, server errors, we let the exception bubble to the Web API app, here we have a global exception filter which looks at the exception, logs it with ELMAH and tries to make sense of it setting the correct HTTP status code and a relevant friendly error message as the body again in a HttpResponseException
. For exceptions that we aren't expecting the client will receive the default 500 internal server error, but a generic message due to security reasons.
Update 3
Recently, after picking up Web API 2, for sending back general errors we now use the IHttpActionResult interface, specifically the built in classes for in the System.Web.Http.Results
namespace such as NotFound, BadRequest when they fit, if they don't we extend them, for example a NotFound result with a response message:
public class NotFoundWithMessageResult : IHttpActionResult
{
private string message;
public NotFoundWithMessageResult(string message)
{
this.message = message;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.NotFound);
response.Content = new StringContent(message);
return Task.FromResult(response);
}
}
Take a look at this example, Stephen has a clear answer for you:
So this is what happens, starting with the top-level method (Button1_Click
for UI / MyController.Get
for ASP.NET):
The top-level method calls GetJsonAsync
(within the UI/ASP.NET context).
GetJsonAsync
starts the REST request by calling HttpClient.GetStringAsync
(still within the context).
GetStringAsync
returns an uncompleted Task
, indicating the REST request is not complete.
GetJsonAsync
awaits the Task
returned by GetStringAsync
. The context is captured and will be used to continue running the GetJsonAsync
method later. GetJsonAsync
returns an uncompleted Task
, indicating that the GetJsonAsync
method is not complete.
The top-level method synchronously blocks on the Task
returned by GetJsonAsync
. This blocks the context thread.
... Eventually, the REST request will complete. This completes the Task
that was returned by GetStringAsync
.
The continuation for GetJsonAsync
is now ready to run, and it waits for the context to be available so it can execute in the context.
Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync
to complete, and GetJsonAsync
is waiting for the context to be free so it can complete. For the UI example, the "context" is the UI context; for the ASP.NET example, the "context" is the ASP.NET request context. This type of deadlock can be caused for either "context".
Another link you should read: Await, and UI, and deadlocks! Oh my!
Best Answer
Update: ASP.NET Core does not have a
SynchronizationContext
. If you are on ASP.NET Core, it does not matter whether you useConfigureAwait(false)
or not.For ASP.NET "Full" or "Classic" or whatever, the rest of this answer still applies.
Original post (for non-Core ASP.NET):
This video by the ASP.NET team has the best information on using
async
on ASP.NET.This is true with UI applications, where there is only one UI thread that you have to "sync" back to.
In ASP.NET, the situation is a bit more complex. When an
async
method resumes execution, it grabs a thread from the ASP.NET thread pool. If you disable the context capture usingConfigureAwait(false)
, then the thread just continues executing the method directly. If you do not disable the context capture, then the thread will re-enter the request context and then continue to execute the method.So
ConfigureAwait(false)
does not save you a thread jump in ASP.NET; it does save you the re-entering of the request context, but this is normally very fast.ConfigureAwait(false)
could be useful if you're trying to do a small amount of parallel processing of a request, but really TPL is a better fit for most of those scenarios.Actually, just doing an
await
can do that. Once yourasync
method hits anawait
, the method is blocked but the thread returns to the thread pool. When the method is ready to continue, any thread is snatched from the thread pool and used to resume the method.The only difference
ConfigureAwait
makes in ASP.NET is whether that thread enters the request context when resuming the method.I have more background information in my MSDN article on
SynchronizationContext
and myasync
intro blog post.