WEB API httpException is not caught - c#

I have a WEB API application, where we add some session verification logic and then call base SendAsync method after that.
When we pass request containing byte[] larger than 4MB line response = await base.SendAsync(request, cancellationToken); throws a httpException "maximum request length exceeded", which is ok and can be managed through IIS settings. What bothers me is that next step - it's still calling controller method, where I can see that this byte[] parameter set to null. Controller method throws ArgumentNullException and this what end user gets.
First of all I can't understand why controller method still gets called after HttpException. And more important how can I manage to catch this HttpException, so that end user will see actual reason, why the request was not processed.
Thank you in advance.
public class SessionIdHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
//some additional logic here
HttpResponseMessage response;
try
{
response = await base.SendAsync(request, cancellationToken);
}
catch (Exception e)
{
response = new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent(e.Message, Encoding.UTF8, "application/json")
};
}
finally
{
if (signedOnHere)
{
Signoff(sessionId);
}
}
return response;
}
}

What bothers me is that next step - it's still calling controller
method, where I can see that this byte[] parameter set to null.
When you call base.SendAsync(request, cancellationToken);, you're effectively continuing the WebAPI pipeline, which will eventually results in the controller getting invoked:
What you actually want is to check the Content-Length header before you even send the request to the controller. You do so by retrieving it for the header of the request. If the size exceeds, you can return a response indicating that, otherwise, continue the pipeline:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// 4MB, perhaps you want to loosen this up a bit as other request
// characteristics will effect the size.
const int maxRequestSize = 4194304;
IEnumerable<string> contentLengthHeader;
request.Headers.TryGetValues("Content-Length", out contentLengthHeader);
var contentLength = contentLengthHeader.FirstOrDefault();
if (contentLength == null)
{
//No content-length sent, decide what to do.
}
long actualContentlength;
if (!long.TryParse(contentLength, out actualContentlength))
{
// Couldn't parse, decide what to do.
}
if (actualContentlength > maxRequestSize)
{
// If reached, the content-length of the request was too big.
// Return an error response:
return Task.FromResult(request.CreateErrorResponse(HttpStatusCode.Forbidden,
string.Format("Request size exceeded {0} bytes", maxRequestSize)));
}
return base.SendAsync(request, cancellationToken);
}
If you don't want this check for all the controllers in your WebAPI application, you can pre-route each DelegatingHandler.

Related

c# a task was canceled at the await call

