I'm building an HTTP API client using RestSharp, and I've noticed that when the server returns an HTTP error code (401 Unauthorized, 404 Not Found, 500 Internal Server Error, etc.) the RestClient.Execute() doesn't throw an exception - instead I get a valid RestResponse with a null .Data property. I don't want to manually check for every possible HTTP error code within my API client - does RestSharp provide a better way of passing these errors to my client application?
A little further detail. RestSharp exposes a Response.ErrorException property - if the RestClient.Execute<T>() call causes any exception, it'll be exposed via the ErrorException property instead of being thrown. Their documentation includes the following example:
// TwilioApi.cs
public class TwilioApi {
const string BaseUrl = "https://api.twilio.com/2008-08-01";
public T Execute<T>(RestRequest request) where T : new()
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment); // used on every request
var response = client.Execute<T>(request);
if (response.ErrorException != null)
{
const string message = "Error retrieving response. Check inner details for more info.";
var twilioException = new ApplicationException(message, response.ErrorException);
throw twilioException;
}
return response.Data;
}
}
I've adopted that pattern in my code, but my API server is returning a 401 Unauthorized and yet the ErrorException property is still null. I can see the Unauthorized status code and error message in the RestResponse.StatusCode and RestResponse.StatusDescription properties - but I'm confused as to why an unauthorized response wouldn't result in the ErrorException field being populated.
I encountered this same problem while trying to create a generic error handler for a RestSharp WebAPI client. Given these extension methods:
public static class RestSharpExtensionMethods
{
public static bool IsSuccessful(this IRestResponse response)
{
return response.StatusCode.IsSuccessStatusCode()
&& response.ResponseStatus == ResponseStatus.Completed;
}
public static bool IsSuccessStatusCode(this HttpStatusCode responseCode)
{
int numericResponse = (int)responseCode;
return numericResponse >= 200
&& numericResponse <= 399;
}
}
I made a request that required the response to be deserialized:
public async Task<ResponseModel<TResponse>> PerformRequestAsync<TResponse>(IRestRequest request)
{
var response = await _client.ExecuteTaskAsync<ResponseModel<TResponse>>(request);
ResponseModel<TResponse> responseData;
if (response.IsSuccessful())
{
responseData = response.Data;
}
else
{
string resultMessage = HandleErrorResponse<TResponse>(request, response);
responseData = new ResponseModel<TResponse>
{
Success = false,
ResultMessage = resultMessage
};
}
return responseData;
}
However, during testing, I found that when I had no error handling configured for that case, my web serivce returned an HTML-formatted 404 page when an unmapped URL was requested. This caused the response.ErrorException property to contain the following string:
Reference to undeclared entity 'nbsp'. Line n, position m.
As apparently RestSharp tried to parse the response as XML, even though the content-type was text/html. Maybe I'll file an issue with RestSharp for this.
Of course in production you should never get a 404 when calling your own service, but I want this client to be thorough and reusable.
So there's two solutions I can think of:
Inspect the status code and show the description
Make sure the service returns an error object that you can parse
The former is done quite easily. In HandleErrorResponse() I build the result message (user presentable) and error string (loggable) based on the numeric value of the status code:
public string HandleErrorResponse(IRestRequest request, IRestResponse response)
{
string statusString = string.Format("{0} {1} - {2}", (int)response.StatusCode, response.StatusCode, response.StatusDescription);
string errorString = "Response status: " + statusString;
string resultMessage = "";
if (!response.StatusCode.IsScuccessStatusCode())
{
if (string.IsNullOrWhiteSpace(resultMessage))
{
resultMessage = "An error occurred while processing the request: "
+ response.StatusDescription;
}
}
if (response.ErrorException != null)
{
if (string.IsNullOrWhiteSpace(resultMessage))
{
resultMessage = "An exception occurred while processing the request: "
+ response.ErrorException.Message;
}
errorString += ", Exception: " + response.ErrorException;
}
// (other error handling here)
_logger.ErrorFormat("Error response: {0}", errorString);
return resultMessage;
}
Now as my API responses always are wrapped in a ResponseModel<T> of my making, I can set up an exception filter and a NotFound route to return a parsable response model with the error or exception message in the ResultMessage property:
public class HandleErrorAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
// (log context.Exception here)
context.Response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel<object>
{
Success = false,
ResultMessage = "An exception occurred while processing the request: " + context.Exception.Message
});
}
}
And:
public class ErrorController : ApiController
{
public HttpResponseMessage Handle404()
{
const string notFoundString = "The requested resource could not be found";
var responseMessage = Request.CreateResponse(HttpStatusCode.NotFound, new ResponseModel<object>
{
Success = false,
ResultMessage = notFoundString
});
responseMessage.ReasonPhrase = notFoundString;
return responseMessage;
}
}
This way the response from my service can always be parsed by RestSharp, and I can use the generic logging method:
public string HandleErrorResponse<TResponseModel>(IRestRequest request, IRestResponse<<ResponseModel<TResponseModel>> response)
And log the actual response at // (other error handling here), if available:
if (response.Data != null && !string.IsNullOrWhiteSpace(response.Data.ResultMessage))
{
resultMessage = response.Data.ResultMessage;
errorString += string.Format(", Service response: \"{0}\"", response.Data.ResultMessage);
}
RestSharp has added boolean property IRestResponse.IsSuccessful which covers your use case. I couldn't find any documentation referring to this property, but here's the line that defines the property's method.
Interesting to note is that RestSharp considers codes 200-299 to be successful, while CodeCaster considers codes 200-399 to be successful.
It should be enough to check for a success code, and throw or report the error if you get any other code apart from success. This usually means checking for HTTP Status 200 after every request. If you create a new resource, you should expect Status 201.
With most APIs/frameworks, it is very very unusual to see any other status code except these if nothing has gone wrong.
Related
Hi I am very new to Service Stack and am wondering how can I return a different http status code.
The ones that I need be able to return are:
204 - processed but no content
400 - bad request
404 - not found
422 - for validation issues
500 - internal server error
Can Anyone help?
If your Service doesn't return a response, e.g. has a void method or returns null, ServiceStack automatically returns a 204 No Content response status.
This behavior can be reverted to an empty 200 OK response with:
SetConfig(new HostConfig {
Return204NoContentForEmptyResponse = false
});
Request DTOs returning empty responses should implement the IReturnVoid marker interface
Custom Error Codes
All other status codes are error status codes which are documented in ServiceStack's Error Handling docs.
E.g. It's generally recommended to return the ideal C# Exception and have ServiceStack automatically return the ideal HTTP Error code.
By Default C# Exceptions inheriting from:
ArgumentException, SerializationException or FormatException returns a 400 BadRequest
NotImplementedException or NotSupportedException returns a 405 MethodNotAllowed
FileNotFoundException is return as 404 NotFound
AuthenticationException is returned as 401 Unauthorized
UnauthorizedAccessException is returned as 403 Forbidden
OptimisticConcurrencyException is returned as 409 Conflict
All Other normal C# Exceptions are returned as 500 InternalServerError
So any Exceptions inheriting ArgumentException which includes most of the Fluent Validation Exceptions will automatically return the preferred 400 BadRequest.
Other ways to customize HTTP Error Statuses include:
Custom mapping of C# Exceptions to HTTP Error Status
You can change what HTTP Error Status is returned for different Exception Types by configuring them with:
SetConfig(new HostConfig {
MapExceptionToStatusCode = {
{ typeof(CustomUnprocessableEntityException), 422 },
{ typeof(CustomerNotFoundException), 404 },
}
});
Implementing IHasStatusCode
In addition to customizing the HTTP Response Body of C# Exceptions with
IResponseStatusConvertible,
you can also customize the HTTP Status Code by implementing IHasStatusCode:
public class Custom401Exception : Exception, IHasStatusCode
{
public int StatusCode => 401;
}
Returning a HttpError
If you want even finer grained control of your HTTP errors you can either throw or return an HttpError letting you customize the Http Headers and Status Code and HTTP Response body to get exactly what you want on the wire:
public object Get(User request)
{
throw HttpError.NotFound($"User {request.Name} does not exist");
}
The above returns a 404 NotFound StatusCode on the wire and is a short-hand for:
new HttpError(HttpStatusCode.NotFound, $"User {request.Name} does not exist");
HttpError with a Custom Response DTO
The HttpError can also be used to return a more structured Error Response with:
var responseDto = new ErrorResponse {
ResponseStatus = new ResponseStatus {
ErrorCode = typeof(ArgumentException).Name,
Message = "Invalid Request",
Errors = new List<ResponseError> {
new ResponseError {
ErrorCode = "NotEmpty",
FieldName = "Company",
Message = "'Company' should not be empty."
}
}
}
};
throw new HttpError(HttpStatusCode.BadRequest, "ArgumentException") {
Response = responseDto,
};
The Stackoverflow API, if the request is not successful, how to read the response please?
using (HttpClient Client = new HttpClient(handler))
{
string response = string.Empty;
response = Client.GetStringAsync(apiUri.Uri).Result;
return JsonConvert.DeserializeObject<Questions<Question>>(response);
}
When an error occurs:
{"error_id":502,"error_message":"too many requests from this IP, more requests available in 82216 seconds","error_name":"throttle_violation"}
An error InnerException = {"Response status code does not indicate success: 400 (Bad Request)."}
The following paragraph is from Call a Web API From a .NET Client (C#)
HttpClient does not throw an exception when the HTTP response contains an error code. Instead, the IsSuccessStatusCode property is false if the status is an error code. If you prefer to treat HTTP error codes as exceptions, call HttpResponseMessage.EnsureSuccessStatusCode on the response object. EnsureSuccessStatusCode throws an exception if the status code falls outside the range 200–299. Note that HttpClient can throw exceptions for other reasons — for example, if the request times out.
In your case the code could be something like the following.
var response = await client.GetAsync(apiUri.Uri);
if (!response.IsSuccessStatusCode)
{
var text = await response.Content.ReadAsStringAsync();
// Log error
return null; // throw etc.
}
else
{
var text = await response.Content.ReadAsStringAsync();
var o = JsonConvert.DeserializeObject<Questions<Question>>(o);
return o;
}
(You can move var text = ... outside of if if you wish.)
Status code can be examined instead of calling IsSuccessStatusCode, if needed.
if (response.StatusCode != HttpStatusCode.OK) // 200
{
throw new Exception(); / return null et.c
}
I'm trying to return status code 404 with a JSON response, like such:
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static dynamic Save(int Id)
{
HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.NotFound;
return new
{
message = $"Couldn't find object with Id: {id}"
};
}
But I keep getting HTML error page for 404 error instead of the JSON response. I've tried various of manipulating the Response with Flush, Clear, Write, SuppressContent, CompleteRequest (Not in order), but whenever I return 404 it still picks up the html error page.
Any ideas on how I can return a status code other than 200 OK (Since it's not ok, it's an error) and a JSON response?
I know I can throw an exception, but I'd prefer not to since it doesn't work with customErrors mode="On"
This is an older Website project in ASP.Net, and it seems most solutions in ASP MVC doesn't work.
Usually when you get he HTML error page that is IIS taking over the handling of the not found error.
You can usually bypass/disable this by telling the response to skip IIS custom errors.
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static dynamic Save(int id) {
//...
//if we get this far return not found.
return NotFound($"Couldn't find object with Id: {id}");
}
private static object NotFound(string message) {
var statusCode = (int)System.Net.HttpStatusCode.NotFound;
var response = HttpContext.Current.Response;
response.StatusCode = statusCode;
response.TrySkipIisCustomErrors = true; //<--
return new {
message = message
};
}
So, my main question is in the title. How is it possible to avoid the browser to throw Http Statuscode 406 once an invalid method is supplied?
I've tried by default to allow all incoming methods by using [AcceptVerbs("GET", "POST", "PUT", "DELETE")] and after that to filter out the actual allowed method by using this method:
private bool CheckAllowedMethod(HttpMethod allowed, HttpMethod given)
{
if (allowed == given)
{
return true;
}
throw new InvalidMethodException("This method is not available over " + Request.Method.Method);
}
Even though this works it isn't very neat. The behaviour I want to avoid when using [HttpPost], or any equivalent of those, is that the browser throws a Http Statuscode 406 and literaly prints nothing to the site even though I want to display a JSON string at all times.
So, is this possible any easier or do I have to use my current method?
Full code:
[AcceptVerbs("GET", "POST", "PUT", "DELETE")]
[Route("api/Auth/Login/{apikey}")]
public HttpResponseMessage GenerateLoginCode(string apikey = "") {
HttpResponseMessage response = CreateResponse();
try {
CheckAllowedMethod(HttpMethod.Post, Request.Method);
ChangeContent(response, JSONString.Create(Apikey.Login(apikey)));
} catch (Exception ex) {
ChangeContent(response, Error.Create(ex));
}
return response;
}
private HttpResponseMessage CreateResponse() {
return Request.CreateResponse(HttpStatusCode.OK);
}
private void ChangeContent(HttpResponseMessage res, string data) {
res.Content = new StringContent(data, System.Text.Encoding.UTF8, "application/json");
}
private bool CheckAllowedMethod(HttpMethod allowed, HttpMethod given) {
if (allowed == given) {
return true;
}
throw new InvalidMethodException("This method is not available over " + Request.Method.Method);
}
I would not do this via accepting all methods and filtering manually, but rather with a middleware that catches the error response and rewrites it.
I dug quite deeply into error handling in Web API 2 earlier this year, and expanded on my findings in this blog post. If you do something similar to that, you could handle the exception from a disallowed method in a special catch clause in the middleware and write whatever you want to the response.
I would like to return a json errormessage but at the moment in fiddler I cannot see this in the json panel:
string error = "An error just happened";
JsonResult jsonResult = new JsonResult
{
Data = error,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
response = Request.CreateResponse(HttpStatusCode.BadRequest, jsonResult.Data);
how to do this?
A few points:
If all you're looking to do is return an error response containing a simple error message, Web API provides a CreateErrorResponse method for that. So you can simply do:
return Request.CreateErrorResponse(HttpStatusCode.BadRequest,
"An error just happened");
This will result in the following HTTP response (other headers omitted for brevity):
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 36
{"Message":"An error just happened"}
If you want to return a custom object instead, then you use Request.CreateResponse like you were doing, but don't use the MVC JsonResult. Instead, just pass your object directly to CreateResponse:
var myError = new
{
Data = "An error just happened",
OtherDetails = "foo bar baz"
};
return Request.CreateResponse(HttpStatusCode.BadRequest, myError);
Now, say you are doing this but you're not getting JSON back from the server. It is important to realize that Web API normally uses content type negotiation to determine what format to use when sending the response back. That means, it looks at the Accept header that was sent by the client with the request. If the Accept header contains application/xml, for example, then Web API will return XML. If the header contains application/json then it will return JSON. So, you should check that your client is sending the correct Accept header.
That said, there are ways to force Web API to always return data in a specific format if that is what you really want. You can do this at the method level by using a different overload of CreateResponse which also specifies the content type:
return Request.CreateResponse(HttpStatusCode.BadRequest, myError,
new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
Alternatively, you can remove the XML formatter from the configuration altogether in your WebApiConfig file:
config.Formatters.Remove(config.Formatters.XmlFormatter);
This will force Web API to always use JSON regardless of what the client asks for.
you can return JSON like below,
return Request.CreateResponse<ResponseApiModel>(HttpStatusCode.BadRequest, response);
I recommend to use IHttpActionResult on your method return type instead HttpResponseMessage, if your api's method return type is IHttpActionResult. you can return like;
return Content(HttpStatusCode.InternalServerError, response);
you can check also that link about best practice of error returning Especially #Daniel Little's answer is really useful.
I know the answer added to late but maybe stand someone in good stead.
JsonResult is a MVC concept. It does not work in Web API. One way to explicitly return json content is to use the class I created in this answer https://stackoverflow.com/a/20504951/6819
Add
config.Formatters.Remove(config.Formatters.XmlFormatter);
line in your WebApiConfig file
I think this will help you or others.
define your custom ApiController
public abstract class ApiController : System.Web.Http.ApiController
{
protected internal virtual BadRequestErrorMessageResult BadRequest(object message)
{
return new BadRequestErrorMessageResult(message);
}
}
define custom message result class
public class BadRequestErrorMessageResult : IHttpActionResult
{
private readonly object _message;
public BadRequestErrorMessageResult(object message)
{
_message = message;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent<object>(_message, new JsonMediaTypeFormatter(), "application/json")
};
return Task.FromResult(response);
}
}
use your custom response method
// GET api/<controller>
[Authorize]
[HttpGet]
public IHttpActionResult Test()
{
return BadRequest(new
{
Succeeded = false,
Message = "An error occurred during processing, please contact the administrator!"
});
}
response data with custom header error status code
{"Succeeded":false,"Message":"An error occurred during processing, please contact the administrator!"}
You can directly set the status code of the current HTTP response through Response property
Response.StatusCod = (int)HttpStatusCode.BadRequest;
return Json(HttpStatusCode.BadRequest);