简体   繁体   English

ASP.NET Core 相当于 ASP.NET MVC 5 的 HttpException

[英]ASP.NET Core equivalent of ASP.NET MVC 5's HttpException

In ASP.NET MVC 5 you could throw a HttpException with a HTTP code and this would set the response like so:在 ASP.NET MVC 5 中,您可以使用 HTTP 代码抛出HttpException ,这将设置响应如下:

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

HttpException does not exist in ASP.NET Core. ASP.NET Core 中不存在HttpException What is the equivalent code?什么是等效代码?

I implemented my own HttpException and supporting middleware which catches all HttpException 's and turns them into the corresponding error response.我实现了我自己的HttpException和支持中间件,它捕获所有HttpException并将它们转换为相应的错误响应。 A short extract can be seen below.下面可以看到一个简短的摘录。 You can also use the Boxed.AspNetCore Nuget package.您还可以使用Boxed.AspNetCore Nuget 包。

Usage Example in Startup.cs 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.异常比仅仅从方法返回错误要慢。

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.与 @davidfowl 简短交谈后,似乎 ASP.NET 5 没有“神奇地”转向响应消息的HttpExceptionHttpResponseException这样的概念。

What you can do, is hook into the ASP.NET 5 pipeline via MiddleWare , and create one that handles the exceptions for you.您可以做的是通过 MiddleWare 连接到 ASP.NET 5 管道,并创建一个为您处理异常的管道

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:这是他们的错误处理程序中间件的源代码中的一个示例,它会将响应状态代码设置为 500,以防管道进一步发生异常:

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 :你需要用StartUp.cs注册它:

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

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.更新:从 .NET Core RC 2 开始,Http 前缀被删除。 It is now:就是现在:

return new StatusCodeResult(400);

The Microsoft.AspNet.Mvc.Controller base class exposes a HttpBadRequest(string) overload which takes an error message to return to the client. Microsoft.AspNet.Mvc.Controller基类公开HttpBadRequest(string)重载,该重载将错误消息返回给客户端。 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.最终,我的鼻子说从控制器操作中调用的任何私有方法都应该完全了解 http 上下文并返回IActionResult ,或者执行一些其他小任务,与它位于 http 管道内的事实完全隔离。 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.当然,这是我个人的意见,但是执行某些业务逻辑的类不应该返回 HTTP 状态代码,而应该抛出自己的异常,这些异常可以在控制器/操作级别捕获和转换。

There is no equivalent in ASP.NET Core itself. ASP.NET Core 本身没有等价物。 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. Opw.HttpExceptions.AspNetCore NuGet 包正是这样做的。

Middleware and extensions for returning exceptions over HTTP, eg as ASP.NET Core Problem Details.用于通过 HTTP 返回异常的中间件和扩展,例如作为 ASP.NET Core 问题详细信息。 Problem Details are a machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807 .问题详细信息是一种机器可读格式,用于指定基于https://tools.ietf.org/html/rfc7807 的HTTP API 响应中的错误。 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 4xx
  • 400 BadRequestException 400 错误请求异常
  • 400 InvalidModelException 400 无效模型异常
  • 400 ValidationErrorException<T> 400 验证错误异常<T>
  • 400 InvalidFileException 400 无效文件异常
  • 401 UnauthorizedException 401 未授权异常
  • 403 ForbiddenException 403 禁止异常
  • 404 NotFoundException 404 NotFoundException
  • 404 NotFoundException<T> 404 NotFoundException<T>
  • 409 ConflictException 409 冲突异常
  • 409 ProtectedException 409 受保护的异常
  • 415 UnsupportedMediaTypeException 415 不支持的媒体类型异常
5xx 5xx
  • 500 InternalServerErrorException 500 内部服务器错误异常
  • 500 DbErrorException 500 数据库错误异常
  • 500 SerializationErrorException 500 序列化错误异常
  • 503 ServiceUnavailableException 503 服务不可用异常

Starting from ASP.NET Core 3 you can use ActionResult to return HTTP status code:从 ASP.NET Core 3 开始,您可以使用ActionResult返回 HTTP 状态代码:

[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更多细节在这里: https : //docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1

Here is an extended version of @muhammad-rehan-saeed answer.这是@muhammad-rehan-saeed 答案的扩展版本。 It logs exceptions conditionaly and disables http cache.它有条件地记录异常并禁用 http 缓存。
If you use this and UseDeveloperExceptionPage, you should call UseDeveloperExceptionPage before this.如果你使用这个和 UseDeveloperExceptionPage,你应该之前调用 UseDeveloperExceptionPage。

Startup.cs:启动.cs:

app.UseMiddleware<HttpExceptionMiddleware>();

HttpExceptionMiddleware.cs 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 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).自动记录每个“正常”异常(例如 404)。
    • disabled in dev mode (when app.UseDeveloperExceptionPage is called)在开发模式下禁用(当 app.UseDeveloperExceptionPage 被调用时)
    • cannot catch only specific exceptions不能只捕获特定的异常
  • Opw.HttpExceptions.AspNetCore: logs exception when everything works fine Opw.HttpExceptions.AspNetCore:当一切正常时记录异常

See also ASP.NET Core Web API exception handling另请参阅ASP.NET Core Web API 异常处理

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM