HttpClient Await PostAsync not completed - asp.net

I have an asmx Web Service and I am using async Task. My problem is whenever I reached on the PostAsync statement it will just end there and fire a result to the browser with an empty result. Which is not I want. I tried passing the httpclient as a parameter to my service class thinking it may solved the issue.
I tried putting ConfigureAwait(false) and it gives a result however I don't want this because I need to return the value to the user. If I use ConfigurAwait(false) it will return an empty result to the browser even if it it still not completed. Am I doing this right? Thanks
in my webmethod
public class WebService1 : WebService
{
HttpClient Client = new HttpClient();
XDocument doc = new XDocument();
[WebMethod]
private async Task<String> Sample1(string a, int b)
{
myServiceClass _ms = new myServiceClass(Client);
var message = await _ms.GetResponseMessageAsync(a,b);
doc = await _ms.ReadResponseAsync(message); // It will not reach here if I don't use ConfigureAwait(false)
return JsonConvert.SerializeObject(doc);
}
}
myServiceClass.cs
public class myServiceClass
{
HttpClient _client;
public myServiceClass(HttpClient client)
{
_client = client;
}
public async Task<HttpResponseMessage> GetResponseMessageAsync(string a, int b)
{
HttpResponseMessage message;
httpcontent = (a,encoding.UTF8,"text/xml"); //This is just a sample content
message = await _client.PostAsync(UrlString, httpcontent); //<= here it stops and return empty result if there is no ConfigureAwait(false).
if (!message.IsSuccessStatusCode)
{
throw new HttpRequestException($"Cannot connect to api: {message.StatusCode} , {message.ReasonPhrase}");
}
return message; // It will not reach here if I don't use ConfigureAwait(false)
}
}

Related

Different threads using same instance of DbContext, called from within DelegatingHandler

I have .Net Core 3.1 application that is using EF Core 3.1.9. During a specific process I am getting the following error:
A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913
I am using Dependency Injection for the DbContext and have gone through all the flows to make sure everything is properly and immediately await'ed.
The error occurs within LtiUserRepository.cs which will be shown below.
That process starts with an external http call using an HttpClient that has a custom MessageHandler, registered in Startup.cs:
services.AddHttpClient<MyRepository>("MyCustomUserClient", client =>
{
var canvasUrl = Configuration.GetSection("Urls:Removed").Value ?? "https://example.com/";
client.BaseAddress = new System.Uri(removed);
}).AddHttpMessageHandler<LtiUserApiAuthenticationHttpClientHandler>();
The code that initiates the HTTP Call is:
public async Task<PlatformQuizSubmissions> GetUserQuiz(string courseId, string quizId)
{
var path = $"api/v1/courses/{courseId}/quizzes/{quizId}/submission";
var response = await _myCustomUserClient.GetAsync(path);
// some stuff
var responseContent = await response.Content.ReadAsStringAsync();
// Some other stuff
}
The purpose of the custom MessageHandler is to check for a header, get some data, and append a query parameter to each request
public sealed class LtiUserApiAuthenticationHttpClientHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
private readonly ILtiUserService _userService;
public LtiUserApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor, ILtiUserService ltiUserService)
{
_accessor = accessor;
_userService = ltiUserService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var obo = _accessor.HttpContext.Request.Headers["QT-OBO"];
// THIS IS THE PART THAT QUERIES THE DATABASE
var user = await _userService.Get(new Guid(obo));
var uriBuilder = new UriBuilder(request.RequestUri);
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"as_user_id={user.PlatformUserId}";
}
else
{
uriBuilder.Query = $"{uriBuilder.Query}&as_user_id={user.PlatformUserId}";
}
request.RequestUri = uriBuilder.Uri;
return await base.SendAsync(request, cancellationToken);
}
}
You can see above that the MessageHandler calls _userservice.Get, which is this:
public async Task<LtiUser> Get(Guid guid)
{
return await _ltiUserRepository.Get(guid);
}
That simply returns from the repository, which is this:
public class LtiUserRepository : ILtiUserRepository
{
private readonly SqlDbContext _db;
private readonly IMapper _mapper;
private readonly ILogger<LtiUserRepository> _logger;
public LtiUserRepository(SqlDbContext sqlDbContext, IMapper mapper, ILoggerFactory logger)
{
_db = sqlDbContext;
_mapper = mapper;
_logger = logger != null ? logger.CreateLogger<LtiUserRepository>() : throw new ArgumentNullException(nameof(logger));
}
public async Task<LtiUser> Get(Guid guid)
{
try
{
return await _db.LtiUsers
.AsNoTracking()
.Where(l => l.UUID == guid)
.ProjectTo<LtiUser>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync();
}
catch (Exception ex)
{
// This is where the error is caught.
_logger.LogCritical($"Could not get LtiUser via (UUID) {guid} : {ex.Message}");
return null;
}
}
}
The database is registered in Startup.cs with:
protected virtual void ConfigureDatabaseServices(IServiceCollection services)
{
services.AddDbContext<SqlDbContext>(
o => o.UseSqlServer(Configuration.GetConnectionString("DbConnectionString")),
ServiceLifetime.Transient);
}
When I hit this endpoint using ApacheBench with 20 requests, concurrency of 2 I get this error anywhere from 2 to 10 times. However, looking at the following snippet from the MessageHandler (LtiUserApiAuthenticationHttpClientHandler) again:
var user = await _userService.Get(new Guid(obo));
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"as_user_id={user.PlatformUserId}";
}
else
{
uriBuilder.Query = $"{uriBuilder.Query}&as_user_id={user.PlatformUserId}";
}
If I replace user.PlatformUserId with a hardcoded, known value, (and comment out the call to _userService.Get) I can use AB with 1000 requests and a concurrency of 20 and have 0 occurrences of the issue. That leads me to believe I have it narrowed down to the offending flow, but am not sure of the correct way to do this.