I'm trying to hit my simple server's endpoint using the following code, but I keep getting "A task was canceled." during the await call. The server's logs don't show any errors and cts.IsCancellationRequested == false, however e.CancellationToken.IsCancellationRequested == true. Any advice on how to track down the cause of this cancellation? At the very least, how can I tell if it's coming from the front end or the server endpoint?
private async Task<string> SendSingleRequestToDlis(
HttpClient client,
StringContent requestData)
{
int timeout = 600000; // in ms
string dlisEndpoint = "myendpointhere";
string response;
using (var cts = new CancellationTokenSource(timeout))
{
//send
HttpResponseMessage request;
try
{
request = await client.PostAsync(dlisEndpoint, requestData);
}
catch (Exception e)
{
throw new Exception("Could not establish conection to model hosted on DLIS.", e);
}
....
You are not using your CancellationToken so don't implement it. Just use this:
string dlisEndpoint = "myendpointhere";
string response;
HttpResponseMessage request;
try
{
request = await client.PostAsync(dlisEndpoint, requestData);
}
catch (Exception e)
{
throw new Exception("Could not establish conection to model hosted on DLIS.", e);
}
If you wanted to use the cancellation token you actually have to pass it down into the client.
request = await client.PostAsync(dlisEndpoint, requestData, cts.Source);
At the very least, how can I tell if it's coming from the front end or the server endpoint?
You can know it's at the client because you're seeing an OperationCanceledException. A server can just return a response code, which you would see as an HttpRequestException.
i timed it just now and the cancellation happens at exactly 100s
This is the default value of the HttpClient.Timeout property. Set this property higher (or to Timeout.InfiniteTimeSpan) at the point where the HttpClient is created (or configured, if the HttpClient is injected).

How to propagate HTTP responses cleanly to consumers of a typed HTTP client in ASP.NET Core

Is there a good way of propagating an object on a successful response or the status code and response body to consumers of a typed HTTP client in ASP.NET Core?
Given the following API service:
public class TestApiService
{
private readonly HttpClient _httpClient;
public TestApiService(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("https://localhost:5000");
_httpClient = httpClient;
}
public async Task<string> GetVersion()
{
var response = await _httpClient.GetAsync("/api/v1/version");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
return null;
}
}
which is registered with the DI container via:
services.AddHttpClient<TestApiService>();
I would like to return the string value from the TestApiService.GetVersion() method if the response was successful or if the response was not successful return the status code and the response body.
It doesn't appear to be possible to do something like:
public async Task<string> GetVersion()
{
var response = await _httpClient.GetAsync("/api/v1/version");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
and get the desired outcome because the HttpRequestException thrown from HttpResponseMessage.EnsureSuccessStatusCode() does not include the status code or the response body.
There is an open issue about this on GitHub but I'm not sure if it will get implemented anytime soon or not.
While ActionResult does exist it seems to really be for the controller layer so I'm not sure if using it here is an appropriate use of that class or not or if there is a better way of getting the desired outcome?
It should be possible to create my own exception class and throw that from the service but I would really like to avoid that if there is some built-in mechanism that is usable instead.
Remove response.EnsureSuccessStatusCode() this is basically checking the status and if its not a 200 throwing the exception. Consider using response.IsSuccessStatusCode or check the response status code manually. Either way will prevent the raising of the exception which you don't want.
if (HttpStatusCode.Ok == response.StatusCode)
{
// Read your result
}
else if ( // handle the specific failure case was it a 404 or a 401)
{
string value = await response.Content?.ReadAsStringAsync();
// Read your failed result
return $"{response.StatusCode} {value}".Trim()";
}
The next question is how you handle and communicate failure to the callee's of your service? Do you want your service to be opaque to your client application?
Since your code is only returning a string, have you considered either returning something else such as an encompassing object { Success = true|false, Error = "", ErrorCode = 1234, Data = "value"} or simply throwing an appropriate exception to communicate the nature of the failure. E.g. You might want to throw an appropriate exception, e.g. TestApiException where TestApiException might have the ErrorCode or whatever you need on it.

ASP.NET Core return custom response when method is not allowed

I'm using ASP.NET Core for building REST service
I need to return custom response code if user tries to request an endpoint with unsupported method.
For example endpoint localhost/api/test supports GET method only, but user requests it with POST method. I need to return 404 response and custom body.
How to do this with ASP.NET Core?
UPD:
Possibly I formulated my question incorrectly.
I need ASP Core return 405 response code with custom JSON body in case if a method is not allowed.
This should be a standard behavior, but not implemented yet (according to this issue)
So I'm looking to workaround to return 405 response code nevertheless ASP Core does not support it out of box.
On a controller method level, probably this will guide you. You create a HttpResonseMessage, with your preferred status code and message. Note: if you want status like 302 then you also need to fill location header.
if (Request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
{
IHttpActionResult response;
HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.NotFound);
responseMsg.Content = new StringContent("Method doesn't support POST or whatever", System.Text.Encoding.UTF8, "text/html");
response = ResponseMessage(responseMsg);
return response;
}
Assuming you add a custom header in your controller method, to differencitate it from framework response.
In webapi.config register a CustomMessageHandler.
config.MessageHandlers.Add(new CustomMessageHandler());
//Define CustomMessageHandler like below and overide SendAsync
public class CustomMessageHandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var reasonInvalid = String.Empty;
var res= base.SendAsync(request, cancellationToken);
if (res.Result.StatusCode == HttpStatusCode.NotFound || res.Result.StatusCode == HttpStatusCode.MethodNotAllowed)
{
if(!res.Result.Headers.Contains("CustomHeaderforIntentional404"))
{
res.Result.StatusCode = HttpStatusCode.MethodNotAllowed;
res.Result.Content = new StringContent("Method doesn't support this method CUSTOM MESSAGE", System.Text.Encoding.UTF8, "text/html");
return res;
}
}
return res;
}
}
as per the official docs ...
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling
app.UseStatusCodePages();
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}"); // PathBase relative
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}"); // Absolute
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
// app.UseStatusCodePagesWithReExecute("/errors/{0}");
... something like that should work.

Get Deadlock when doing a HTTPRequest after catching WebserviceException

