ASP.NET Core equivalent of ASP.NET MVC 5's HttpException

46,805

Solution 1

I implemented my own HttpException and supporting middleware which catches all HttpException's and turns them into the corresponding error response. A short extract can be seen below. You can also use the Boxed.AspNetCore Nuget package.

Usage Example in Startup.cs

public void Configure(IApplicationBuilder application)
{
    application.UseIISPlatformHandler();

    application.UseStatusCodePagesWithReExecute("/error/{0}");
    application.UseHttpException();

    application.UseMvc();
}

Extension Method

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseHttpException(this IApplicationBuilder application)
    {
        return application.UseMiddleware<HttpExceptionMiddleware>();
    }
}

Middleware

internal class HttpExceptionMiddleware
{
    private readonly RequestDelegate next;

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

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await this.next.Invoke(context);
        }
        catch (HttpException httpException)
        {
            context.Response.StatusCode = httpException.StatusCode;
            var responseFeature = context.Features.Get<IHttpResponseFeature>();
            responseFeature.ReasonPhrase = httpException.Message;
        }
    }
}

HttpException

public class HttpException : Exception
{
    private readonly int httpStatusCode;

    public HttpException(int httpStatusCode)
    {
        this.httpStatusCode = httpStatusCode;
    }

    public HttpException(HttpStatusCode httpStatusCode)
    {
        this.httpStatusCode = (int)httpStatusCode;
    }

    public HttpException(int httpStatusCode, string message) : base(message)
    {
        this.httpStatusCode = httpStatusCode;
    }

    public HttpException(HttpStatusCode httpStatusCode, string message) : base(message)
    {
        this.httpStatusCode = (int)httpStatusCode;
    }

    public HttpException(int httpStatusCode, string message, Exception inner) : base(message, inner)
    {
        this.httpStatusCode = httpStatusCode;
    }

    public HttpException(HttpStatusCode httpStatusCode, string message, Exception inner) : base(message, inner)
    {
        this.httpStatusCode = (int)httpStatusCode;
    }

    public int StatusCode { get { return this.httpStatusCode; } }
}

In the long term, I would advise against using exceptions for returning errors. Exceptions are slower than just returning an error from a method.

Solution 2

After a brief chat with @davidfowl, it seems that ASP.NET 5 has no such notion of HttpException or HttpResponseException that "magically" turn to response messages.

What you can do, is hook into the ASP.NET 5 pipeline via MiddleWare, and create one that handles the exceptions for you.

Here is an example from the source code of their error handler middleware which will set the response status code to 500 in case of an exception further up the pipeline:

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ErrorHandlerOptions _options;
    private readonly ILogger _logger;

    public ErrorHandlerMiddleware(RequestDelegate next, 
                                  ILoggerFactory loggerFactory,
                                  ErrorHandlerOptions options)
    {
        _next = next;
        _options = options;
        _logger = loggerFactory.CreateLogger<ErrorHandlerMiddleware>();
        if (_options.ErrorHandler == null)
        {
            _options.ErrorHandler = _next;
        }
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError("An unhandled exception has occurred: " + ex.Message, ex);

            if (context.Response.HasStarted)
            {
                _logger.LogWarning("The response has already started, 
                                    the error handler will not be executed.");
                throw;
            }

            PathString originalPath = context.Request.Path;
            if (_options.ErrorHandlingPath.HasValue)
            {
                context.Request.Path = _options.ErrorHandlingPath;
            }
            try
            {
                var errorHandlerFeature = new ErrorHandlerFeature()
                {
                    Error = ex,
                };
                context.SetFeature<IErrorHandlerFeature>(errorHandlerFeature);
                context.Response.StatusCode = 500;
                context.Response.Headers.Clear();

                await _options.ErrorHandler(context);
                return;
            }
            catch (Exception ex2)
            {
                _logger.LogError("An exception was thrown attempting
                                  to execute the error handler.", ex2);
            }
            finally
            {
                context.Request.Path = originalPath;
            }

            throw; // Re-throw the original if we couldn't handle it
        }
    }
}