httpclient call is invoked after await keyword in dotnet core

I would like to do some operation which doesn't depend on API response and at the same time I want API to finish its process.
But in my case, API doesn't receive request when postasync is executed.
Instead, Web api receive request after await weatherForeCastdata.
I noticed strange behavior today
when I executed endpoint for first time(both the apis), webapi received request after postasync method. From second time, api receives request after executing await weatherForeCastdata.
I launched applictaion from vs code
browser : chrome
Can anyone help me ?
public async Task<IEnumerable<WeatherForecast>> Get()
{
var rng = new Random();
var weatherForeCastdata = new HttpClientCall<WeatherForecast>(_configuration).PostRequest(_configuration["Services:Payperiod"],new WeatherForecast());
Console.WriteLine("apiinvoked");
var data = await weatherForeCastdata;
//var data1 = await data.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<WeatherForecast>>(data);
}
public class HttpClientCall<T> where T : class
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
private readonly IConfiguration _configuration;
internal HttpClientCall(IConfiguration configuration)
{
httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; //Is valid
}
return true;
};
_configuration = configuration;
}
public async Task<string> PostRequest(string apiUrl, T postObject)
{
using (var client = new HttpClient(httpClientHandler))
{
client.DefaultRequestHeaders.Add("ClientId", _configuration["header"]);
Console.WriteLine(apiUrl);
var response = client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter());
var response1=await response;
return await response1.Content.ReadAsStringAsync();
}
}
}

Mocking HttpMessageHandler with moq - How do I get the contents of the request?

Is there a way to get the contents of the http request before deciding what kind of response I want to send back for the test? Multiple tests will use this class and each test will have multiple http requests.
This code does not compile because the lambda is not async and there is an await in it. I'm new to async-await, so I'm not sure how to resolve this. I briefly considered having multiple TestHttpClientFactories, but that would mean duplicated code, so decided against it, if possible.
Any help is appreciated.
public class TestHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
messageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
HttpResponseMessage response = new HttpResponseMessage();
var requestMessageContent = await request.Content.ReadAsStringAsync();
// decide what to put in the response after looking at the contents of the request
return response;
})
.Verifiable();
var httpClient = new HttpClient(messageHandlerMock.Object);
return httpClient;
}
}
To take advantage of the async delegate use the Returns method instead
public class TestHttpClientFactory : IHttpClientFactory {
public HttpClient CreateClient(string name) {
var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
messageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns(async (HttpRequestMessage request, CancellationToken token) => {
string requestMessageContent = await request.Content.ReadAsStringAsync();
HttpResponseMessage response = new HttpResponseMessage();
//...decide what to put in the response after looking at the contents of the request
return response;
})
.Verifiable();
var httpClient = new HttpClient(messageHandlerMock.Object);
return httpClient;
}
}
Or consider creating your own handler that exposes a delegate to handle the desired behavior.
For example
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
And used in the factory like this
public class TestHttpClientFactory : IHttpClientFactory {
public HttpClient CreateClient(string name) {
var messageHandlerMock = new DelegatingHandlerStub(async (HttpRequestMessage request, CancellationToken token) => {
string requestMessageContent = await request.Content.ReadAsStringAsync();
HttpResponseMessage response = new HttpResponseMessage();
//...decide what to put in the response after looking at the contents of the request
return response;
});
var httpClient = new HttpClient(messageHandlerMock);
return httpClient;
}
}

