Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle errors differently for (or distinguish between) API calls and MVC (views) calls in ASP.NET Core

In my applications based on ordinary MVC and WebApi I had two different error handling routes.

If an error occurred during WebApi call, I would intercept it (using standard web api options) and return json message with corresponding HTTP Status Code so that client app can handle it.

If the error happened in MVC, then I would use some standard handlers that would redirect user to some default error screen possibly based on status code.

Now in ASP.NET Core both are joined in the same framework, so if I just intercept and return JSON, then I risk showing json to a user, since it can be an action that returns a view. On the other hand if I use app.UseExceptionHandler then my API calls would get HTML from the error page that is unusable inside js.

What is the good way to provide separate error handling for this two cases? Or perhaps there is a better way to handle it altogether?

P.S. I would rather reuse the MVC exception handler that comes out of the box and only add the web api part.

like image 657
Ilya Chernomordik Avatar asked Oct 20 '16 08:10

Ilya Chernomordik


People also ask

How does ASP.NET handle Web API errors?

Using HttpError in ASP.NET Web API You can use the CreateErrorResponse extension method in your Web API controller method to return meaningful error codes and error messages. Note that the CreateErrorResponse method creates an HttpError object and then wraps it inside an HttpResponseMessage object.

What are the different ways in which we can handle run time errors in Web API application code?

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.

Which is the best way to handle errors in net?

You can handle default errors at the application level either by modifying your application's configuration or by adding an Application_Error handler in the Global. asax file of your application. You can handle default errors and HTTP errors by adding a customErrors section to the Web. config file.


2 Answers

There are many ways to achive your goal:

1- Using two different exception filter(i would go with this approach because your question is about mvc pipline)

Implementation:

// For api
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        // send error as json
    }
}


[ApiExceptionFilter]
public class ApiController : Controller{...}

// For mvc
public class MvcExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        // send view result
    }
}


[MvcExceptionFilter]
public class HomeController : Controller{...}

If you want to add filter globally, see Register filter for an area

2- Using UseWhen and UseExceptionHandler

         app.UseWhen(x => x.Request.Path.Value.StartsWith("/api"), builder =>
         {
             builder.UseExceptionHandler(new ExceptionHandlerOptions()
             {
                 ExceptionHandler = async (ctx) =>
                 {
                     var feature = ctx.Features.Get<IExceptionHandlerFeature>();
                     var error = feature?.Error;
                     // send json
                 }
             });
         });
        app.UseWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
        {
            builder.UseExceptionHandler("/Error");
        });`

3- Using UseExceptionHandler conditionally:

        app.UseExceptionHandler(new ExceptionHandlerOptions()
        {
            ExceptionHandler = async (ctx) =>
            {
                if (ctx.Request.Path.Value.StartsWith("/api"))
                {
                    var feature = ctx.Features.Get<IExceptionHandlerFeature>();
                    var error = feature?.Error;
                    // send json
                }
                else
                {
                    // redirect error page
                }
            }
        });
like image 197
adem caglin Avatar answered Oct 20 '22 00:10

adem caglin


I would recommend to write your custom middleware to handle exceptions as want. An example here:

public class ErrorMiddleware
{
    private readonly RequestDelegate next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        if (exception == null) return;

        var code = HttpStatusCode.InternalServerError;

        if (exception is MyNotFoundException) code = HttpStatusCode.NotFound;
        //here you can check what kind of exception it is

        //wite is proper for Web API, but here you can do what you want
        await WriteExceptionAsync(context, exception, code).ConfigureAwait(false);
    }

    private static async Task WriteExceptionAsync(HttpContext context, Exception exception, HttpStatusCode code)
    {
        var response = context.Response;
        response.ContentType = "application/json";
        response.StatusCode = (int)code;
        await response.WriteAsync(JsonConvert.SerializeObject(new 
        {
            error = new
            {
                message = exception.Message,
                exception = exception.GetType().Name
            }
        })).ConfigureAwait(false);
    }
}

And this is how you can register your middleware:

app.UseMiddleware(typeof(ErrorMiddleware));
like image 23
Dawid Rutkowski Avatar answered Oct 20 '22 00:10

Dawid Rutkowski