11

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.

tereško
  • 58,060
  • 25
  • 98
  • 150
Simon
  • 1,412
  • 4
  • 20
  • 48

2 Answers2

15

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.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Hi, thank you for the quick response, It was very helpful, just tested and that has now allowed us to direct the user to the appropiate error page, however, how is it possible to get the custom error message within the createErrorResponse? is this only possible with update that you have added? if so, what is the point in adding this message in? thank you again – Simon Dec 29 '13 at 10:26
  • 1
    What happened when you tested it? Was the `Application_Error` reached? Was the `ErrorsController` executed? How did you define the respective error page? Did you even define an `ErrorsController`? You should not have any custom errors defined in your web.config because in this example all the handling is done in `Application_Error`. It will skip any custom error pages you might have defined there. – Darin Dimitrov Dec 29 '13 at 10:27
  • Hi, thank you for the quick response, It was very helpful, just tested and that has now allowed us to direct the user to the appropiate error page, however, how is it possible to get the custom error message within the createErrorResponse? is this only possible with update that you have added? if so, what is the point in adding this message in? thank you again – Simon Dec 29 '13 at 10:32
  • If you use `HttpException` then the custom error message will be available in the `ex.Message`. If you use the `ApiException` then it will be available in `ex.Reason`. The purpose of using an ApiException is that it allows you to capture the response body in addition to the error reason. In the HTTP protocol you can have status code (200, ...), reason (some description for the status code) and the response body. – Darin Dimitrov Dec 29 '13 at 10:33
  • Actually I made a mistake. It appears that the `CreateErrorResponse` method serializes the message as part of the response body and not as the reason. I will update my answer. You could use `throw new HttpException((int)response.StatusCode, response.Content.ReadAsStringAync().Result);` in order to be able to pass the response body as message of the exception. And then retrieve it with `ex.Message`. – Darin Dimitrov Dec 29 '13 at 10:39
  • Thank you, i understand the concept on the APIException (this could be a shared project between the API and MVC), and assume that ResponseBody could contain the full error details. What I'm still struggling with (I'm probably being silly) is how to get the error message ("This is a custom message") from the createErrorResponse within the MVC call. ie. if (!response.IsSuccessStatusCode) { throw new HttpException((int)response.StatusCode, response.ReasonPhrase); } Is there a way to get the custom message from the response object – Simon Dec 29 '13 at 10:50
  • Did you read my previous comment? Read it more carefully once again. You should use `throw new HttpException((int)response.StatusCode, response.Content.ReadAsStringAync().Result);`. And then inside the Application_Error use `ex.Message` to get the error message. Also the purpose of the `ApiException` is not at all to be shared between the Web API and the MVC application. This class is only used in the MVC application. Your Web API doesn't know anything about it. It is used to transport useful information about the error to the Application_Error event which otherwise wouldn't be available. – Darin Dimitrov Dec 29 '13 at 10:51
  • I understand. Thats brilliant, and working perfectly, as mentioned, we're new to MVC, so thank you for your time and patients. – Simon Dec 29 '13 at 11:18
  • ErrorsController implementation can be found at link ---- http://stackoverflow.com/a/5229581/1129978 – Himalaya Garg Nov 26 '16 at 09:10
0

An excellent post for solving above problem is here

It mainly has two main steps-

Step-1 : Deserializes the Response Content in HTTP Error Results e.g.

var httpErrorObject = response.Content.ReadAsStringAsync().Result;

// Create an anonymous object to use as the template for deserialization:
var anonymousErrorObject = 
    new { message = "", ModelState = new Dictionary<string, string[]>() };

// Deserialize:
var deserializedErrorObject = 
    JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);

Step-2: Add errors in ModelState Dictionaries Client-Side

foreach (var error in apiEx.Errors)
{
     ModelState.AddModelError("", error);
}

string error messages are added in apiEx.Errors using deserializedErrorObject.

See the link

Himalaya Garg
  • 1,525
  • 18
  • 23