And you need to register it with StartUp.cs:

public class Startup
{
    public void Configure(IApplicationBuilder app, 
                          IHostingEnvironment env, 
                          ILoggerFactory loggerfactory)
    {
       app.UseMiddleWare<ExceptionHandlerMiddleware>();
    }
}

Solution 3

Alternatively, if you just want to return an arbitrary status code and aren't concerned with the Exception-based approach, you can use

return new HttpStatusCodeResult(400);

Update: as of .NET Core RC 2, the Http prefix is dropped. It is now:

return new StatusCodeResult(400);

Solution 4

The Microsoft.AspNet.Mvc.Controller base class exposes a HttpBadRequest(string) overload which takes an error message to return to the client. So from within a controller action, you could call:

return HttpBadRequest("Bad Request.");

Ultimately my nose says any private methods called from within a controller action should either be fully http-context-aware and return an IActionResult, or perform some other small task completely isolated from the fact that it's inside of an http pipeline. Granted this is my personal opinion, but a class that performs some piece of business logic should not be returning HTTP status codes, and instead should be throwing its own exceptions which can be caught and translated at the controller/action level.

Solution 5

There is no equivalent in ASP.NET Core itself. As others have said, the way to implement this is with a middleware and your own exceptions.

The Opw.HttpExceptions.AspNetCore NuGet package does exactly this.

Middleware and extensions for returning exceptions over HTTP, e.g. as ASP.NET Core Problem Details. Problem Details are a machine-readable format for specifying errors in HTTP API responses based on https://www.rfc-editor.org/rfc/rfc7807. But you are not limited to returning exception results as Problem Details, but you can create your own mappers for your own custom formats.

It is configurable and well documented.

Here is the list of provided exceptions out of the box:

4xx

  • 400 BadRequestException
  • 400 InvalidModelException
  • 400 ValidationErrorException<T>
  • 400 InvalidFileException
  • 401 UnauthorizedException
  • 403 ForbiddenException
  • 404 NotFoundException
  • 404 NotFoundException<T>
  • 409 ConflictException
  • 409 ProtectedException
  • 415 UnsupportedMediaTypeException

5xx

  • 500 InternalServerErrorException
  • 500 DbErrorException
  • 500 SerializationErrorException
  • 503 ServiceUnavailableException
Share:
46,805

Related videos on Youtube

Muhammad Rehan Saeed
Author by

Muhammad Rehan Saeed

▶️ YouTube: https://youtube.com/MuhammadRehanSaeed 📰 Website/Blog: https://rehansaeed.com 🐦 Twitter: https://twitter.com/RehanSaeedUK 🐙 GitHub: https://github.com/RehanSaeed 👨🏻‍💼 LinkedIn: https://www.linkedin.com/in/muhammad-rehan-saeed 📚 StackOverflow: https://stackoverflow.com/users/1212017/muhammad-rehan-saeed 👁️‍🗨️ Twitch: https://www.twitch.tv/rehansaeeduk

Updated on July 05, 2022