Asp.net web api controller long querystring returns BadRequest

I have an Asp.net web api with one method that receive a crypted string with variable length, my problem is it string some times could take a length near 5000.
When that method receive a string under 1930 length it process everything ok, but for lengths over 1929 returns
404 Operation returned an invalid status code 'BadRequest'
I changed method GET to POST but problem persists.
I changed filter options on server to very very long limits, but problem persists
This is my controller:
public class LicencingManagerController : ApiController
{
[HttpPost]
[Route(nameof(SaveCustomer2))]
public string SaveCustomer2(string custInfo)
{
return $"ItsOk = {custInfo}";
}
}
Actually I made calls to controller using autogenerated c# client by AutoRest 2.0.483.
I tried hand building PostAsync Request using HttpClient but nothing works.
string custInfo = "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";
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("custInfo", custInfo)
};
var content = new FormUrlEncodedContent(pairs);
var client = new HttpClient();
// call sync
var response = client.PostAsync($"{Modelos.Shared.HttpsServicesReference.Licencing}/LicencingAPI/SaveCustomer2", content).Result;
if (response.IsSuccessStatusCode)
{
}
What is the right way to process this controller call?
There is another approach to perform this ?
Create model to hold data
public class Customer {
public string CustumerInfo { get; set; }
}
Update API to expect model in body of request
public class LicencingManagerController : ApiController {
[HttpPost]
[Route(nameof(SaveCustomer2))]
public IHttpActionResult SaveCustomer2([FromBody]Customer model) {
if(!ModelState.IsValid)
return BadRequest(ModelState);
var custInfo = model.CustumerInfo;
return Ok($"ItsOk = {custInfo}");
}
}
Post content to API in body of the request like you did before
string custInfo = "H4sIAAAAAAAEAA...";
string url = $"{Modelos.Shared.HttpsServicesReference.Licencing}/LicencingAPI/SaveCustomer2";
var pairs = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("CustumerInfo", custInfo)
};
var content = new FormUrlEncodedContent(pairs);
var client = new HttpClient();
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode) {
}

Stuck on AsyncPost in Web API

I am really stuck on this as I have searched far and wide for a solution for an asyncpost using Web API but couldnt find anything. Essentially, its got to make a POST call using HttpClient to the relevant controller class AddMenuItem using Web API but it just doesn't work. It simply throws an error of a 404 Error and cannot see the controller method. Any reasons why and solution for this would be very helpful!
// Async Post Call
public static async void asyncPost()
{
using (var client = new HttpClient())
{
try
{
var values = new System.Collections.Generic.Dictionary<string, string>();
values.Add("ItemName", "Pepperoni Pizza");
var content = new FormUrlEncodedContent(values);
string baseAddress = "http://localhost:9000/";
HttpResponseMessage response3 = await client.PostAsync(baseAddress + "api/values/AddMenuItem", content);
if (response3.StatusCode == System.Net.HttpStatusCode.OK)
{
// Do something...
}
}
catch (OperationCanceledException) { }
}
}
// POST api/values
public void AddMenuItem([FromBody]string itemName)
{
//Should go in here when PostAync is called
}
Don't use async void; use async Task instead.
public static async Task PostAsync()
Then your controller can call it with await:
public async Task AddMenuItem([FromBody]string itemName)
{
await PostAsync(..);
}

Resources