After first request HttpClientHandler is disposed - c#

I want to send two request from my ViewModel (first - GET and then - POST) using HttpClient. GET request completes without any error. But if then I send POST request I got exception:
{System.ObjectDisposedException: The object has been closed. (Exception from HRESULT: 0x80000013)} System.Exception {System.ObjectDisposedException}
Or if I ran POST request before GET - POST completes ok and GET - fails with the same exception.
I'm using one HttpClientHandler for both requests (becouse I store Cookies in that HttpClientHandler)
public async Task<CategoryGroupModel> GetCategoryGroup(int categoryGroupId)
{
var handler = new HttpClientHandler {CookieContainer = App.Cookies};
using (var client = new MmcHttpClient(handler))
{
// HTTP GET
HttpResponseMessage response = await client.GetAsync("api/categorygroups/" + categoryGroupId);
if (response.IsSuccessStatusCode)
{
var resultAsString = await response.Content.ReadAsStringAsync();
var jsonResult = JObject.Parse(resultAsString);
var wsResponse = jsonResult.ToObject<WebServiceResponse<CategoryGroupModel>>();
if (wsResponse.Status == HttpStatusCode.OK)
{
return wsResponse.Result;
}
else
{
throw new Exception();
}
}
else
{
throw new Exception();
}
}
}
public async Task<CategoryGroupModel> CreateCategoryGroup(CategoryGroupModel categoryGroup)
{
var handler = new HttpClientHandler {CookieContainer = App.Cookies};
using (var client = new MmcHttpClient(handler))
{
var response = await client.PostAsJsonAsync("api/categorygroups", categoryGroup);
if (response.IsSuccessStatusCode)
{
var resultAsString = await response.Content.ReadAsStringAsync();
var jsonResult = JObject.Parse(resultAsString);
var wsResponse = jsonResult.ToObject<WebServiceResponse<CategoryGroupModel>>();
if (wsResponse.Status == HttpStatusCode.OK)
{
return wsResponse.Result;
}
else
{
throw new Exception();
}
}
else
{
throw new Exception();
}
}
}
MmcHttpClient:
public class MmcHttpClient : HttpClient
{
public MmcHttpClient(HttpClientHandler handler) : base(App.Handler)
{
BaseAddress = new Uri("http://localhost:65066/");
DefaultRequestHeaders.Accept.Clear();
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
StackTrace:
System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Http.HttpClientHandler'.
at System.Net.Http.HttpClientHandler.CheckDisposed()
at System.Net.Http.HttpClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.PostAsync(String requestUri, HttpContent content, CancellationToken cancellationToken)
at System.Net.Http.HttpClientExtensions.PostAsync[T](HttpClient client, String requestUri, T value, MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType, CancellationToken cancellationToken)
at System.Net.Http.HttpClientExtensions.PostAsync[T](HttpClient client, String requestUri, T value, MediaTypeFormatter formatter, CancellationToken cancellationToken)
at System.Net.Http.HttpClientExtensions.PostAsJsonAsync[T](HttpClient client, String requestUri, T value, CancellationToken cancellationToken)
at System.Net.Http.HttpClientExtensions.PostAsJsonAsync[T](HttpClient client, String requestUri, T value)
at MMCClient.Repositories.CategoryGroupRepository.<CreateCategoryGroup>d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MMCClient.ViewModels.CategoryGroupViewModel.<Create>d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__3(Object state)
at System.Threading.WinRTSynchronizationContext.Invoker.InvokeCore()} System.Exception {System.ObjectDisposedException}

It was my fault. After adding second param (disposeHandler) to HttpClient constructor all works good:
public class MmcHttpClient : HttpClient
{
public MmcHttpClient(HttpClientHandler handler, bool disposeHandler) :
base(App.Handler, disposeHandler)
{
BaseAddress = new Uri("http://localhost:65066/");
DefaultRequestHeaders.Accept.Clear();
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
You can read about it here

Related

"Cannot access a disposed object" error - stemming from HttpResponseMessage?

C# 10/.NET 6
I am struggling to identify what is causing this error. From this code:
internal class Call
{
internal int RetryCount { get; set; } = 1;
internal HttpClient Client { get; init; }
internal HttpRequestMessage Request { get; private set; }
internal Call(HttpClient client, HttpRequestMessage request)
{
...
}
internal async Task<T> SendRequestAsync<T>()
{
JsonSerializerOptions serializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
while (true)
{
if (this.RetryCount > Setup.Config.RetryMax)
throw new TimeoutException($"Maximum number of retries reached.");
await LogRequest(this.Client, this.Request);
Thread.Sleep(Setup.Config.ThrottleBuffer);
try
{
HttpResponseMessage? response = new();
response = await this.Client.SendAsync(this.Request);
await LogResponse(response);
_ = response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>(serializerOptions) ?? throw new Exception("Result object was null after JSON deserialization.");
}
catch (HttpRequestException ex)
{
if (ex.StatusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.InsufficientStorage or HttpStatusCode.ServiceUnavailable)
{
Log.Error($"HTTP request failed. Retrying in {(Setup.Config.RetryDelay / 1000) * this.RetryCount} seconds...");
Thread.Sleep(Setup.Config.RetryDelay * this.RetryCount);
this.RetryCount++;
continue;
}
else if (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
{
Log.Error($"HTTP request failed. Permissions error: unable to retry.");
throw;
}
else
{
throw;
}
}
}
}
}
When called repeatedly, it runs 15 times and then errors on the 16th run. I get this error:
Object name: System.Net.Http.HttpClient.
at System.Net.Http.HttpClient.CheckDisposed()
at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request)
at [redacted].Call.SendRequestAsync[T]() in C:\Users\[redacted]\Call.cs:line 97
Line 97 points to this line: response = await this.Client.SendAsync(this.Request)
I found this thread where a user had a similar problem with StringContent being automatically disposed by HttpClient. However, I have HttpResponseMessage being created each time in the loop.
What am I missing?
The error was pointing to the HttpClient being disposed, not HttpResponseMessage. The consumer was disposing the client prematurely.

Updating an activity in BotFramework after a timeout

I have a requirement that a Bot message posted to MS Teams should expire after a timeout period. To achieve this I am calling UpdateAsyncActivity() on a separate thread after a timeout, however this fails with a NullReferenceException:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Bot.Builder.BotFrameworkAdapter.UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
at Microsoft.Bot.Builder.TurnContext.<>c__DisplayClass31_0.<<UpdateActivityAsync>g__ActuallyUpdateStuff|0>d.MoveNext()--- End of stack trace from previous location where exception was thrown ---
at Microsoft.Bot.Builder.TurnContext.UpdateActivityInternalAsync(Activity activity, IEnumerable`1 updateHandlers, Func`1 callAtBottom, CancellationToken cancellationToken)
at Microsoft.Bot.Builder.TurnContext.UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken)
at NotifyController.ClearCard(ITurnContext turnContext, Activity timeoutActivity, Int32 timeoutInMinutes) in NotifyController.cs:line 48
The code looks something like this:
[Route("api/notify")]
[ApiController]
public class NotifyController : ControllerBase
{
private IBotFrameworkHttpAdapter Adapter;
private readonly string _appId;
private readonly IConversationStorage _conversationStorage;
public NotifyController(IBotFrameworkHttpAdapter adapter, IConfiguration configuration, IConversationStorage conversationStorage)
{
Adapter = adapter;
_appId = configuration["MicrosoftAppId"] ?? string.Empty;
_conversationStorage = conversationStorage;
}
[HttpPost]
public async Task<IActionResult> PostForm([FromBody] RestData restData)
{
ConversationReference conversationReference =
_conversationStorage.GetConversationFromStorage(restData.ConversationId);
await ((BotAdapter)Adapter).ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
await BotCallback(restData.AdaptiveCard, context, token), default(CancellationToken));
return new ContentResult();
}
private async Task BotCallback(string adaptiveCard, ITurnContext turnContext, CancellationToken cancellationToken)
{
var activity = MessageFactory.Attachment(adaptiveCard.ToAttachment());
ResourceResponse response = await turnContext.SendActivityAsync(activity);
var timeoutActivity = turnContext.Activity.CreateReply();
timeoutActivity.Attachments.Add(AdaptiveCardExamples.TimeoutTryLater.ToAttachment());
timeoutActivity.Id = response.Id;
// SUCCESS
//Thread.Sleep(10000);
//await turnContext.UpdateActivityAsync(timeoutActivity);
// FAIL
Thread CardClearThread = new Thread(() => ClearCard(turnContext, timeoutActivity));
CardClearThread.Start();
}
private async void ClearCard(ITurnContext turnContext, Activity timeoutActivity)
{
Thread.Sleep(10000);
await turnContext.UpdateActivityAsync(timeoutActivity);
}
}
I want the timeout to happen on a separate thread so that PostForm() returns a response as soon as the original message is sent.
I presume what is happening is that some aspect of turnContext is being disposed when the main thread returns, causing the sleeping thread to fail when it wakes up.
Is there a solution/alternative approach to this?
With in some mins , Bot has to response to the team otherwise this issue get popup best solution for this to implement proactive message concept
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp

Error with IHttpActionResult in web api actions

I am facing method not found error in web server, but locally in visual studio it works:
[HttpGet]
[Route("api/checkhealth")]
public async Task<IHttpActionResult> CheckHealth()
{
var message = "checkhealth method was invoked";
return new TextResult(message, Request);
}
Then in browser getting below error:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>Method not found: 'System.Net.Http.HttpRequestMessage System.Web.Http.ApiController.get_Request()'.
</ExceptionMessage>
<ExceptionType>System.MissingMethodException</ExceptionType>
<StackTrace>at BMI.Controllers.APIController.<CheckHealth>d__0.MoveNext() at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine) at BMI.Controllers.APIController.CheckHealth() at lambda_method(Closure , Object , Object[] ) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass6_3.<GetExecutor>b__2(Object instance, Object[] methodParameters) at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ApiControllerActionInvoker.
<InvokeActionAsyncCore>d__1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ActionFilterResult.
<ExecuteAsync>d__5.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Dispatcher.HttpControllerDispatcher.
<SendAsync>d__15.MoveNext()
</StackTrace>
</Error>
I have implemented IHttpActionResult as below:
public class TextResult : IHttpActionResult
{
string message;
HttpRequestMessage request;
public TextResult(string message, HttpRequestMessage request)
{
this.message = message;
this.request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage()
{
Content = new StringContent(message),
RequestMessage = request
};
return Task.FromResult(response);
}
}
The actual method in my project is post but here I am trying to fix with get first then I believe post will also work.
Here to mention the below method work perfectly, so I think something I am missing with IHttpActionResult:
[HttpGet]
[Route("api/getok")]
public JsonResult<string> getJson()
{
return Json("OK");
}
Do you have any one faced and solved this problem yet. Please help me, thanks in advance.
Using the CreateResponse extension on the request would allow any configuration from the request to be copied over to the response which would probably be missing when you create the response manually like in your example.
public class TextResult : IHttpActionResult {
string message;
HttpRequestMessage request;
public TextResult(string message, HttpRequestMessage request) {
this.message = message;
this.request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) {
var response = request.CreateResponse(HttpStatusCode.OK, message);
return Task.FromResult(response);
}
}
Also the controller action is not defined correctly as it is defined as async Task<IHttpActionResult> when the action is not doing anything async.
Refactor to follow correct syntax if not actually asynchronous.
[HttpGet]
[Route("api/checkhealth")]
public IHttpActionResult CheckHealth() {
var message = "checkhealth method was invoked";
return new TextResult(message, Request);
}

