CustomErrors与HttpErrors - 一个重大的设计缺陷?

时间:2014-06-28 09:00:27

标签: asp.net asp.net-mvc iis

我们可能知道,例如, What is the difference between customErrors and httpErrors?CustomErrors是一种在Web应用程序中定义错误页面的旧方法,但这种方法存在一些问题 - 例如,如果您关心正确的http响应代码,因为CustomErrors的方法是重定向到错误页面而不是替换当前响应,这会通过http状态代码破坏通信的大部分语义完整性。

HttpErrors是自IIS 7.0以来可用的新功能,它在服务器级而不是应用程序级运行,并且更适合以有效的方式处理错误响应,例如通过使用当前响应而不是重定向。

但是,如果在我看来,ASP.NET中这些工件的基础架构如今看起来给我们带来了一些问题。

一个简单的配置示例

<httpErrors existingResponse="Auto" errorMode="Custom">
    <remove statusCode="404"/>
    <error statusCode="404" path="/Error/E404" responseMode="ExecuteURL" />
</httpErrors>

我们将 errorMode 设置为 Custom ,因为我们要测试错误处理本身,并将 existingResponse 设置为 Auto ,它将引入一个依赖于Response.TrySkipIisCustomErrors的分支:

  • True :现有的错误响应将通过此模块未经处理,考虑到它的语义含义,这是完全合理的。
  • 错误:如果HttpErrors模块具有匹配规则,则现有的错误响应将被替换为HttpErrors模块,这很有意义。

理想情况下允许我们自己处理一些错误,例如当 ../ product / id 中的产品不存在时,我们可以手动返回特定的404页面,其中包含有关缺失产品的信息,仍然让HttpErrors模块处理其余所有内容,例如 ../ products / namebutshouldbeid 或只是 ../ misspelledandunmatchableurl

但是,据我所知,这是行不通的。原因在于内部方法 System.Web.HttpResponse。ReportRuntimeError ,它将在运行时错误(例如未找到控制器/操作)上调用,并且我们有一个看起来像这样的部分:

// always try to disable IIS custom errors when we send an error
if (_wr != null) {
    _wr.TrySkipIisCustomErrors = true;
}

if (!localExecute) {
code = HttpException.GetHttpCodeForException(e);

// Don't raise event for 404.  See VSWhidbey 124147.
if (code != 404) {
    WebBaseEvent.RaiseRuntimeError(e, this);
}

// This cannot use the HttpContext.IsCustomErrorEnabled property, since it must call
// GetSettings() with the canThrow parameter.
customErrorsSetting = CustomErrorsSection.GetSettings(_context, canThrow);
if (customErrorsSetting != null)
    useCustomErrors = customErrorsSetting.CustomErrorsEnabled(Request);
else
    useCustomErrors = true;
}

在第一次调试时我发现 useCustomErrors 设置为false,我无法理解为什么因为我知道我有一个有效的 HttpError 配置,因为它有效我从控制器返回HttpNotFoundResult

然后我意识到这不是 HttpErrors ,而是旧的 CustomErrors 。并且 CustomErrors 显然不了解 HttpErrors

&#34;错误&#34;

所以发生的事情是Response.TrySkipIisCustomErrors设置为true,并且由于没有定义 CustomErrors ,它会返回详细的404响应。此时我们希望 HttpErrors 启动,但它不会因为 TrySkipIisCustomErrors 现在设置为true。

我们也无法使用 CustomErrors ,因为这会让我们回到令人反感的错误重定向问题。

返回HttpNotFoundResult的原因是因为它不会触发运行时错误,只返回一个404结果 HttpErrors 将按预期拦截,只要我们避免将Response.TrySkipIisCustomErrors设为true。

应如何处理/解决此问题?

我认为默认情况下不允许 System.Web.HttpResponse。ReportRuntimeError Response.TrySkipIisCustomErrors设置为true,因为我们有另一个依赖于此的错误处理模块。因此,如果我们有任何HttpErrors配置,或者它处理< em> HttpErrors 配置以及 CustomErrors 配置。

或者我错过了一些秘密魔法来解决这个问题?

2 个答案:

答案 0 :(得分:2)

就像你一样,我认为ASP.NET不应该设置TrySkipIisCustomErrors或者可以添加一个选项,以便我们可以避免它。

作为一种解决方法,我构建了一个能够将查询传输到ASP.NET MVC控制器的ASPX页面。

<强> ErrorHandler.aspx.cs

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ErrorHandler.aspx.cs" Inherits="Guillaume.ErrorHandler" %>

代码

protected void Page_Load(object sender, EventArgs e)
{
  //Get status code
  var queryStatusCode = Request.QueryString.Get("code");
  int statusCode;
  if (!int.TryParse(queryStatusCode, out statusCode))
  {
    var lastError = Server.GetLastError();
    HttpException ex = lastError as HttpException;
    statusCode = ex == null ? 500 : ex.GetHttpCode();
  }
  Response.StatusCode = statusCode;

  // Execute a route
  RouteData routeData = new RouteData();
  string controllerName = Request.QueryString.Get("controller") ?? "Errors";
  routeData.Values.Add("controller", controllerName);
  routeData.Values.Add("action", Request.QueryString.Get("action") ?? "Index");

  var requestContext = new RequestContext(new HttpContextWrapper(Context), routeData);
  IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(requestContext, controllerName);
  controller.Execute(requestContext);
}

