Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 659
Simon Avatar asked Dec 29 '13 09:12

Simon


People also ask

How do you handle exceptions in Web API?

You can customize how Web API handles exceptions by writing an exception filter. An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception.

How exceptions are handled in MVC?

Another way to handle controller level exceptions is by overriding the OnException() method in the controller class. This method handles all your unhandled errors with error code 500. It allows you to log an exception and redirect to the specific view. It does not require to enable the <customErrors> config in web.

How Web API is different from MVC?

Asp.Net Web API VS Asp.Net MVC Asp.Net MVC is used to create web applications that return both views and data but Asp.Net Web API is used to create full-blown HTTP services with an easy and simple way that returns only data, not view.

Which is better MVC or Web API?

The Web API returns the data in various formats, such as JSON, XML and other format based on the accept header of the request. But the MVC returns the data in the JSON format by using JSONResult. The Web API supports content negotiation, self hosting. All these are not supported by the MVC.


1 Answers

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.

like image 199
Darin Dimitrov Avatar answered Nov 06 '22 05:11

Darin Dimitrov