WebAPI ActionFilter Await ExecuteActionFilterAsync No Result - c#

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).

Related

Need to specify return type of a generic method that calls an API endpoint

I have a generic method that does a Post:
protected async Task<T> PostAsync<T>(string resource, object value = null)
{
T result = default(T);
var client = _httpClientFactory.CreateClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Post, _baseUri + resource);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
if (value != null)
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(value), Encoding.UTF8, "application/json");
using (var response = await client.SendAsync(requestMessage))
{
try
{
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<T>(responseContent);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
//if (!response.IsSuccessStatusCode)
//throw new HttpStatusCodeException((int)response.StatusCode, await response.Content.ReadAsStringAsync());
}
}
return result;
}
The API controller method that I am calling looks like this (I have omitted some implementation details). As you can see, it doesn't return any data:
[HttpPost("{contactId}/opt-out/test")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateTestAsync(Guid contactId, [FromQuery] string source)
{
try
{
return Ok();
}
catch (Exception exception)
{
}
}
Now using the generic Post method, I would like to know how to call the API endpoint. I have tried the following:
public async Task UpdateContactOptOutAsync(Guid contactId, string source)
{
return await base.PostAsync<Task>($"contacts/{contactId}/opt-out/test?source={source}");
}
But I get the following error:
Since 'BeamApiRepository.UpdateContactOptOutAsync(Guid, string)' is an async method that returns 'Task', a return keyword must not be followed by an object expression. Did you intend to return 'Task<T>'?
So how do I specify a return type to call an API endpoint that doesn't return any value?
it is better not to use it without T since it used to deserialize result. So I recomend to use it this way (PostAsync< Task > will work too)
public async Task UpdateContactOptOutAsync(Guid contactId, string source)
{
await base.PostAsync<Task<object>>($"contacts/{contactId}/opt-out/test?source={source}");
}
if your API dosn't return any data it will return null, otherwise it will still return the result as an object instance. But the code ignores it since you have void method. You can use var result = await base.Post.... and put some error code if it is not null, but it is out of the scope of this question.
Your Http client will likely not return a Task<Task> so why are you calling PostAsync<Task>(...)?
If you are using C# 7 or lower, you can simply call PostAsync<object>(...) which will always return null for the API you are calling.
If you are using C# 8 or higher, you can call PostAsync<object?>(...).
Better yet, you can add a generic method which simply returns Task (with no return type), so it for API POST calls with no response body. That way, it is clear that the response has nothing.
protected async Task PostAsync(string resource, object value = null)
{
....
}
Assuming you have your own implementation of Task (and the generic type in the call to PostAsync<Task> is not a mistake) then you need UpdateContactOptOutAsync to return System.Threading.Tasks.Task<Task>.
(just make sure to not mix-up the namespaces)
public async Task<Task> UpdateContactOptOutAsync(Guid contactId, string source)
Otherwise, if you haven't implemented your own Task class, then UpdateContactOptOutAsync should look something like this:
public async Task<MyResponse> UpdateContactOptOutAsync(Guid contactId, string source)
{
return await base.PostAsync<MyResponse>($"contacts/{contactId}/opt-out/test?source={source}");
}
Where MyResponse is the actual type you expect to deserialize from the response.
You may need to specify the return Task type, like this:
public async Task<IActionResult> UpdateContactOptOutAsync(Guid contactId, string source)
{
return await base.PostAsync<Task>($"contacts/{contactId}/opt-out/test?source={source}");
}

ASP.NET Web API async controller method and deadlock

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;
}

Task.Wait always returns false although task finished

