Simon Simon - 8 days ago 6
C# Question

web API and MVC exception handling

We are currently re-developing our web forms system into web API and MVC (this is new technology for us)
So far, all seems to be ok, however we are struggling to send back errors from Web API application to the MVC application. We realise that we need to capture any exceptions and these are transformed into HTTP responses

Web API Product controller looks like this:

public HttpResponseMessage GetProducts()
{
BAProduct c = new BAProduct();
var d = c.GetProducts();
if (d == null)
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "This is a custom error message");
else
return Request.CreateResponse(HttpStatusCode.OK, d);
}


The MVC application will call the web API by the following code:-

public T Get<T>()

using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(Config.API_BaseSite);

client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("api/myapplicaton/products").Result;
response.EnsureSuccessStatusCode();
T res = response.Content.ReadAsAsync<T>().Result;
return (T)res;
}
}


What we are trying to achieve is when an HTTP error is received from the web API within the MVC application, the user is either redirected to a custom error page, or display the custom error message within the current view (depending on the severity of the error).
The issue that we are having is that:-


  1. How to we access the custom error message that we have sent back? ( from the sample code this would be "This is a custom error message", We have been through every attribute within res and cannot see this message)

  2. Depending on the status code how do we capture this and redirect users to individual error pages, i.e. 404 page, 500 page and display the custom response message that was sent back.
    we have been down the global.asax route

    protected void Application_Error(object sender, EventArgs e)
    {
    Exception exception = Server.GetLastError();
    Response.Clear();
    HttpException httpException = exception as HttpException;



however our httpExecption is always NULL

We have searched etc, and as of yet, cannot find anything appropriate,
hopefully someone can point us in the right direction.

Answer

The reason why your httpException instance is null is because the response.EnsureSuccessStatusCode(); method doesn't thrown an HttpException which is what you are attempting to cast it to. It is throwing an HttpRequestException which is different but has no easy way of getting more details (such as the status code for example).

As an alternative to calling this method you could test the IsSuccessStatusCode boolean property and throw an HttpException yourself:

public T Get()
{
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(Config.API_BaseSite);

        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        HttpResponseMessage response = client.GetAsync("api/myapplicaton/products").Result;
        if (!response.IsSuccessStatusCode)
        {
            string responseBody = response.Content.ReadAsStringAync().Result;
            throw new HttpException((int)response.StatusCode, responseBody);
        }

        T res = response.Content.ReadAsAsync<T>().Result;
        return (T)res;
    }
}

This HttpException could now be caught in your Application_Error and depending on the status code proceed with the handling:

protected void Application_Error()
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();

    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "Http500";
    routeData.Values["exception"] = exception;

    Response.StatusCode = 500;
    Response.TrySkipIisCustomErrors = true;

    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 403:
                routeData.Values["action"] = "Http403";
                break;
            case 404:
                routeData.Values["action"] = "Http404";
                break;

            // TODO: Add other cases if you want to handle
            // different status codes from your Web API
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}

In this example I assume that you have an ErrorsController with the respective actions (Http500, Http403, Http404, ...). The respective action will be invoked depending on the status code and you may return different views.


UPDATE:

You might want to capture additional artifacts of the HTTP request such as the reason phrase so that you display it in your error page. In this case you could simply write your own exception that will contain the information you need:

public class ApiException : Exception
{
    public HttpStatusCode StatusCode { get; set; }
    public string Reason { get; set; }
    public string ResponseBody { get; set; }
}

that you could throw:

if (!response.IsSuccessStatusCode)
{
    throw new ApiException    
    {
        StatusCode = response.StatusCode,
        Reason = response.ReasonPhrase,
        ResponseBody = response.Content.ReadAsStringAync().Result,
    };
}

and then work with this custom exception in your Application_Error.

Comments