Comments

  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed almost 2 years

    In ASP.NET MVC 5 you could throw a HttpException with a HTTP code and this would set the response like so:

    throw new HttpException((int)HttpStatusCode.BadRequest, "Bad Request.");
    

    HttpException does not exist in ASP.NET Core. What is the equivalent code?

  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed almost 9 years
    Is this functionality that is temporarily missing in the Beta or is this code we will have to write?
  • Yuval Itzchakov
    Yuval Itzchakov almost 9 years
    @Rehan This is what we'll have to write. They don't plan on bringing that functionality to MVC 6. Every hooking will be done via the middleware pipeline.
  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed almost 9 years
    Seems like they are omitting a pretty common piece of functionality. They should at least provide the middle-ware, even if they don't apply it by default.
  • Yuval Itzchakov
    Yuval Itzchakov almost 9 years
    @RehanSaeed I agree, that is very useful functionality and I use it often as well. The good thing ASP.NET 5 is open source, we can apply that functionality ourselves if we want.
  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed almost 9 years
    I'll add your code to ASP.NET MVC Boilerplate and enable it by default. Thanks for helping.
  • Yuval Itzchakov
    Yuval Itzchakov almost 9 years
    @davidfowl Which extension method?
  • CallMeLaNN
    CallMeLaNN over 8 years
    @YuvalItzchakov I use empty ASP.NET 5 Web API and realize nice 500 error page probably come from this diagnostic middleware. How to replace this middleware or turn off in production? Does the framework register it upfront before the Startup.Configure being called?
  • kspearrin
    kspearrin over 8 years
    What about adding a custom body to the response? Like a structured JSON error response model?
  • gentiane
    gentiane about 8 years
    Yes, directly in an action it is surely the best approach. But sometimes, the action call a private method that does a specific work (returning something else than an ActionResult), and sometimes you would like that this method can throw exceptions to produce a response (mainly errors like 400 bad request, 403 forbidden). In this case the exception is a good choice.
  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed about 8 years
    Agreed but some people may have old MVC 5 apps using HttpException. Porting is made much easier if the same concept is optionally made available.
  • lc.
    lc. about 8 years
    Sure, and I think there is actually a shim available. I came to this question looking for the "ASP.NET 5 way of doing things" though, and had to find it elsewhere - your question asks "what is the equivalent code" which is IMHO congruent to "what is the new way of doing this" - which is what I was after. So I thought I would add this answer for the next person.
  • StackOverflower
    StackOverflower almost 8 years
    Unfortunately message is not making it to the client. Body is returned as empty :( (Content-Length = 0)
  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed almost 8 years
    @StackOverflower Updated
  • StackOverflower
    StackOverflower almost 8 years
    Something else that worked for me was to use Response.WriteAsync to set the body. Thanks
  • Pedro Moreira
    Pedro Moreira over 7 years
    @kspearrin context.Result = new JsonResult(body);
  • Arsenius
    Arsenius about 7 years
    Error CS0246: The type or namespace name `ErrorHandlerOptions' could not be found. Are you missing an assembly reference? (CS0246) How to fix this?
  • Vasily Hall
    Vasily Hall about 7 years
    I was doing the Core and put in my middleware to catch 404s to redirect to 404 page, and came upon a problem where I wanted to throw this error in lieu of a 500. This answer put me on the right track and I am using return NotFound(); as the action's result when some server error occurs.
  • ToDevAndBeyond
    ToDevAndBeyond almost 6 years
    Like mentioned above, I replaced the catch block body with: context.Response.StatusCode = httpException.StatusCode; await context.Response.WriteAsync(httpException.Message);
  • arao6
    arao6 about 5 years
    Having freedom to write our own middleware is great, but basic things like throwing exceptions from a HTTP handling framework should be built-in.
  • Rosdi Kasim
    Rosdi Kasim over 4 years
    Wrapping up all exceptions and turning them into error response may not be a good idea. The exception won't bubble up to Azure Application Insights as exception and Application Insights won't be able to present you with a detailed stack trace for the errors.
  • PRMan
    PRMan almost 4 years
    I worked at a place that specifically forbid the new keyword in Controllers (presumably for DI testability reasons, even though we all know these work). So how would you do it then?
  • Jeremy
    Jeremy almost 4 years
    If you want to be subversive and not use the new keyword, I suppose (StatusCodeResult) Activator.CreateInstance(typeof(StatusCodeResult), 400); would do the trick. Or you can create a StatusCodeFactory class that has methods to return new ones.
  • Paul
    Paul about 2 years
    Unfortunately catch (HttpException httpException) doesn't catch exceptions thrown in user code. If I set a breakpoint in catch block, it never triggers. Exceptions make debugger stop on them with a message that they weren't caught in user code.
  • Paul
    Paul about 2 years
    I would like to return status code from a method, but my methods belong to SOAP, were generated from WSDL files and I have no chance to change anything on their result. Even result belongs to SOAP.