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

C#.Netasp.net Coreasp.net Core-MvcHttpexception

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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 - C#

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 - C#

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 - C#

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 - C#

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

Solution 6 - C#

Here is an extended version of @muhammad-rehan-saeed answer. It logs exceptions conditionaly and disables http cache.
If you use this and UseDeveloperExceptionPage, you should call UseDeveloperExceptionPage before this.

Startup.cs:

app.UseMiddleware<HttpExceptionMiddleware>();

HttpExceptionMiddleware.cs

/**
 * Error handling: throw HTTPException(s) in business logic, generate correct response with correct httpStatusCode + short error messages.
 * If the exception is a server error (status 5XX), this exception is logged.
 */
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 e)
        {
            var response = context.Response;
            if (response.HasStarted)
            {
                throw;
            }

            int statusCode = (int) e.StatusCode;
            if (statusCode >= 500 && statusCode <= 599)
            {
                logger.LogError(e, "Server exception");
            }
            response.Clear();
            response.StatusCode = statusCode;
            response.ContentType = "application/json; charset=utf-8";
            response.Headers[HeaderNames.CacheControl] = "no-cache";
            response.Headers[HeaderNames.Pragma] = "no-cache";
            response.Headers[HeaderNames.Expires] = "-1";
            response.Headers.Remove(HeaderNames.ETag);

            var bodyObj = new {
                Message = e.BaseMessage,
                Status = e.StatusCode.ToString()
            };
            var body = JsonSerializer.Serialize(bodyObj);
            await context.Response.WriteAsync(body);
		}
    }
}

HTTPException.cs

public class HttpException : Exception
{
    public HttpStatusCode StatusCode { get; }

    public HttpException(HttpStatusCode statusCode)
    {
        this.StatusCode = statusCode;
    }

    public HttpException(int httpStatusCode)
        : this((HttpStatusCode) httpStatusCode)
    {
    }

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

    public HttpException(int httpStatusCode, string message)
        : this((HttpStatusCode) httpStatusCode, message)
    {
    }

    public HttpException(HttpStatusCode statusCode, string message, Exception inner)
        : base(message, inner)
    {
    }

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

I had better results with this code than with :

  • UseExceptionHandler:
    • automatically logs every "normal" exceptions (ex 404).
    • disabled in dev mode (when app.UseDeveloperExceptionPage is called)
    • cannot catch only specific exceptions
  • Opw.HttpExceptions.AspNetCore: logs exception when everything works fine

See also https://stackoverflow.com/questions/38630076/asp-net-core-web-api-exception-handling

Solution 7 - C#

Starting from ASP.NET Core 3 you can use ActionResult to return HTTP status code:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ITEMS_TYPE> GetByItemId(int id)
{
...
    if (result == null)
    {
        return NotFound();
    }

    return Ok(result);
}

More details are here: https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMuhammad Rehan SaeedView Question on Stackoverflow
Solution 1 - C#Muhammad Rehan SaeedView Answer on Stackoverflow
Solution 2 - C#Yuval ItzchakovView Answer on Stackoverflow
Solution 3 - C#JeremyView Answer on Stackoverflow
Solution 4 - C#lc.View Answer on Stackoverflow
Solution 5 - C#0xcedView Answer on Stackoverflow
Solution 6 - C#KiruahxhView Answer on Stackoverflow
Solution 7 - C#yfranzView Answer on Stackoverflow