【问题标题】:ASP.NET Core equivalent of ASP.NET MVC 5's HttpExceptionASP.NET Core 等效于 ASP.NET MVC 5 的 HttpException
【发布时间】:2015-06-25 15:01:58
【问题描述】:

在 ASP.NET MVC 5 中,您可以抛出带有 HTTP 代码的 HttpException,这将设置响应如下:

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

HttpException 在 ASP.NET Core 中不存在。什么是等效代码?

【问题讨论】:

    标签: c# .net asp.net-core asp.net-core-mvc httpexception


    【解决方案1】:

    我实现了自己的HttpException 并支持中间件,它可以捕获所有HttpException 并将它们转换为相应的错误响应。下面可以看到一个简短的摘录。您也可以使用Boxed.AspNetCore Nuget 包。

    Startup.cs 中的使用示例

    public void Configure(IApplicationBuilder application)
    {
        application.UseIISPlatformHandler();
    
        application.UseStatusCodePagesWithReExecute("/error/{0}");
        application.UseHttpException();
    
        application.UseMvc();
    }
    

    扩展方法

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

    中间件

    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; } }
    }
    

    从长远来看,我建议不要使用异常来返回错误。异常比仅从方法返回错误要慢。

    【讨论】:

    • 不幸的是消息没有发送到客户端。正文返回为空 :( (Content-Length = 0)
    • @StackOverflower 更新
    • 对我有用的其他方法是使用 Response.WriteAsync 设置正文。谢谢
    • 如上所述,我将 catch 块体替换为:context.Response.StatusCode = httpException.StatusCode; await context.Response.WriteAsync(httpException.Message);
    • 总结所有异常并将它们转换为错误响应可能不是一个好主意。异常不会作为异常冒泡到 Azure Application Insights,Application Insights 将无法为您提供错误的详细堆栈跟踪。
    【解决方案2】:

    在brief chat with @davidfowl 之后,ASP.NET 5 似乎没有“神奇地”转向响应消息的HttpException 或HttpResponseException 的概念。

    您可以做的是hook into the ASP.NET 5 pipeline via MiddleWare,并创建一个为您处理异常。

    这是来自他们的错误处理程序中间件的source code 的一个示例,它会将响应状态代码设置为 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
            }
        }
    }
    

    你需要用StartUp.cs注册它:

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

    【讨论】:

    • @Rehan 这就是我们必须写的。他们不打算将该功能引入 MVC 6。每个挂钩都将通过中间件管道完成。
    • @RehanSaeed 我同意,这是非常有用的功能,我也经常使用它。 ASP.NET 5 是开源的,好在我们可以根据需要自行应用该功能。
    • 我会将您的代码添加到ASP.NET MVC Boilerplate 并默认启用它。感谢您的帮助。
    • @davidfowl 哪种扩展方法?
    • @YuvalItzchakov 我使用空的 ASP.NET 5 Web API 并意识到不错的 500 错误页面可能来自这个诊断中间件。 如何替换此中间件或在生产中关闭? 框架是否在调用 Startup.Configure 之前预先注册它?
    【解决方案3】:

    或者,如果您只想返回任意状态代码并且不关心基于异常的方法,您可以使用

    return new HttpStatusCodeResult(400);
    

    更新:从 .NET Core RC 2 开始,Http 前缀被删除。现在是:

    return new StatusCodeResult(400);
    

    【讨论】:

    • 是的,直接在动作中肯定是最好的方法。但有时,该操作调用一个执行特定工作的私有方法(返回 ActionResult 以外的其他内容),有时您希望此方法可以抛出异常以产生响应(主要是错误,如 400 错误请求,403 禁止)。在这种情况下,例外是一个不错的选择。
    • 我在一个专门禁止 Controllers 中的 new 关键字的地方工作(可能是出于 DI 可测试性的原因,尽管我们都知道这些工作)。那你会怎么做呢?
    • 如果你想具有颠覆性并且不使用new 关键字,我想(StatusCodeResult) Activator.CreateInstance(typeof(StatusCodeResult), 400); 可以解决问题。或者,您可以创建一个具有返回新方法的 StatusCodeFactory 类。
    【解决方案4】:

    Microsoft.AspNet.Mvc.Controller 基类公开了一个 HttpBadRequest(string) 重载,该重载将错误消息返回给客户端。因此,在控制器操作中,您可以调用:

    return HttpBadRequest("Bad Request.");
    

    最后,我的鼻子说从控制器操作中调用的任何私有方法都应该完全支持 http-context-aware 并返回 IActionResult,或者执行一些其他小任务,完全与它位于 http 管道内部的事实隔离开来.当然这是我个人的看法,但是执行某些业务逻辑的类不应该返回 HTTP 状态代码,而应该抛出它自己的异常,这些异常可以在控制器/动作级别被捕获和翻译。

    【讨论】:

    • 同意,但有些人可能拥有使用 HttpException 的旧 MVC 5 应用程序。如果可以选择提供相同的概念,则移植会变得更加容易。
    • 当然,我认为实际上有可用的垫片。我来到这个问题寻找“ASP.NET 5 做事方式”,但不得不在其他地方找到它 - 你的问题是“什么是等效代码”,恕我直言,“什么是新的做事方式”这个”——这就是我所追求的。所以我想我会为下一个人添加这个答案。
    • 我正在做核心并放入我的中间件以捕获 404 以重定向到 404 页面,并遇到了一个问题,我想 throw 这个错误来代替500. 这个答案让我走上了正确的道路,当发生某些服务器错误时,我使用return NotFound(); 作为操作的结果。
    【解决方案5】:

    在 ASP.NET Core 本身中没有等价物。正如其他人所说,实现这一点的方法是使用中间件和您自己的异常。

    Opw.HttpExceptions.AspNetCore NuGet 包正是这样做的。

    用于通过 HTTP 返回异常的中间件和扩展,例如作为 ASP.NET Core 问题详细信息。问题详细信息是一种机器可读格式,用于指定基于 https://www.rfc-editor.org/rfc/rfc7807 的 HTTP API 响应中的错误。但您不仅可以将异常结果作为问题详细信息返回,还可以为自己的自定义格式创建自己的映射器。

    它是可配置的并且有据可查。

    以下是开箱即用提供的例外列表:

    4xx

    • 400 错误请求异常
    • 400 无效模型异常
    • 400 验证错误异常
    • 400 无效文件异常
    • 401 未授权异常
    • 403 禁止异常
    • 404 NotFoundException
    • 404 NotFoundException
    • 409 冲突异常
    • 409 受保护异常
    • 415 UnsupportedMediaTypeException

    5xx

    • 500 内部服务器错误异常
    • 500 DbErrorException
    • 500 序列化错误异常
    • 503 服务不可用异常

    【讨论】:

      【解决方案6】:

      这是@muhammad-rehan-saeed 答案的扩展版本。 它有条件地记录异常并禁用 http 缓存。
      如果你使用 this 和 UseDeveloperExceptionPage,你应该调用 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)
          {
          }
      }
      

      我用这段代码得到的结果比用:

      • 使用异常处理程序:
        • 自动记录每个“正常”异常(例如 404)。
        • 在开发模式下禁用(调用 app.UseDeveloperExceptionPage 时)
        • 不能只捕获特定异常
      • Opw.HttpExceptions.AspNetCore:在一切正常时记录异常

      另见ASP.NET Core Web API exception handling

      【讨论】:

        【解决方案7】:

        从 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);
        }
        

        更多详情在这里:https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1

        【讨论】:

          猜你喜欢
          • 2016-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-18
          • 1970-01-01
          相关资源
          最近更新 更多