I get somehow a Deadlock/hanging after I purposely catch a WebServiceException to let my application continue. However, even though the application contiues. Doing a webservice hangs and it probably seems like it is still trying to do something from the previous call.
I tried using CancellationTokenSource but that did not seem to solve my problem.
RetryHandler:
public class RetryHandler : DelegatingHandler
{
private const int MaxRetries = 2;
public RetryHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
Exception lastException = null;
for (var i = 0; i < MaxRetries; i++)
{
try
{
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
lastException = ex;
}
if (IsSuccessful(response))
{
return response;
}
}
throw GetException(response, lastException);
}
Calling Post twice makes my program hang:
public async Task<T> Post<T>(
string path,
HttpContent content,
string username,
string token,
HttpMessageHandler handler)
{
var client = new HttpClient(new RetryHandler(handler));
var authString = GetAuthenticationString(username, token);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authString);
AddUsernameAndTokenToClientRequestHeader(client, username, token);
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", content.Headers.ContentType.MediaType);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
var result = await client.PostAsync(new Uri(path), content, _cancelHttpRequests.Token).ConfigureAwait(false);
var resultContent = result.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<T>(resultContent);
}
What is happening here is, although you are catching the exception, and supposedly let your application continue, the service itself continues it's work, async, so that even when you are trying to force the service to continue, it will still go on attempting to complete all of the desired action.
In your case: causing deadlock. Cancellation token won't help here, as your service running async and you already stopped it by catching the exception, so, you are basically doing nothing with this token.
Two ways to solve this:
Either, disconnect the service when you are getting the exception, this way forcing the service to shut.
Or try to work with your service in a sync way so that you can stop the service when ever needed, this way insuring it won't do any additional work when you stop it.

Custom DelegatingHandler never return after await base.SendAsync

I'm building an API consumer using HttpClient. Because the provider require the consumer to authenticate using Digest Authentication, so I need to write a custom DelegatingHandler like below:
public class DigestAuthDelegatingHandler : DelegatingHandler
{
public DigestAuthDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Unauthorized)//This line of code is never reached
{
//Generate the Digest Authorization header string and add to the request header,
//then try to resend the request to the API provider
}
return response;
}
}
I create a HttpClient and add my custom DelegatingHandler to the message handlers pineline
HttpClient httpClient = new HttpClient(new DigestAuthDelegatingHandler(new HttpClientHandler()));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.BaseAddress = new Uri("http://127.0.0.1/");
HttpResponseMessage response = httpClient.GetAsync("api/getTransactionInfo?TransactionNumber=1000).Result;
After doing that, it look like that my consumer runs forever. When I add a break point AFTER the code line await base.SendAsync above, I see that the code will never return, so I have no way to check if the response is get an 401 unauthorized to extract the Digest authorization header details. Nothing wrong at the API provider because I've successfully built another API consumer site using the traditional WebHttpRequest support Digest Authenticate and it works well.
IMPORTANT NOTE: if I switch to write consumer as a Console Application then it works well. So, I'm not sure but I think it's problem related to ASP.NET thread when running in asynchronous mode?
Is there anything wrong I'm doing?
I agree with Darrel - most probably, task is faulted and there is no result... you can use explicit continuation to inspect task state - for example,
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
// put the code to check the task state here...
});
On different note, I am not sure if you need to create a custom DelegatingHandler for authenticating ... try using HttpClientHandler with Credentials properties (or UseDefaultCredentials to pass default credentials of current user)
var httpClient = new HttpClient(new HttpClientHandler() {
PreAuthenticate = true,
Credentials = new NetworkCredentials(...
});
EDIT: Found the example with digest authentication being used with http client using credential cache - see this SO Q & A: HttpRequestMessage and Digest Authentication
This should solve your actual problem w.r.t. digest authentication without building your own handler.
My guess is the .Result is blocking the continuation in your handler. Try changing the .Result to a .ContinueWith
Having run into this same issue, this is what ended up working for me. The problem was that the endpoint threw a 500 Internal Server Error and thus blocked the thread.
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await base.SendAsync(request, cancellationToken)
.ContinueWith<HttpResponseMessage>(task =>
{
return task.Result;
});
}
Note that the return inside the anon function returns to the SendAsync function, and then we actually return the Result.

Categories

Resources