Web API request content empty

I have a DelegatingHandler implementation to log request/response content:
public class RequestAndResponseLoggerDelegatingHandler : DelegatingHandler
{
public IDataAccess Data { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var started = DateTime.UtcNow;
var response = await base.SendAsync(request, cancellationToken);
await Log(started, request, response);
return response;
}
private async Task Log(DateTime start, HttpRequestMessage request, HttpResponseMessage response)
{
var finished = DateTime.UtcNow;
var requestContent = await request.Content.ReadAsStringAsync();
var responseContent = await response.Content.ReadAsStringAsync();
var info = new ApiLogEntry(start, finished, requestContent, responseContent, request, response);
Data.Log(info);
}
}
but for some reason requestContent is coming up empty. request.Content.Length is confirming that there is content, it's just not being extracted.
Any ideas?
The request body stream is read and bound into parameters and as a result of binding, the stream has been positioned to the end. That is why it is coming as empty. If you seek to the beginning before request.Content.ReadAsStringAsync(), it should work. Instead of that, you can simply read the request body first before binding happens, some thing like this.
public class RequestAndResponseLoggerDelegatingHandler : DelegatingHandler
{
public IDataAccess Data { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var started = DateTime.UtcNow;
var requestContent = await request.Content.ReadAsStringAsync();
var response = await base.SendAsync(request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
await Log(started, request, response, requestContent, responseContent);
return response;
}
private async Task Log(DateTime start, HttpRequestMessage request,
HttpResponseMessage response, string requestContent,
string responseContent)
{
var finished = DateTime.UtcNow;
var info = new ApiLogEntry(start, finished, requestContent, responseContent,
request, response);
Data.Log(info);
}
}

How do I set the WWW-Authentication header in an IAuthenticationFilter implementation?

I'm implementing basic authentication using MVC5's IAuthenticationFilter interface. My understanding is that this is now the preferred approach instead of using a DelegatingHandler. I've got it working but the www-authenticate header is not being returned in the response. This is my implementation of ChallengeAsync:
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var result = await context.Result.ExecuteAsync(cancellationToken);
if (result.StatusCode == HttpStatusCode.Unauthorized)
{
result.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", "realm=localhost"));
}
}
The header is returned if I set it in AuthenticateAsync but I think I'm supposed to set it in ChallengeAsync. Sample implementations have been hard to find.
In ChallengeAsync, set context.Result to an instance of type IHttpActionResult, like so.
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken)
{
context.Result = new ResultWithChallenge(context.Result);
return Task.FromResult(0);
}
Provide an implementation, like so.
public class ResultWithChallenge : IHttpActionResult
{
private readonly IHttpActionResult next;
public ResultWithChallenge(IHttpActionResult next)
{
this.next = next;
}
public async Task<HttpResponseMessage> ExecuteAsync(
CancellationToken cancellationToken)
{
var response = await next.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "realm=localhost"));
}
return response;
}
}

Categories

Resources