I have a service which is consuming an SMS REST API using HttpClient:
HttpClient http = this._httpClientFactory.CreateClient();
// Skipped: setup HttpRequestMessage
using (HttpResponseMessage response = await http.SendAsync(request))
{
try
{
_ = response.EnsureSuccessStatusCode();
}
catch (HttpRequestException)
{
string responseString = await response.Content.ReadAsStringAsync(); // Fails with ObjectDisposedException
this._logger.LogInformation(
"Received invalid HTTP response status '{0}' from SMS API. Response content was {1}.",
(int)response.StatusCode,
responseString
);
throw;
}
}
The API returns an error, but I would like to be able to log it. So I need to log both the failing status code (which I can read from response.StatusCode) and the associated content (which may contain additional error useful details).
This code fails on the instruction await response.Content.ReadAsStringAsync() with this exception:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Http.HttpConnection+HttpConnectionResponseContent'.
Module "System.Net.Http.HttpContent", in CheckDisposed
Module "System.Net.Http.HttpContent", in ReadAsStringAsync
Some sources suggest that you shouldn't read the response content when the status code is not in the success range (200-299), but what if the response really contains useful error details?
.NET version used: .NET Core 2.1.12 on AWS lambda linux runtime.
OK, apparently this is a known issue in the .NET API, which has been addressed in .NET Core 3.0. response.EnsureSuccessStatusCode() is actually disposing the response content. It was implemented this way to supposedly help users:
// Disposing the content should help users: If users call EnsureSuccessStatusCode(), an exception is
// thrown if the response status code is != 2xx. I.e. the behavior is similar to a failed request (e.g.
// connection failure). Users don't expect to dispose the content in this case: If an exception is
// thrown, the object is responsible fore cleaning up its state.
This is an undesirable behavior which was removed from 3.0. In the meantime, I just switched to use IsSuccessStatusCode before the log:
HttpClient http = this._httpClientFactory.CreateClient();
// Skipped: setup HttpRequestMessage
using (HttpResponseMessage response = await http.SendAsync(request))
{
if (!response.IsSuccessStatusCode)
{
string responseString = await response.Content.ReadAsStringAsync(); // Fails with ObjectDisposedException
this._logger.LogInformation(
"Received invalid HTTP response status '{0}' from SMS API. Response content was {1}.",
(int)response.StatusCode,
responseString
);
_ = response.EnsureSuccessStatusCode();
}
}
A little bit more redundant, but it should work.
Related
I am trying to do File Copy operation in c# .net core using Microsoft graph API.
It is an asynchronous operation, and by doc, it says it returns a location in the response header to check the status of the operation,
Now the issue is I need its response header so that I can check the status of file copy operation but every time I am getting 'null' as value, I have tried following code,
DriveItem response = await graphClient.Sites[siteId].Drive.Items[itemId]
.Copy(fileName, parentReference)
.Request()
.PostAsync();
The driveItem returns null but I think at least it should have returned the additional data-carrying response status and location.
When I use online graph api it just works fine returning response and location, but it doesn't with graph client service.
Apparently it is an issue with msgraph-sdk-dotnet, at least it could be reproduced in 3.8.0 version, the error occurs while deserializing HTTP response. Probably it would be more beneficial to report it as a bug in referenced repository.
Meanwhile you could consider to construct a request for Copy a DriveItem endpoint and process response (including extracting Location header) as demonstrated below:
var message = graphClient.Sites[siteId].Drive.Items[itemId]
.Copy(fileName, parentReference)
.Request()
.GetHttpRequestMessage();
message.Method = HttpMethod.Post;
var body = new DriveItemCopyRequestBody {Name = fileName, ParentReference = parentReference};
message.Content = new StringContent(graphClient.HttpProvider.Serializer.SerializeObject(body));
message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = graphClient.HttpProvider.SendAsync(message).Result;
Console.Write(response.Headers.Location);
I am trying to implement a Xamarin app that works with the Asana API.
I have successfully implemented the OAuth as documented in the Asana documentation here... at least I assume it is successful. I get an access token from the token endpoint in an HTTPResponse with HTTP Status "OK".
But then when I turn around and try to make an API call with that same access token, I get a 403 Forbidden error. I tried the same API call in my browser (after logging in to Asana), and it works fine, which leads me to believe that I do have access to the resource, I must have an issue with authorizing the request on my end.
The API call in question is (documented here): https://app.asana.com/api/1.0/workspaces.
My C# code is as follows (abbreviated to relevant parts, and assume that ACCESS_TOKEN contains the access token I got from the token exchange endpoint):
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", ACCESS_TOKEN);
client.DefaultRequestHeaders.Add("Accept", "application/json");
And then I use this HttpClient (named client) in the following function:
// Returns a list of the Asana workspace names for the logged in user.
private async Task<List<string>> GetWorkspacesAsync()
{
List<string> namesList = new List<string>();
// Send the HTTP Request and get a response.
this.UpdateToken(); // Refreshes the token if needed using the refresh token.
using (HttpResponseMessage response = await client.GetAsync("/workspaces"))
{
// Handle a bad (not ok) response.
if (response.StatusCode != HttpStatusCode.OK)
{
// !!!THIS KEEPS TRIGGERING WITH response.StatusCode AS 403 Forbidden!!!
// Set up a stream reader to read the response.
// This is for TESTING ONLY
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
Debug.WriteLine(content);
}
throw new HttpRequestException("Bad HTTP Response was returned.");
}
// If execution reaches this point, the Http Response returned with code OK.
// Set up a stream reader to read the response.
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
JsonValue responseJson = JsonValue.Parse(content);
foreach (JsonValue workspaceJson in responseJson["data"])
{
string workspaceName = workspaceJson["name"];
Debug.WriteLine("Workspace Name: " + workspaceName);
namesList.Add(workspaceName);
}
}
}
// I have other awaited interactions with app storage in here, hence the need for the function to be async.
return namesList;
}
Finally found the answer. It looks like I was using HttpClient incorrectly; a subtle thing that should be equivalent, but is not due to the way it is implemented.
The answer
I needed to place the final slash at the end of the BaseAddress property of HttpClient, and NOT at the start of the relative address for the specific request. This answered question explains this.
To fix my code
I needed to change the setting up of the BaseAddress:
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0/"; // FINAL SLASH NEEDED HERE
And remove the slash from the request's relative address:
// DO NOT put slash before relative address "workspaces" here
using (HttpResponseMessage response = await client.GetAsync("workspaces"))
Why I got the original error
When HttpClient combined the BaseAddress with the relative URI I specified in GetAsync(), it dropped off some of the base address, since the final slash was not included. The resulting address from combining the BaseAddress with the relative URI was a valid URL, but not a valid page/API call in Asana. Asana thus did an automatic redirect to a login page, which, of course, the rest of the API call would be forbidden from there.
How I discovered this
In debugging, I grabbed the access token returned during my app's authorization with Asana. I then recreated the request to the "/workspaces" API myself in Postman, and the request worked as expected. This confirmed that my authorization worked fine, and the issue must be with the specific request rather than the authorization. In debugging I then looked into the HttpResponseMessage, which has a property called RequestMessage, that includes the actual URL the GetAsync() made the request against. I observed the Login URL from Asana, rather than the BaseAddress I specified... which led me to the question/
answer linked above.
Hope this explanation helps anyone who comes across a similar error!
I am using HttpClient (System.Net.Http) to send a GET request and I occasionally get the following exception:
Unable to read data from the transport connection. The connection was closed before all data could be read. Expected 894 bytes, read 0 bytes.
The instance of HttpClient that I am using is static, as Microsoft recommends, and I set the Connection header to "keep-alive". I am setting the Authorization header per request and sending the request using the following code:
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authorizationHeader);
var response = await HttpClient.SendAsync(request);
The exception is thrown on the last line in the snippet. Can someone help me figure out what would be causing this exception to be thrown?
This exception was stemming from the issue described here.
The fix is to download IIS Crypto and set the template to Best Practices (requires reboot) on all relevant servers.
I'm trying to understand something about exception handling with a HttpWebRequest.
I have a client library and it's making a request to a WebAPI controller;
HttpWebRequest r = (HttpWebRequest)WebRequest.Create(url);
r.Method = "POST";
r.ContentType = "application/json";
foreach (var header in request.Headers)
{
r.Headers.Add(header.Key, header.Value.ToString());
}
r.ContentLength = request.RequestBody.Length;
using (StreamWriter writer = new StreamWriter(r.GetRequestStream()))
writer.Write(request.RequestBody);
I know the request will throw an exception, and contain the message entity already exists - 1234.
When I get the response;
using (HttpWebResponse response = (HttpWebResponse)r.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
return reader.ReadToEnd();
return "Invalid";
}
I get a WebException thrown. So, the caller of the request has a try..catch in it. And I get the WebException. What I get is a protocol error, not the 500 internal server error that was thrown (using correct status codes to represent the message comes later). Now if I read the Response of the WebException, it does contain my message and the stacktrace.
Questions
Why do I not get a status code of 500 in my response, why does it throw a protocol error?
Is there a more correct way of handling the request?
I have searched around and found some people getting this issue when not using the correct headers etc. But as far as I can tell, I have added all the headers that I can and still get the same behavior.
An 500 internal server error usually means that the API received the request but threw an unhandled exception while processing it, thus the "Internal Server Error".
You may log to a database or file all your API's unhandled exceptions to help your debugging process. Good luck.
I'm writing a Web API in using the MVC 4 Web API framework. I'm using BASIC authentication with a custom authentication message handler inheriting from DelegatingHandler.
Everything is working peachy, except my error handling.
I'm using the Web API as a wrapper over an existing authentication mechanism and an exception is thrown when the user's password is expired. Within the API action methods, I am able to handle this easily by throwing an HTTPResponseException and I get a tidy little json object such as below:
{
"Message": "Password is expired."
}
If I try this same approach in my Auth Message Handler, I get all the nasty .NET response output.
<html>
<head>
<title>Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.</title>
<style>
body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;}
p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
... you get the idea ...
I think I'm close... in SendAsync
catch (Exception ex)
{
var response = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message);
response.Headers.Add("WWW-Authenticate", "Basic realm=\"myrealm\"");
}
Here, perhaps if I can somehow get a Task<HttpResponseMessage> from my response object to return, I think I might be ok.
Basically my question is "How should exception handling really be done so that I am able to return a nice json/xml object to the user?"
One more thing to note, this is .NET 4, not 4.5. I've seen a few examples using the await keyword, but that won't work in this scenario.
You can wrap your response in a task:
catch (Exception)
{
return Task<HttpResponseMessage>.Factory.StartNew(() =>
{
var response = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message);
response.Headers.Add("WWW-Authenticate", "Basic realm=\"myrealm\"");
return response;
});
}
return base.SendAsync(request, cancellationToken);