I'm using HttpClient trying to execute a POST method in Web API controller. The controller method is synchronous. I'm doing so this way:
var response = owin.HttpClient.PostAsJsonAsync(uri, body);
After that I'm calling Wait:
var result = response.Wait(15000);
When running this code, I see the http finish executing, yet the result value is always false. What can I be missing?
Edit:
I now tried an async approach yet it didn't help me as well
public IHttpActionResult Add(Item item)
{
var result = _db.AddItem(item);
return Ok(result);
}
Test project:
TestServer _owinTestServer;
public async Task<HttpResponse message> Method1(string url, object body)
{
return await
_owinTestServer.HttpClient.PostAsJsonAsync(url,body);
}
public async Task<ItemPreview> Method2(object body);
{
return await Method1("..", body ).Result.Content.ReadAsAsync<ItemPreview>();
}
[TestMethod]
public void test1()
{
Item item = new(...);
Method2(item).Continue with(task => {// Never reach here }
}
What am I doing wrong?
When debugging I see that the controller method returns a good response, yet it never reaches back to my test
You are mixing async and blocking calls (ie .Result, .Wait()) which are leading to a deadlock.
This looks more like it is a blocking issue on the test client side.
You need to make the test async all the way through in this case if you want to await on results from the server.
Convert test method to async
[TestMethod]
public async Task test1() {
//Arrange
Item item = new Item(...);
//Act
var preview = await Method2(item);
//Assert
Assert.IsNotNull(preview);
}
And update the methods to not mix async and blocking calls.
Method1 does not need asyn/await if it is not using the task after the call so it can be removed and just have the method return the Task that can be awaited
TestServer _owinTestServer;
public Task<HttpResponse> Method1(string url, object body) {
return _owinTestServer.HttpClient.PostAsJsonAsync(url, body);
}
Method2 needs to await the response from Method1 and then get its content.
public async Task<ItemPreview> Method2(object body) {
var response = await Method1("..", body );
return await response.Content.ReadAsAsync<ItemPreview>();
}
It doesn't matter if the controller method is synchronous or not. That is purely a concern on the server code. Since the PostAsJsonAsync method is asynchronous, you need to await it:
var response = await owin.HttpClient.PostAsJsonAsync(uri, body);
Which will allow your code to wait for the response from the server.
I assume your initial code looked something like this...
var response = owin.HttpClient.PostAsJsonAsync(uri, body);
var result = response.Wait(15000);
var itemPreview = response.Result.Content.ReadAsAsync<ItemPreview>();
...
And yes, result will be false after waiting the 15 seconds. This is because you have setup the request, A.K.A response, but you haven't actually made the called and any response.Wait(n) will return false.
You just need to start the ReadAsAsync...
var response = owin.HttpClient.PostAsJsonAsync(uri, body);
var itemPreview = response.Result.Content.ReadAsAsync<ItemPreview>();
However, I think you will find you can skip the response.Wait(n) all together, as it will return true because the ReadAsAsync() will wait to return or fail. If you're looking to configure the request timeout, you can do that in the HttpClient and your ReadAsAsync will throw an AggregateException with an InnerException of TaskCanceledException.
On a side note, you don't need to use owin.HttpClient you can just instantiate a new HttpClient. I believe the owin object you are referring to is for self hosting your WebApi, I don't know if that matters. But let's say you are calling Add(Item item) on your WebApi and that db.AddItem(item) will return and ItemPreview object, your code could look like this:
[TestMethod]
public void test1()
{
Item item = new(...);
var uri = "..";
var client = new HttpClient();
var response = client.PostAsJsonAsync(uri, item);
var itemPreview = response.Result.Content.ReadAsAsync<ItemPreview>();
/* The things to happen once you have item preview */
}
The result may always be false because the _db.AddItem is returning false all the time. If not, I've made a change in your code ideally which should work for you
TestServer _owinTestServer;
public async Task<HttpResponse message> Method1(string url, object body)
{
return await _owinTestServer.HttpClient.PostAsJsonAsync(url,body);
}
public async Task<ItemPreview> Method2(object body);
{
return await Method1("..", body ).Result.Content.ReadAsAsync<ItemPreview>();
}
[TestMethod]
public void test1()
{
Item item = new(...);
await Method2(item).ContinueWith(task => {// Never reach here }
}
Since Method2 returns an Async Task, the ContinueWith will not wait for it to complete and hence it may required await on the method invocation.

Custom AuthorizeAttribute OnAuthorizationAsync Function

I'm trying to make my own custom System.Web.Http.AuthorizeAttribute. Everything was working until I needed to run some async function inside of the overridden OnAuthorizationfunction.
I found the OnAuthorizationAsync method and I use that, however still having issues running an async method. I need to ensure that a header attached to the HttpActionContext is in the db.
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
IEnumerable<string> values;
if (actionContext.Request.Headers.TryGetValues("Authentication", out values))
{
var token = new Token() { TokenString = values.First() };
if (await _tg.ValidateToken(token))
{
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
}
base.HandleUnauthorizedRequest(actionContext);
}
_tg.ValidateToken(token) is an async method that returns a bool. If I try to await it and make OnAuthorizationAsync an async method I then apparently cant return a Task:
Since OnAuthorizeAsync is an async method that returns 'Task', a return keyword must not be followed by an object expression.
Is there a way you can see out of this mess?
Because you use await, you need to add the async modifier to the method's declaration. Then change the return you have there to await. The compiler will do the rest - i.e. returning the Task for you.
Based on #sellotape's answer:
public async override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
IEnumerable<string> values;
if (actionContext.Request.Headers.TryGetValues("Authentication", out values))
{
var token = new Token() { TokenString = values.First() };
if (await _tg.ValidateToken(token))
{
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
}
await base.HandleUnauthorizedRequest(actionContext);
}

How do I ensure DelegatingHandlers throw exceptions on timeouts?

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);
}

Categories

Resources