Blake Rivell
Blake Rivell

Reputation: 13875

Error handling (Sending ex.Message to the client)

I have an ASP.NET Core 1.0 Web API application and trying to figure out how to pass the exception message to the client if a function that my controller is calling errors out.

I have tried so many things, but nothing implements IActionResult.

I don't understand why this isn't a common thing that people need. If there truthfully is no solution can someone tell me why?

I do see some documentation out there using HttpResponseException(HttpResponseMessage), but in order to use this, I have to install the compat shim. Is there a new way of doing these things in Core 1.0?

Here is something I have been trying with the shim but it isn't working:

// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
    Customer c = _customersService.GetCustomerById(id);
    if (c == null)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            Content = new StringContent("Customer doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
            StatusCode = HttpStatusCode.NotFound

        };

        throw new HttpResponseException(response);

        //return NotFound();
    }
    return new ObjectResult(c);
}

When the HttpResponseException is thrown, I look on the client and can't find the message I am sending anything in the content.

Upvotes: 47

Views: 75247

Answers (7)

Hameed Syed
Hameed Syed

Reputation: 4235

Late to the party but refining the answer .

Define your error response class with minimum below attributes

using Microsoft.AspNetCore.Http;

    public class ErrorResponse
        {
            private readonly RequestDelegate next;
            public ErrorResponse(RequestDelegate next)
            {
                this.next = next;
            }
    
            public async Task Invoke(HttpContext context )
            {
                try
                {
                    await next(context);
                }
                catch (Exception ex)
                {
                    await HandleExceptionAsync(context, ex);
                }
            }
    
            private static Task HandleExceptionAsync(HttpContext context, Exception ex)
            {
                var code = HttpStatusCode.InternalServerError;         
                string result = string.Empty;
                object data = new object();
                if (ex is ForbiddenException)
                {
                    code = HttpStatusCode.Forbidden;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.Forbidden(ex.Message), data));
                }
                else if(ex is BadRequestException){
                    code = HttpStatusCode.BadRequest;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.BadRequest(ex.Message), data));
                }
                else if (ex is NotFoundException)
                {
                    code = HttpStatusCode.NotFound;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.NotFound(ex.Message), data));
                }
                else if (ex is UnauthorizedException)
                {
                    code = HttpStatusCode.Unauthorized;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.Unauthorized(ex.Message), data));
                }
                else
                {
                    result = JsonConvert.SerializeObject(new Response<object>(Status.InternalServerError(ex.Message), data));
                }
    
    
                context.Response.ContentType = "application/json";
                context.Response.StatusCode = (int)code;
                return context.Response.WriteAsync(result);
            }
        }

Next use this class as middleware in startup.cs class

app.UseHttpsRedirection();
app.UseMiddleware(typeof(ErrorResponse)); 

Now each request and response will go through this class,if an error occurs then error code will be set to true with error code. A sample response like below

data: {}
status: {
code: 404
error: true
message: "No employee data found"
type: "Not Found"
}

Upvotes: 1

Onsongo Moseti
Onsongo Moseti

Reputation: 449

I had the same problem and after some research, I found out I could use HttpClient to call my API and read the response easily. HttpClient does not throw any error when the HTTP response contains an error code, but it sets the IsSuccessStatusCode property to false.

This is my function using the HttpClient. I call this from my controller.

  public static async Task<HttpResponseMessage> HttpClientPost(string header, string postdata, string url)
        {
            string uri = apiUrl + url;
            using (var client = new HttpClient())
            {
                //client.BaseAddress = new Uri(uri);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", header);
                HttpResponseMessage response = await client.PostAsync(uri, new StringContent(postdata));

                return response;
            }
        }

This is my controller code, where I call the function and read the response and determine whether I have an error or not and respond accordingly. Note that I am checking the IsSuccessStatusCode.

                HttpResponseMessage response;
                string url = $"Setup/AddDonor";
                var postdata = JsonConvert.SerializeObject(donor);

                response = await ApiHandler.HttpClientPost(HttpContext.Session.GetString(tokenName), postdata, url);
                //var headers = response.Headers.Concat(response.Content.Headers);
                var responseBody = await response.Content.ReadAsStringAsync();

                if (response.IsSuccessStatusCode)
                {
                    tnxresult = JsonConvert.DeserializeObject<TnxResult>(AppFunctions.CleanResponse(responseBody));

                    return Json(new
                    {
                        ok = true,
                        message = tnxresult.Message,
                        statusCode = tnxresult.StatusCode
                    });
                }
                else
                {
                  ApiError rs = JsonConvert.DeserializeObject<ApiError>(AppFunctions.CleanResponse(responseBody));

                    return Json(new
                    {
                        ok = false,
                        message = rs.Message,
                        statusCode = rs.StatusCode
                    });

                }

