I'm using RestSharp in Windows Phone 8.1 application. RestClient throws exception when server returns response with code different than 200. Wiki says that I should get response with correct status code. I want to get content of response because server returns error messages.
private async Task<T> ExecuteAsync<T>(IRestRequest request)
{
if (!_networkAvailableService.IsNetworkAvailable)
{
throw new NoInternetException();
}
request.AddHeader("Accept", "application/json");
IRestResponse<T> response;
try
{
response = await _client.Execute<T>(request); //here I get exception
}
catch (Exception ex)
{
throw new ApiException();
}
HandleApiException(response);
return response.Data;
}
private void HandleApiException(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
return;
}
//never reach here :(
ApiException apiException;
try
{
var apiError = _deserializer.Deserialize<ApiErrorResponse>(response);
apiException = new ApiException(apiError);
}
catch (Exception)
{
throw new ApiException();
}
throw apiException;
}
sample of server response:
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 86
Content-Type: application/json;charset=UTF-8
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: *
X-Powered-By: ASP.NET
Date: Mon, 28 Sep 2015 12:30:10 GMT
Connection: close
{"error":"invalid_token","error_description":"The user name or password is incorrect"}
If you are working under Windows Phone 8.1, you're using RestSharp Portable (https://github.com/FubarDevelopment/restsharp.portable) (probably).
Use this:
var client = new RestClient();
client.IgnoreResponseStatusCode = true;
With this, you don't get exception with 404 for example.
I hope it will be helpful :)
Related
I have developed a C# application that calls a REST service existing in some PC in the network.
This is the code to make a request:
public async Task<bool> OpenDoorAsync(string name, int delay)
{
var data = await CallApiAsync("api/door/remoteOpenByName", new Dictionary<string, string> { { "doorName", name }, { "interval", delay.ToString() } });
return data.IsSuccess;
}
private async Task<ResponseData> CallApiAsync(string endPoint, Dictionary<string, string> parameters)
{
try
{
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Connection.ParseAdd("keep-alive");
var content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
string fullUri = "http://192.168.0.122:8088/api/door/remoteOpenByName?doorName=10.185.85.237-1&interval=5&access_token=1234";
HttpResponseMessage response = await client.PostAsync(fullUri, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ResponseData>(responseBody);
}
}
catch (Exception ex)
{
OnError("Existió un error al realizar la llamada.", ex);
return new ResponseData()
{
message = "failed"
};
}
}
Entry point is OpenDoorAsync, called this way, from a Winform form:
await _device.OpenDoorAsync(TxtNombrePuerta.Text.Trim(), IntInterval.Value);
Well, after the execution of PostAsync method, a HTTP 500 error is returned:
{StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Connection: close
Date: Thu, 28 Jan 2021 21:06:35 GMT
Set-Cookie: JSESSIONID=4062B932CDB44B4CA3FCCC275937AC15; Path=/; HttpOnly
Server: Apache-Coyote/1.1
Content-Length: 2580
Content-Language: en
Content-Type: text/html; charset=utf-8
}}
However, if I make the same request using Google Chrome RESTED extension, it works perfectly:
Just in case, I analyzed Google Chrome developer tools after the RESTED call and I have not found anything weird. I thought maybe I missed to send something else in the headers.
Does anybody know what is happening with the call from the C# application? Clearly, I am not doing something that RESTED is.
I don't really know why it does not work when using HttpClient class, however, I solved the problem installling an using RestSharp NuGet package.
Finally, the code was reduced to this:
private ResponseData CallApi(string endPoint, Dictionary<string, string> parameters)
{
try
{
string fullUri = $"http://{GetServerIp()}:{((MainWindow)MainWindow).ServerPort}/{endPoint}?{GetQueryParameters(parameters)}";
var client = new RestClient(fullUri);
var request = new RestRequest(Method.POST);
var response = client.Execute(request);
return JsonConvert.DeserializeObject<ResponseData>(response.Content);
}
catch (Exception ex)
{
OnError("Existió un error al realizar la llamada.", ex);
return new ResponseData()
{
message = "failed"
};
}
}
Your working example is passing cookies, which may be required for the API you're calling.
I try to call an external api, when I am using the Postman, it is working and returning value as follows:
Post to URL: https://test.com/api/v1/users/check
Data Raw Jason to post:
{
"access_token":"4444-EA444B6-2844C7-A09C-44B05CA78E42A3",
"email":"test#test.com",
"create_user": true,
"first_name": "test4",
"last_name": "test",
"phone": 3104054512
}
So this is working and returning me response model.
but when try this code to call the api:
Controller:
[Route("CreateUser")]
public Task<UserReturn> CreateUser([FromBody] User user)
{
return homebirdRepository.CreateUser(user);
}
public async Task<UserReturn> CreateUser(User userCheck)
{
using (GetWSObject<UserReturn> addObjectInt = new GetWSObject<UserReturn>())
{
return await addObjectInt.PostWSObjectModel("api/v1/users/check", userCheck, "API_URI");
}
}
public async Task<T> PostWSObjectModel(string uriActionString, Object model, string apiKey)
{
T returnValue = default(T);
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(WebConfigurationManager.AppSettings[apiKey]);
var content = new StringContent(JsonConvert.SerializeObject(model));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await client.PostAsync(uriActionString, content);
response.EnsureSuccessStatusCode();
var test = response.Content.ReadAsStringAsync().Result;
returnValue = JsonConvert.DeserializeObject<T>(((HttpResponseMessage)response).Content.ReadAsStringAsync().Result);
}
return returnValue;
}
catch (Exception e)
{
throw (e);
}
}
This code is returning me this error:
StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content:
System.Net.Http.StreamContent,
Headers:
{
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-
With
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Cache-Control: no-cache, private
Date: Sat, 28 Dec 2019 00:00:04 GMT
Set-Cookie:laravel_session=eyJpdiI6IlI2MUdzOFJmS0RcL1k1VmJCeTc4bk1nPT0iLCJ2YWx1ZSI6IlZXNW11MGw2bXk0ajFEaTM2VnhmbUZjQnFzdnRDRHV5ejJMaDRqTVJYQm1yclNyUUkweDNRMUhpZDZwblpES1MiLCJtYWMiOiI0NmFiODA4YzEyNTkxZDllNDViNGUwOGIzYjY2ZWYxZGQwNzI1NmZmYzYxYTBkZGU0M2NmMDBlYzIzN2E3OTFjIn0%3D; expires=Sat, 28-Dec-2019 02:00:04 GMT; Max-Age=7200; path=/; httponly
Server: Apache
Content-Length: 21
Content-Type: text/html; charset=UTF-8
}}
You most likely have a header, cookie, etc. that is not set in C# which is set in Postman. There are several ways you can determine which properties are being set in Postman. I've answered a similar question here. Fiddler or some other separate tool shouldn't be necessary.
you must add the Authorization header, this one is added and calculated by postman, you can copy/post. the following if you are using a Basic authentication.
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "value to copy from postman");
OK. So this is really starting to baffle me now. I can get this to work on one UWP app, but not this one.
I have this piece of code to post:
public async Task<string> SubmitDataWithTokenAsync(string url, string token)
{
var httpClient = new HttpClient();
HttpResponseMessage response;
try
{
var root = new
{
fields = new Dictionary<string, string>
{
//General Parameters...
//Inspection Parameters...
//Startup Parameters...
//Mechanical Parameters...
//Electrical Parameters...
//SCR Parameters...
//Shutdown Parameters...
}
};
var s = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat };
var content = JsonConvert.SerializeObject(root, s);
var request = new HttpRequestMessage(HttpMethod.Post, url);
//Add the token in Authorization header
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
response = await httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
catch (Exception ex)
{
return ex.ToString();
}
}
}
the content comes back just fine, so it seems....
"{\"fields\":{\"Date\":\"8/16/2018 2:18:48 PM -04:00\",\"Maximo\":null,\"IBX\":\"DC4\",\"Generator\":\"Generator D\",\"AirQuality\":\"Red / Unhealthy\",\"Engineer\":\"Kassim Ganiyou\",\"MT1Level\":null,\"MT2Level\":null,\"StartDTLevel\":null,\"BC1V\":null,\"BC1A\":null,\"BC2V\":null,\"BC2A\":null,\"StartCoolantTEmp\":null,\"StartHours\":null,\"Reason\":null,\"InspectionNotes\":null,\"StartTime\":null,\"CrankV1\":null,\"CrankV2\":null,\"Emissions\":null,\"SCRStartTime\":null,\"OilPressure\":null,\"CoolantTemp\":null,\"BatteryVolt\":null,\"LeftExhTemp\":null,\"RightExhTemp\":null,\"ABVolts\":null,\"BCVolts\":null,\"CAVolts\":null,\"AAmps\":null,\"BAmps\":null,\"CAmps\":null,\"KW\":null,\"Frequency\":null,\"SCROutletTemp\":null,\"NOx\":null,\"UReaFLow\":null,\"Alarms\":null,\"SCRSTopTime\":null,\"StopTime\":null,\"StopHours\":null}}"
The request comes back:
{Method: POST, RequestUri: 'https://graph.microsoft.com/v1.0/sites/root/lists/A07CEC93-XXXX-XXXX-XXXX-0F756D2EF63A/items', Version: 2.0, Content: System.Net.Http.StringContent, Headers:
{
Authorization: Bearer eyJ0eX...PUQ
Content-Type: application/json; charset=utf-8
Content-Length: 603
}}
But then the response is:
{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
client-request-id: bb26e6fe-4fae-41ae-921d-aeb39063bd8e
Strict-Transport-Security: max-age=31536000
request-id: bb26e6fe-4fae-41ae-921d-aeb39063bd8e
Transfer-Encoding: chunked
x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"East US","Slice":"SliceC","Ring":"5","ScaleUnit":"001","Host":"AGSFE_IN_1","ADSiteName":"EUS"}}
Duration: 319.5961
Cache-Control: private
Date: Thu, 16 Aug 2018 20:03:10 GMT
Content-Type: application/json
}}
I have another UWP app going to the same sharepoint site and I get a Status 201 no problem. I am just not seeing where my issue is in this case.
This is why it is good to take a break from a project and come back to it later.
I found out that I had mistyped ONE letter on the SharePoint list. So today when I reviewed the response code it pointed it our to me.
The code WAS good.
Thanks all.
I want to handle HEAD requests in my Web API 2 application. I copy-pasted the code from StrathWeb's blog post Adding HTTP HEAD support to ASP.NET Web API:
public class HeadHandler : DelegatingHandler
{
private const string Head = "IsHead";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Head)
{
request.Method = HttpMethod.Get;
request.Properties.Add(Head, true);
}
var response = await base.SendAsync(request, cancellationToken);
object isHead;
response.RequestMessage.Properties.TryGetValue(Head, out isHead);
if (isHead != null && ((bool) isHead))
{
var oldContent = await response.Content.ReadAsByteArrayAsync();
var content = new StringContent(string.Empty);
content.Headers.Clear();
foreach (var header in response.Content.Headers)
{
content.Headers.Add(header.Key, header.Value);
}
content.Headers.ContentLength = oldContent.Length;
response.Content = content;
}
return response;
}
}
When I make a HEAD request from Fiddler:
HEAD http://localhost:54225/api/books/5 HTTP/1.1
User-Agent: Fiddler
Host: localhost:54225
I get the following response back:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc2ltb25lXERvY3VtZW50c1xTaW1vbnNEb2N1bWVudHNcSVRcQyNcRGVtb0NvZGVcUkVTVGZ1bFdlYlNlcnZpY2VDbGllbnRERU1PXEJvb2tTZXJ2aWNlXGFwaVxib29rc1w1?=
X-Powered-By: ASP.NET
Date: Sun, 19 Feb 2017 12:09:52 GMT
The Content-Length is 0 but it should be 104 bytes.
When I add a breakpoint to the HeadHandler and step through the code it seems to set the Content-Length header value correctly, to 104 bytes, before returning the response.
Could there be some other step in the pipeline that is running after the HeadHandler, which recognises the response has no body, and sets the Content-Length to 0? The only things in the WebApiConfig.cs that come after the HeadHandler has been added are the mapping of the HttpAttribute routes and the setting up of the default route, neither of which seem likely to me to result in the Content-Length header being reset. Alternatively, could a configuration setting somewhere possibly affect the Content-Length returned in the response?
All you need to do is set the Content-Length of the response's Content.Headers - no need to create new content for the response. The act of requesting HEAD removes the body content from the response anyway.
This may have been a change since the article you quoted was written (2013), and no longer needs to create content from scratch...
So this is all you should need:
public class HeadHandler : DelegatingHandler
{
private const string Head = "IsHead";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Head)
{
request.Method = HttpMethod.Get;
request.Properties.Add(Head, true);
}
var response = await base.SendAsync(request, cancellationToken);
object isHead;
response.RequestMessage.Properties.TryGetValue(Head, out isHead);
if (isHead != null && ((bool)isHead))
{
var oldContent = await response.Content.ReadAsByteArrayAsync();
response.Content.Headers.ContentLength = oldContent.Length;
return response;
}
return response;
}
}
I have a server which is throwing WebFaultException
try
{
...
}
catch (InvalidPasswordException)
{
throw new WebFaultException<String>
("This is a bad password",
HttpStatusCode.Unauthorized);
}
So far so good. However when this WebFault is caught in another C# project (the client) I don't know how to get the details.
try
{
HttpWebRequest httpWebRequest = WebRequest.Create(uri) as HttpWebRequest;
httpWebRequest.Method = verb;
httpWebRequest.ContentType = "text/xml";
httpWebRequest.ContentLength = serializedPayload.Length;
using (StreamWriter streamOut = new StreamWriter(httpWebRequest.GetRequestStream(), Encoding.ASCII))
{
streamOut.Write(serializedPayload);
streamOut.Close();
}
// error here on GetResponseStream
using (StreamReader streamIn = new StreamReader(httpWebRequest.GetResponse().GetResponseStream()))
{
string strResponse = streamIn.ReadToEnd();
streamIn.Close();
return strResponse;
}
}
catch (Exception e)
{
// The exception is of type WebException with unauthorized but don't know where the details are
}
This code catches a WebException which I can't find the details for, just the unauthorized.
Thanks
UPDATE 1: When I do the request in fiddler the response body is the detail but as that exception is thrown before the response body is ever read then it isn't showing up. So the question is how do I read the response body DESPITE a non 200 being thrown.
Fiddler Raw Response:
HTTP/1.1 401 Unauthorized
Server: ASP.NET Development Server/10.0.0.0
Date: Tue, 12 Oct 2010 22:42:31 GMT
X-AspNet-Version: 4.0.30319
Content-Length: 100
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Connection: Close
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Email/Password Mismatch</string>
I'm just guessing, but I think you just need to check the .StatusCode member of the result of GetResponse() and don't try to call GetResponseStream() unless it's 200. If it's an error code (401 in your case), then the error details should be in the .Content member of GetResponse().
Something like:
var r = httpWebRequest.GetResponse();
if(r.StatusCode != 200)
{
MessageBox.Show(r.Content); // Display the error
}
else
{
var streamIn = new StreamReader(r.GetResponseStream());
string strResponse = streamIn.ReadToEnd();
streamIn.Close();
return strResponse;
}
WebException has a Response property that you can cast to a HttpWebResponse and then call GetResponseStream on.
On a related note, go get yourself the Microsoft.Http library from the WCF REST Starter kit, it is a way better client library that HttpWebRequest/Response. For more details, I have some blog posts here: http://www.bizcoder.com/index.php/2009/12/08/why-the-microsoft-http-library-is-awesome/
I've used this
try
{
//wcf service call
}
catch (FaultException ex)
{
throw new Exception( (ex as WebFaultException<MyContractApplicationFault>).Detail.MyContractErrorMessage );
}