could someone help me how to fix this error?
I can't resolve this until now. I can't figure out where the problem is.
"Cannot access a disposed object. Object name: 'JsonDocument'"
I just started to use "Sytem.Text.Json" that's why I'm still learning and want to to know how to use it properly.
Thank you.
public static async Task<JsonElement> ParseJsonData(string api, CancellationToken ct)
{
clientHandler = new HttpClientHandler()
{
UseProxy = Proxy.IsUseProxy ? true : false,
Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
//ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; },
// SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var uri = new Uri(api, UriKind.Absolute);
utils.SetConnection(uri);
client = new HttpClient(clientHandler);
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{
AddRequestHeaders(request, uri);
return await ResponseMessage(request, ct);
}
}
private static async Task<JsonElement> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
{
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
{
ct.ThrowIfCancellationRequested();
using (var content = response.Content)
{
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
var json = await ParseStream(stream, response);
return json.RootElement;
}
}
}
private static async Task<JsonDocument> ParseStream(Stream stream, HttpResponseMessage response)
{
if (stream == null || stream.CanRead == false)
{
return default;
}
HttpStatusCode status = response.StatusCode;
StatusCode.status = status.ToString();
StatusCode.value = (int)status;
using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json;
}
}
UPDATE: (Here's what I've tried)
public static async Task<JsonDocument> ParseJsonData(string api, CancellationToken ct)
{
clientHandler = new HttpClientHandler()
{
UseProxy = Proxy.IsUseProxy ? true : false,
Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; }
// SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var uri = new Uri(api, UriKind.Absolute);
utils.SetConnection(uri);
client = new HttpClient(clientHandler);
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{
AddRequestHeaders(request, uri);
return await ResponseMessage(request, ct);
}
}
private static async Task<JsonDocument> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
{
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
{
ct.ThrowIfCancellationRequested();
HttpStatusCode status = response.StatusCode;
using (var content = response.Content)
{
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
if (stream == null || stream.CanRead == false) { return default; }
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
var json = await JsonDocument.ParseAsync(stream, options).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json;
}
}
}
public static async Task<test> GetData(string id, CancellationToken ct)
{
string API = $"https://www.test.com/api/videos/{id}";
using (var root = await MyClientHelper.ParseJsonData(API, ct))
{
var json = root.RootElement;
//here i can access the root and dispose after
return new test()
{
/////
}
}
}
It's the way using works. When you leave a using clause, the object is disposed. That's on purpose.
So consider your code:
using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json; <------ the moment you return it you also dispose it
}
So when you try to access it outside, you are getting the error:
var json = await ParseStream(stream, response);
// here your object is already disposed
return json.RootElement;
Solution: before existing the parse function, return your json. The JsonDocument object should not be used outside the using clause.
You should NOT omit to dispose of the object as a workaround: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsondocument?view=netcore-3.1
Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.
Luckily, there's the Clone() method. So instead of:
using JsonDocument doc = JsonDocument.Parse(jsonString);
return doc; // or return doc.RootElement;`
You can do this:
using JsonDocument doc = JsonDocument.Parse(jsonString);
var root = doc.RootElement.Clone();
return root;
"Gets a JsonElement that can be safely stored beyond the lifetime of the original JsonDocument."
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=net-5.0
Related
Wanted to verify if HttpCLient instance should be created outside method passed to polly for ExecuteAsync, or in?
My current usage varies between the two options and I am not sure which is the correct one?
Also, if it incurs some drawbacks, or possible memory leaks, etc. ?
Get:
var client = new HttpClient(new NativeMessageHandler()) { Timeout = new TimeSpan(0, 0, TimeOutSec) };
var httpResponse = await AuthenticationOnUnauthorizePolicy.ExecuteAsync(async () =>
{
UpdateClientHeader(client, correlationId);
return await client.GetAsync(url, token);
});
Post:
var httpResponse = await AuthenticationOnUnauthorizePolicy.ExecuteAsync(async () =>
{
using (var client = new HttpClient(new NativeMessageHandler()) { Timeout = new TimeSpan(0, 0, TimeOutSec) })
{
UpdateClientHeader(client, correlationId);
WriteNetworkAccessStatusToLog();
return await client.PostAsync(url, content);
}
});
The policy used here:
AuthenticationOnUnauthorizePolicy = Policy
.HandleResult<HttpResponseMessage>(reposnse => reposnse.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(1, onRetryAsync:
async (response, count, context) =>
{
_logger.Info("Unauthorized Response! Retrying Authentication...");
await Authenticate();
});
Appreciates any comments on the code above.
Is there a correct way?
Do I need to use the Context to get the client again, or is my usage okay?
Update:
Authenticate method:
public virtual async Task Authenticate()
{
// lock it - only one request can request token
if (Interlocked.CompareExchange(ref _isAuthenticated, 1, 0) == 0)
{
var result = new WebResult();
var loginModel = new LoginModel
{
email = _settingService.Email,
password = _settingService.Password
};
var url = ......
var correlationId = Guid.NewGuid().ToString();
try
{
var stringObj = JsonHelper.SerializeObject(loginModel);
HttpContent content = new StringContent(stringObj, Encoding.UTF8, HttpConsts.JsonMediaType);
using (var client = new HttpClient(new NativeMessageHandler()) { Timeout = new TimeSpan(0, 0, TimeOutSec) }
)
{
UpdateClientHeader(client, correlationId, useToken: false); // not token, we need new one
using (var httpResponse = await client.PostAsync(url, content))
{
var sReader = await httpResponse.Content.ReadAsStringAsync();
await HandleRequest(result, sReader, httpResponse, correlationId, url, "result");
}
}
if (result != null && !result.HasError)
{
_loginToken = result.Token;
}
}
catch (Exception ex)
{
// Log error
}
finally
{
_isAuthenticated = 0;
}
}
}
Update client headr method:
if (_loginToken != null &&
!client.DefaultRequestHeaders.Contains("Token"))
{
client.DefaultRequestHeaders.Add("Token", _loginToken );
}
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(HttpConsts.JsonMediaType));
I want to replace the request/response body in my middleware. Suppose if client sends Hello, I want to change it to Hola and similarly with response. I found the code that works but not to my requirement. My question is why this works and the other do not. It is actually the same code.
Working Code
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
var stream = request.Body;// currently holds the original stream
var originalReader = new StreamReader(stream);
var originalContent = await originalReader.ReadToEndAsync();
var notModified = true;
try
{
if (originalContent != null)
{
//Master is some model name
var modifiedData = new Master() { Id = "Changed in Middleware", Email = "Changed" };
var json = JsonConvert.SerializeObject(modifiedData);
//json variable is just a string here
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
stream = new MemoryStream(requestData);
notModified = false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (notModified)
{
//putting original data
var requestData = Encoding.UTF8.GetBytes(originalContent);
stream = new MemoryStream(requestData);
}
request.Body = stream;
await next(context);
}
Not working Code But My Requirement
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
var stream = request.Body;// currently holds the original stream
var originalReader = new StreamReader(stream);
var originalContent = await originalReader.ReadToEndAsync();
var notModified = true;
try
{
if (originalContent != null)
{
var json = "This is just a string as deserializing returns string";
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
stream = new MemoryStream(requestData);
notModified = false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (notModified)
{
//putting original data
var requestData = Encoding.UTF8.GetBytes(originalContent);
stream = new MemoryStream(requestData);
}
request.Body = stream;
await next(context);
}
So the working code pass on the changes to the controller, and has value Id ="Changed in middlware" and Email = "changed" but the not working code doesnt pass anything ... in the controller, argument has null and not the "This is just a string as deserializing returns string" value. I know this is not some magic happening. Am i missing something?
You need to convert string to jsontype.Try to change your code like this:
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
var stream = request.Body;// currently holds the original stream
var originalReader = new StreamReader(stream);
var originalContent = await originalReader.ReadToEndAsync();
var notModified = true;
try
{
if (originalContent != null)
{
var str = "This is just a string as deserializing returns string";
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
stream = new MemoryStream(requestData);
notModified = false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (notModified)
{
//putting original data
var requestData = Encoding.UTF8.GetBytes(originalContent);
stream = new MemoryStream(requestData);
}
request.Body = stream;
await next(context);
}
TestRequestBody Action:
public IActionResult TestRequestBody([FromBody] string s)
{
return Ok();
}
result:
After reading other answers I can't realize why SendAsync is so slow.
Calling same endpoint from Postman, I got a response in 160ms.
Calling from the code below, takes 10 seconds. I'm using a c# desktop application to make the call.
public static async Task<string> GetToken()
{
var url = "....";
var dict = new Dictionary<string, string>();
dict.Add("username", "foo");
dict.Add("password", "bar");
using (var client = new HttpClient(
new HttpClientHandler
{
Proxy = null,
UseProxy = false
}))
{
//bypass SSL
ServicePointManager.ServerCertificateValidationCallback = new
RemoteCertificateValidationCallback
(
delegate { return true; }
);
var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(dict) };
var res = await client.SendAsync(req); //10 seconds here!
if (res.StatusCode != HttpStatusCode.OK)
return string.Empty;
var token = await JsonConvert.DeserializeObject<TokenResponse>(res.Content.ReadAsStringAsync());
return token.access_token;
}
}
Your code is tangled and ignores IDisposable and this: HttpClient is intended to be instantiated once per application, rather than per-use.
Make reusable method for other-type requests
private static readonly HttpClient client = new HttpClient();
private async Task<T> PostDataAsync<T>(string url, Dictionary<string, string> formData)
{
using (HttpContent content = new FormUrlEncodedContent(formData))
using (HttpResponseMessage response = await client.PostAsync(url, content).ConfigureAwait(false))
{
response.EnsureSuccessStatusCode(); // throws if 404, 500, etc.
string responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<T>(responseText);
}
}
Usage
public static async Task<string> GetToken()
{
var url = "....";
var dict = new Dictionary<string, string>();
dict.Add("username", "foo");
dict.Add("password", "bar");
try
{
TokenResponse token = await PostDataAsync<TokenResponse>(url, dict);
return token.access_token;
}
catch (HttpRequestException ex)
{
// handle Exception here
return string.Empty;
}
}
I am trying to Get/Post using the HttpClient Class and facing the following issues
Do not know how to return the Task from HttpResponseMessage.Content.ReadAsStringAsync().ContinueWith() method.
For some reason, it is keep on cancelling automatically
private static Task<T> HttpClientSendAsync<T>(string url, object data, HttpMethod method, string contentType, CancellationToken token)
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(method, url);
RetryDelegatingHandler retryDelegatingHandler = new RetryDelegatingHandler();
retryDelegatingHandler.PreAuthenticate = true;
retryDelegatingHandler.Credentials = Credential;
retryDelegatingHandler.Proxy = null;
HttpClient httpClient = new HttpClient(retryDelegatingHandler);
httpClient.Timeout = new TimeSpan(Constants.TimeOut);
if (data != null)
{
byte[] byteArray = Encoding.ASCII.GetBytes(Helper.ToJSON(data));
MemoryStream memoryStream = new MemoryStream(byteArray);
httpRequestMessage.Content = new StringContent(new StreamReader(memoryStream).ReadToEnd(), Encoding.UTF8, contentType);
}
Task<HttpResponseMessage> httpResponseMessage = httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.ContinueWith((task) =>
{
if (!task.IsFaulted)
{
HttpResponseMessage response = task.Result;
response.Content.ReadAsStringAsync().ContinueWith(
(stringTask) =>
{
if (!stringTask.IsFaulted)
{
return Helper.FromJSON<T>(stringTask.Result);
}
else
{
Logger.Log(string.Format("SendAsyncRequest Task IsFaulted: {0} \nException: {1}", typeof(T), task.Exception));
UpdateError(typeof(T).ToString());
return default(T);
}
});
}
else
{
Logger.Log(string.Format("SendAsyncRequest Task IsFaulted: {0} \nException: {1}", typeof(T), task.Exception));
UpdateError(typeof(T).ToString());
return default(T);
}
});
}
Update: It does work however still it does not work while trying to handle the Fault.
return httpClient.SendAsync(httpRequestMessage).ContinueWith(task =>
{
var response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(stringTask =>
{
var json = stringTask.Result;
return Helper.FromJSON<T>(json);
});
}).Unwrap();
Task.ContinueWith returns the continuation task: a Task or Task<T>. If I'm understanding the question, in your case here you could do something like this:
var continuation = httpResponseMessage.ContinueWith((task) =>
{
if (!task.IsFaulted)
{
HttpResponseMessage response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(
(stringTask) =>
{
...
and continuation will end up being a Task<Task<T>> which you can call .Unwrap() on to turn that into a proxy task Task<T>.
I have this method in my Windows Phone 8 app where I get some data from a url
public async static Task<byte[]> getData(string url)
{
HttpClient client = null;
HttpResponseMessage response = null;
Stream stream = null;
byte[] dataBytes = null;
bool error = false;
try
{
Uri uri = new Uri(url);
client = new HttpClient();
response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
stream = await response.Content.ReadAsStreamAsync();
dataBytes = getDataBytes(stream);
if (dataBytes == null)
{
error = true;
}
else if (dataBytes.Length == 0)
{
error = true;
}
}
catch (HttpRequestException )
{
}
if (error)
{
return getData(url); // this is where the issue is
}
return dataBytes;
}
But since the method is an async one, the return type cannot be a Task, like I have done on the line return getData(url); since getData(string) returns Task. Any ideas on how I can rewrite this to make it work?
Awaiting the result of getData may do the trick. Still, I strongly recommand you to rewrite your method with a loop, rather than recursively call the method again. It makes it hard to read, and may lead to unforeseen issues.
public async static Task<byte[]> getData(string url)
{
bool success = false;
byte[] dataBytes = null;
while (!success)
{
try
{
Uri uri = new Uri(url);
var client = new HttpClient();
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
dataBytes = getDataBytes(stream);
success = dataBytes != null && dataBytes.Length > 0;
}
catch (HttpRequestException)
{
}
}
return dataBytes;
}
you can get around the compile error by adding changing the return to the following :
if (error)
{
return await getData(url); // this is where the issue is
}
I hope you do realize that this code will keep on looping as long as no data is returned? having many clients like this could easily overload your server.