在web.config中的用法

<configuration>
    <system.web>
        <customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="/Content/ErrorHandler.aspx">
            <error statusCode="404" redirect="/Content/ErrorHandler.aspx?code=404&amp;controller=Errors&amp;action=NotFound" />
        </customErrors>
    </system.web>
</configuration>

常规浏览器请求的行为是正确的:当出现错误并返回错误代码时,会显示错误页面。 它在AJAX / Web API请求上也是正确的:不返回错误页面。我们在JSON或XML中得到了一个可解析的错误。

如果您希望将非ASP.NET错误重定向到自定义错误,则可以添加httpErrors部分。

答案 1 :(得分:2)

我一直在努力解决这个问题,我认为唯一有效的解决方案是here {Starain chen对@Alex在论坛上发布的同一问题的回答here .asp.net):

(我稍微修改过代码)

代码

创建自定义句柄错误属性

public class CustomHandleErrorAttribute : HandleErrorAttribute {
    public override void OnException (ExceptionContext filterContext) {
        if (filterContext.ExceptionHandled) {
            return;
        }

        var httpException = new HttpException(null, filterContext.Exception);
        var httpStatusCode = httpException.GetHttpCode();

        switch ((HttpStatusCode) httpStatusCode) {
            case HttpStatusCode.Forbidden:
            case HttpStatusCode.NotFound:
            case HttpStatusCode.InternalServerError:
                break;

            default:
                return;
        }

        if (!ExceptionType.IsInstanceOfType(filterContext.Exception)) {
            return;
        }

        // if the request is AJAX return JSON else view.
        if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest") {
            filterContext.Result = new JsonResult {
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Data = new {
                    error = true,
                    message = filterContext.Exception.Message
                }
            };
        }
        else {
            var controllerName = (String) filterContext.RouteData.Values["controller"];
            var actionName = (String) filterContext.RouteData.Values["action"];
            var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);

            filterContext.Result = new ViewResult {
                ViewName = String.Format("~/Views/Hata/{0}.cshtml", httpStatusCode),
                ViewData = new ViewDataDictionary(model),
                TempData = filterContext.Controller.TempData
            };
        }

        // TODO: Log the error by using your own method

        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = httpStatusCode;
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }
}

App_Start/FilterConfig.cs

中使用此自定义句柄错误属性
public class FilterConfig {
    public static void RegisterGlobalFilters (GlobalFilterCollection filters) {
        filters.Add(new CustomHandleErrorAttribute());
    }
}

处理Global.asax

中的其余例外情况
protected void Application_Error () {
    var exception = Server.GetLastError();
    var httpException = exception as HttpException ?? new HttpException((Int32) HttpStatusCode.InternalServerError, "Internal Server Error", exception);
    var httpStatusCode = httpException.GetHttpCode();

    Response.Clear();

    var routeData = new RouteData();

    routeData.Values.Add("Controller", "Error");
    routeData.Values.Add("fromAppErrorEvent", true);
    routeData.Values.Add("ErrorMessage", httpException.Message);
    routeData.Values.Add("HttpStatusCode", httpStatusCode);

    switch ((HttpStatusCode) httpStatusCode) {
            case HttpStatusCode.Forbidden:
            case HttpStatusCode.NotFound:
            case HttpStatusCode.InternalServerError:
                routeData.Values.Add("action", httpStatusCode.ToString());
                break;

            default:
                routeData.Values.Add("action", "General");
                break;
        }

    Server.ClearError();

    IController controller = new Controllers.ErrorController();

    // TODO: Log the error if you like

    controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}

创建ErrorController

[AllowAnonymous]
public class ErrorController : Controller {
    protected override void OnActionExecuting (ActionExecutingContext filterContext) {
        base.OnActionExecuting(filterContext);

        var errorMessage = RouteData.Values["ErrorMessage"];
        var httpStatusCode = RouteData.Values["HttpStatusCode"];

        if (errorMessage != null) {
            ViewBag.ErrorMessage = (String) errorMessage;
        }

        if (httpStatusCode != null) {
            ViewBag.HttpStatusCode = Response.StatusCode = (Int32) httpStatusCode;
        }

        Response.TrySkipIisCustomErrors = true;
    }

    [ActionName("403")]
    public ActionResult Error403 () {
        return View();
    }

    [ActionName("404")]
    public ActionResult Error404 () {
        return View();
    }

    [ActionName("500")]
    public ActionResult Error500 () {
        return View();
    }

    public ActionResult General () {
        return View();
    }
}

创建视图

ErrorController中的操作创建视图。 (403.cshtml404.cshtml500.cshtmlGeneral.cshtml

为什么我认为这是唯一有效的解决方案?

  1. 它处理ASP.NET MVC和IIS级错误(假设IIS7 +和集成管道)
  2. 返回有效的http状态代码。 (不是302或200)
  3. 如果我直接导​​航到错误页面,则返回200 OK:如果我导航到200 OK,我希望获得/error/404
  4. 我可以调整错误页面内容。 (使用ViewBag.ErrorMessage
  5. 如果客户端发出错误的AJAX请求并且期望json数据(在控制器的操作中),他/她将获得带有适当状态代码的json数据。