CustomErrors vs HttpErrors - A significant design flaw?

前端 未结 2 1191
孤城傲影
孤城傲影 2020-12-09 05:46

As we might know, from e.g. What is the difference between customErrors and httpErrors?, CustomErrors is a older way to define error pages in a web application but this appr

2条回答
  •  死守一世寂寞
    2020-12-09 06:30

    I've been struggling for several days to solve this problem and I think the only valid solution is the one posted here (An answer by Starain chen to the same question posted by @Alex on forums.asp.net):

    (I've slightly modified the code)

    The code

    Create a custom handle error attribute

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

    Use this custom handle error attribute in App_Start/FilterConfig.cs

    public class FilterConfig {
        public static void RegisterGlobalFilters (GlobalFilterCollection filters) {
            filters.Add(new CustomHandleErrorAttribute());
        }
    }
    

    Handle the remaining exceptions in 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));
    }
    

    Create an 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();
        }
    }
    

    Create views

    Create views for the actions in ErrorController. (403.cshtml, 404.cshtml, 500.cshtml and General.cshtml)

    Why do I think this is the only valid solution?

    1. It handles both ASP.NET MVC and IIS-level errors (Assuming IIS7+ and integrated pipeline)
    2. It returns valid http status codes. (Not 302 or 200)
    3. It returns 200 OK if I navigate to the error page directly: I'd like to get 200 OK if I navigate to /error/404.
    4. I can adjust the error page content. (Using ViewBag.ErrorMessage)
    5. If the client makes an erroneous AJAX request and expects json data (within the actions of controllers) he/she will be served with a json data with the appropriate status code.

提交回复
热议问题