While using a custom message handler, I keep encountering the following error on the API server side:
API Controller:-
[RoutePrefix("errors")]
public class ErrorController : ApiController
{
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetClientEmailId(int id)
{
return this.Ok();
}
}
Custom Message Handler:-
public class ApiMessageHandler: DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request, CancellationToken
cancellationToken)
{
var logger = LogManager.GetLogger(this.GetType().FullName);
if (logger.IsDebugEnabled)
{
var requestMessage = await
request.Content.ReadAsByteArrayAsync();
var resTask = base.SendAsync(request,
cancellationToken).ContinueWith(
t =>
{
if (t.Exception != null)
{
throw t.Exception;
}
return t.Result;
},
cancellationToken);
byte[] responseMessage;
if (resTask.Result.IsSuccessStatusCode)
{
responseMessage = resTask.Result.Content != null
? await resTask.Result.Content.ReadAsByteArrayAsync()
: Encoding.UTF8.GetBytes(resTask.Result.ReasonPhrase);
}
else
{
responseMessage = Encoding.UTF8.GetBytes(resTask.Result.ReasonPhrase);
}
await this.Log(request, requestMessage, resTask.Result,
responseMessage, logger);
return resTask.Result;
}
var response = await base.SendAsync(request, cancellationToken);
return response;
}
when I called action method http://localhos:4200/errors/adf(pass string parameter instead of integer) then I got System.FormatException in Base.SendAsync method.
The exception was thrown: 'System.FormatException' in mscorlib.dll
Additional information: Input string was not in a correct format.
But this exception is not handled by GlobalException Handler.
It looks like a base.SendAsync method has swallowed this exception.
How we can handle this exception and rethrow so GlobalException handler can handle this exception with proper message.
Thank you in advance.
The problem is that you are swallowing the exception yourself. You are not awaiting the task:
if (resTask.Result.IsSuccessStatusCode)
This is a bad practice in 99% of cases, since you are synchronously blocking the thread instead of asynchronously waiting for the result. Your code should be:
var result = await base.SendAsync(request, cancellationToken));
byte[] responseMessage;
if (result.IsSuccessStatusCode)
Since your method does not have a try-catch surrounding it, any exceptions will be thrown to the global handler as usual.
Related
I have a workflow like this:
call an API method
if any exception is thrown but timeout, the program logs that exception and throws it.
if timeout is thrown, the program has to call another API method.
after calling another API method, if everything goes true, the program returns a result.
otherwise the program throws an exception and log it.
both API methods have the same type of result.
I want to implement this with polly policies.
This is my sample code:
var retryPolicy = Policy
.Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.RequestTimeout)
.RetryAsync(1, async (exception, retryCount) =>
await CallAnotherAPI());
var fallbackPolicy = Policy<HttpResponseMessage>
.Handle<Exception>()
.FallbackAsync((r, c, ct) => throw r.Exception,
async (r, c) =>
{
Log(r.Message);
});
var result = await fallbackPolicy
.WrapAsync(retryPolicy)
.ExecuteAsync(async () =>
{
await CallAPI();
});
but it doesn't work and all the time fallbackPolicy is executed. How can I write the code that if retryPolicy goes true, fallbackPolicy won't be executed?
If I understand your workflow correctly then you don't need the retry policy at all. The Fallback policy is enough for this.
So, let suppose that the CallApi and CallAnotherApi are implemented like this:
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallAPI()
{
return await client.GetAsync("http://httpstat.us//408");
}
public static async Task<HttpResponseMessage> CallAnotherAPI()
{
var response = await client.GetAsync("http://httpstat.us//500");
response.EnsureSuccessStatusCode();
return response;
}
I've used httpstatus.us to simulate certain http status codes
CallApi will always fail with Request Timeout
CallAnotherApi will always throw HttpRequestException because of the Ensure method call
Now let's see the policy definition and usage:
public static async Task Main(string[] args)
{
var fallbackPolicy = Policy<HttpResponseMessage>
.HandleResult(msg => msg.StatusCode == HttpStatusCode.RequestTimeout)
.FallbackAsync(async (_) => await CallAnotherAPI());
HttpResponseMessage result;
try
{
result = await fallbackPolicy
.ExecuteAsync(async () =>
{
return await CallAPI();
});
}
catch (Exception ex) {
Console.WriteLine(ex.Message); //TODO: replace with logging
throw;
}
Console.WriteLine(result.StatusCode);
}
The fallback policy should be triggered only if the response's status code was 408
ExecuteAsync will throw exception if either the CallApi or CallAnotherApi throws
Let's see the different scenarios one-by-one
CallApi succeeds
public static async Task<HttpResponseMessage> CallAPI()
{
return await client.GetAsync("http://httpstat.us//200");
}
Output
OK
CallApi fails
public static async Task<HttpResponseMessage> CallAPI()
{
var response = await client.GetAsync("http://httpstat.us//500");
response.EnsureSuccessStatusCode();
return response;
}
Output
Response status code does not indicate success: 500 (Internal Server Error).
Then the application crashes because of throw;
CallApi timeouts and CallAnotherApi succeeds
public static async Task<HttpResponseMessage> CallAPI()
{
return await client.GetAsync("http://httpstat.us//408");
}
public static async Task<HttpResponseMessage> CallAnotherAPI()
{
var response = await client.GetAsync("http://httpstat.us//200");
response.EnsureSuccessStatusCode();
return response;
}
Output
OK
CallApi timeouts and CallAnotherApi fails
public static async Task<HttpResponseMessage> CallAPI()
{
return await client.GetAsync("http://httpstat.us//408");
}
public static async Task<HttpResponseMessage> CallAnotherAPI()
{
var response = await client.GetAsync("http://httpstat.us//500");
response.EnsureSuccessStatusCode();
return response;
}
Output
Response status code does not indicate success: 500 (Internal Server Error).
Then the application crashes because of throw;
The polly fallback is enough in this case. like this....
var fallbackPolicy = Policy<HttpResponseMessage>
.Handle<Exception>()
.FallbackAsync((r, c, ct) => throw r.Exception,
async (r, c) =>
{
Log(r.Message);
});
Please help me to understand why this code cause a deadlock?
I have an asp.net web api application and I tried to make some controller method asynchronous.
[HttpPost]
[Authentication]
public async Task<SomeDTO> PostSomething([FromBody] SomeDTO someDTO)
{
return await _service.DoSomething(someDTO);
}
this is how looks the called service method:
public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
{
...
var someTask = Task.Run(() =>
{
var entity = new SomeEntity(someDTO);
return _repository.Create(entity);
});
...
var result = await someTask;
...
}
And there is some globalhandler, that prints a response to a console.
public class AppGlobalHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var resp = base.SendAsync(request, cancellationToken);
Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{resp?.ConfigureAwait(false).GetAwaiter().GetResult()?.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()}");
return resp;
}
}
Looks like ConfigureAwait(false).GetAwaiter().GetResult()
blocks the caller thread, but I supposed that ConfigureAwait(false) should avoid this, isn't it?
ConfigureAwait(false) would not help you here because it must be all the way down in the call stack (see more here) not at place where you wait synchronously, i.e. it depends rather on the implementation of base.SendAsync. If it acquired a lock on current thread it's too late to do something about it. It is also not recommended in ASP.net pipeline to continue responding on other thread after all (see discussion here and post here).
Finally it is always a highly risky idea to wait synchronously in async context.
If you need to read content, why not doing it like that:
public class AppGlobalHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var resp = await base.SendAsync(request, cancellationToken);
var content = resp?.Content != null
? (await resp.Content.ReadAsStringAsync())
: string.Empty;
Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{content}");
return resp;
}
}
I think you overlook async keyword in Task.Run() method.
public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
{
var someTask = Task.Run( async () => //simply add this for async run
{
var entity = new SomeEntity(someDTO);
return _repository.Create(entity);
});
var result = await someTask;
}
I'm using HttpClient's PostAnsyc method to synchronously call a REST API from service code invoked by my MVC application, but I'm losing exceptions in a DelegatingHandler.
The usage is synchronous. I am aware of the async path and it does not fit my use case.
Here are some variant's I've tried that didn't throw exceptions on timeout:
//controller action
[HttpPost]
public JsonResult Foo(int id)
{
try
{
var result = _businessService.Foo(id);
return Json(result, JsonRequestBehavior.DenyGet);
}
catch(Exception exception)
{
return Json(exception, JsonRequestBehavior.DenyGet);
}
}
//infrastructure code deep in my application
public HttpResponseMessage Post(Uri uri, StringContent content)
{
return _httpClient.PostAsync(uri, content).Result;
}
//DelegatingHandler code
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
base.SendAsync(request, cancellationToken)
.ContinueWith( t =>
{
if (t.IsFaulted)
{
if(t.Exception != null)
{
taskCompletionSource.TrySetException(t.Exception);
}
}
else if (t.IsCanceled)
{
taskCompletionSource.TrySetCanceled();
}
else
{
try
{
LogResponse(t.Result);
taskCompletionSource.SetResult(t.Result);
}
catch (Exception ex)
{
taskCompletionSource.TrySetException(ex);
}
}
}, cancellationToken);
return taskCompletionSource.Task;
}
How do I ensure that my DelegatingHandlers do not swallow exceptions during a timeout?
public async Task<HttpResponseMessage> PostAsync(Uri uri, StringContent content)
{
var cancellation = new CancellationTokenSource();
var task = _httpClient.PostAsync(uri, content, cancellation.Token);
var timeout = Task.Delay(5000);
await Task.WhenAny(task, timeout);
if(timeout.IsCompleted)
{
cancellation.Cancel();
throw new TimeoutException();
}
else
return await task;
}
This example would provide a timeout of 5 seconds before try to cancel the POST operation and throw a timeout exception.
Unfortunately the HttpClient has no synchronous methods, so whatever you do another thread pool thread is taking care of the request and you have to wait for it.
An alternative is to use the WebRequest, but it is less fancy and you have to serialize your payload yourself (which is not big deal with the NewtonSoft Json library)
It's pretty trivial to create a timeout using a CancellationTokenSource
public async Task<HttpResponseMessage> PostAsync(Uri uri, StringContent content)
{
var cancellation = new CancellationTokenSource(5000); // Cancel after 5 seconds.
return await _httpClient.PostAsync(uri, content, cancellation.Token);
}
I try to create a filter to modify the content. For some reason the
var result = await actionContext.Request.Content.ReadAsStringAsync(); does not wait and returns me empty values. I'm sure that there is data. Checked directly inside controller and header. Is there maybe a workaround. Can be blocking, too (HttpContent seems just to have async methods).
public class AsyncAttribute : FilterAttribute, IActionFilter
{
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
await InternalActionExecuting(actionContext, cancellationToken);
if (actionContext.Response != null)
{
return actionContext.Response;
}
HttpActionExecutedContext executedContext;
try
{
var response = await continuation();
executedContext = new HttpActionExecutedContext(actionContext, null)
{
Response = response
};
}
catch (Exception exception)
{
executedContext = new HttpActionExecutedContext(actionContext, exception);
}
await InternalActionExecuted(executedContext, cancellationToken);
return executedContext.Response;
}
public virtual async Task InternalActionExecuting(HttpActionContext actionContext, CancellationToken cancellationToken)
{
//////////////////////////////////////////////////////////////////////////
var result = await actionContext.Request.Content.ReadAsStringAsync();// <------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
}
public virtual async Task InternalActionExecuted(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
}
}
You should not be accessing the body in an action filter, if you have any thing else accessing the body in parameter binding it might be already gone.
Why are you calling actionExecuted inside the action executing overload? The pattern is one gets called before the action is run and one after.
For some reason it also looks like you are trying to implement the filter pipeline itself inside this method.
If you want to bind data from the body your recommended alternative is to use a formatter. Implement a MediaTypeFormatter and read what you need into a string. See one example
My wild bet here is that something else already read the body stream (and technically you are done at the point) and you can try and rewind it first (it will only work if you are in buffered mode).
How comes that a custom ExceptionHandler is never called and instead a standard response (not the one I want) is returned?
Registered like this
config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
and implemented like this
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ExceptionResponse
{
statusCode = context.Exception is SecurityException ? HttpStatusCode.Unauthorized : HttpStatusCode.InternalServerError,
message = "An internal exception occurred. We'll take care of it.",
request = context.Request
};
}
}
public class ExceptionResponse : IHttpActionResult
{
public HttpStatusCode statusCode { get; set; }
public string message { get; set; }
public HttpRequestMessage request { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(statusCode);
response.RequestMessage = request;
response.Content = new StringContent(message);
return Task.FromResult(response);
}
}
and thrown like this (test)
throw new NullReferenceException("testerror");
in a controller or in a repository.
UPDATE
I do not have another ExceptionFilter.
I found a trigger for this behavior:
Given URL
GET http://localhost:XXXXX/template/lock/someId
sending this header, my ExceptionHandler works
Host: localhost:XXXXX
sending this header, it doesn't work and the built-in handler returns the error instead
Host: localhost:XXXXX
Origin: http://localhost:YYYY
This might be an issue with CORS requests (I use the WebAPI CORS package globally with wildcards) or eventually my ELMAH logger. It also happens when hosted on Azure (Websites), though the built-in error handler is different.
Any idea how to fix this?
Turns out the default only handles outermost exceptions, not exceptions in repository classes. So below has to be overridden as well:
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return context.ExceptionContext.IsOutermostCatchBlock;
}
UPDATE 1
WebAPI v2 does not use IsOutermostCatchBlock anymore. Anyway nothing changes in my implementation, since the new code in ShouldHandle still prevents my Error Handler. So I'm using this and my Error Handler gets called once. I catch errors in Controllers and Repositories this way.
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
UPDATE 2
Since this question got so much attention, please be aware that the current solution is the one linked by #JustAMartin in the comments below.
The real culprit here is CorsMessageHandler inserted by EnableCors method in message processing pipline. The catch block intercept any exception and convert into a response before it can reach the HTTPServer try-catch block and ExceptionHandler logic can be invoked
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
HttpResponseMessage result;
if (corsRequestContext != null)
{
try
{
if (corsRequestContext.IsPreflight)
{
result = await this.HandleCorsPreflightRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
result = await this.HandleCorsRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
catch (Exception exception)
{
result = CorsMessageHandler.HandleException(request, exception);
return result;
}
}
result = await this.<>n__FabricatedMethod3(request, cancellationToken);
return result;
}