My API returns error messages in JSON. If the call is successful, I am packing the response in JSON too.

The crucial line of code is this one...

var responseBody = await response.Content.ReadAsStringAsync();

It serializes the HTTP content to a string as an asynchronous operation.

After that I can convert my JSON string to an object and access the error/success message and the Status Code too.

Upvotes: 0

Paul Hiles
Paul Hiles

Reputation: 9778

Rather than raising and catching an exception, how about you simplify your action to:

// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
    var customer = _customersService.GetCustomerById(id);

    if (customer == null)
    {
        return NotFound("Customer doesn't exist");        
    }

    return Ok(customer);
}

I wrote a blog post with some more options such as returning a JSON object instead of text.

Upvotes: 5

Erich Brunner
Erich Brunner

Reputation: 612

Maybe that is helpful. You can return just object and sent for example a BadRequest (HTTP CODE: 400) with your custom object as actual parameter (I just used an interpolated string here) but you can put in anything.

In your client side you can catch that error situation for example with an AJAX error handler.

// GET: api/TruckFahrerGeoData
[HttpGet]
public object GetTruckFahrerGeoData()
{

    var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();

    var geodataItems = _context.TruckFahrerGeoData;

    foreach (var truckFahrerGeoData in geodataItems)
    {
        GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);

        if (geoTelemetryData == null)
        {
            return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
        }
        TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
        {
            Speed = geoTelemetryData.Speed,
            Accuracy = geoTelemetryData.Accuracy,
            TruckAppId = geoTelemetryData.Activity.TruckAppId,
            TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
            ClId = geoTelemetryData.Activity.ClId,
            TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
            TaskId = geoTelemetryData.Activity.TaskId,
            TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
        };

        truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
    }


    return truckFahrerGeoDataItems;
}

Or an even more cleaner way with IActionResult like that way:

// GET: api/TruckFahrerGeoData
[HttpGet]
public IActionResult GetTruckFahrerGeoData()
{

    var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();

    var geodataItems = _context.TruckFahrerGeoData;

    foreach (var truckFahrerGeoData in geodataItems)
    {
        GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);

        if (geoTelemetryData == null)
        {
            return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
        }
        TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
        {
            Speed = geoTelemetryData.Speed,
            Accuracy = geoTelemetryData.Accuracy,
            TruckAppId = geoTelemetryData.Activity.TruckAppId,
            TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
            ClId = geoTelemetryData.Activity.ClId,
            TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
            TaskId = geoTelemetryData.Activity.TaskId,
            TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
        };

        truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
    }


    return Ok(truckFahrerGeoDataItems);
}

Upvotes: 1

shabbirh
shabbirh

Reputation: 674

Yes it is possible to change the status code to whatever you need:

In your CustomExceptionFilterAttribute.cs file modify the code as follows:

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        context.Result = new ContentResult
        {
            Content = $"Error: {exception.Message}",
            ContentType = "text/plain",
            // change to whatever status code you want to send out
            StatusCode = (int?)HttpStatusCode.BadRequest 
        };
    }
}

That's pretty much it.

If you have custom exceptions, then you can also check for them when grabbing the thrown exception from the context. Following on from that you can then send out different HTTP Status Codes depdending on what has happened in your code.

Hope that helps.

Upvotes: 10

Set
Set

Reputation: 49769

Here is an simple error DTO class

public class ErrorDto
{
    public int Code {get;set;}
    public string Message { get; set; }

    // other fields

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

And then using the ExceptionHandler middleware:

            app.UseExceptionHandler(errorApp =>
            {
                errorApp.Run(async context =>
                {
                    context.Response.StatusCode = 500; // or another Status accordingly to Exception Type
                    context.Response.ContentType = "application/json";

                    var error = context.Features.Get<IExceptionHandlerFeature>();
                    if (error != null)
                    {
                        var ex = error.Error;

                        await context.Response.WriteAsync(new ErrorDto()
                        {
                            Code = <your custom code based on Exception Type>,
                            Message = ex.Message // or your custom message
                            // other custom data
                        }.ToString(), Encoding.UTF8);
                    }
                });
            });

Upvotes: 68

cvraman
cvraman

Reputation: 1697

You can create a custom Exception Filter like below

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        context.Result = new JsonResult(exception.Message);
    }
}

Then apply the above attribute to your controller.

[Route("api/[controller]")]
[CustomExceptionFilter]
public class ValuesController : Controller
{
     // GET: api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        throw new Exception("Suckers");
        return new string[] { "value1", "value2" };
    }
}

Upvotes: 6

Related Questions