locked
HttpErrors not behaving as expected RRS feed

  • Question

  • User543691468 posted

    I'm having this in web.config

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

    and it works fine when I return a HttpNotFoundResult from a controller. However, when I enter an url that can not be resolved (controller not found or does not implement IController), I still get the default IIS 404 error page. After turning on and inspecting tracing for failed requests, I saw this:

    GENERAL_NOT_SEND_CUSTOM_ERROR

    Reason="SETSTATUS_TRYSKIP"

    so apparently TrySkipIisCustomErrors is set somehow, but not on my part. I've searched all my app and uncommented every line where I do set this, just in case. I had a errorhandler global filter that only logged the errors and did not do anything about the Response, but I removed this as well just to be sure. I have no CustomErrors in the config.

    I don't understand this.

    Thursday, June 26, 2014 9:49 PM

Answers

  • User-1454326058 posted

    Hi Intrance,

    For your requirement, please refer to:

    For inner exception, please custom HandleErrorAttribute:

     

    public class MyHandleErrorAttribute : HandleErrorAttribute
        {
            public override void OnException(ExceptionContext filterContext)
            {
                //If you don't want to enable custom error in the web.config, please remove the second condition
                if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
                {
                    return;
                }
    
                if (new HttpException(null, filterContext.Exception).GetHttpCode() != 500)
                {
                    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 = View,
                        MasterName = Master,
                        ViewData = new ViewDataDictionary(model),
                        TempData = filterContext.Controller.TempData
                    };
                    //filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "Controller", "ErrorTest" }, { "Action", "Index2" } });
    
                }
    
                // log the error by using your own method
                //LogError(filterContext.Exception.Message, filterContext.Exception);
    
                filterContext.ExceptionHandled = true;
                filterContext.HttpContext.Response.Clear();
                filterContext.HttpContext.Response.StatusCode = new HttpException(null, filterContext.Exception).GetHttpCode();
    
                filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            }
        }
     public class FilterConfig
        {
            public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            {
                //filters.Add(new HandleErrorAttribute());
                filters.Add(new MyHandleErrorAttribute());
            }
        }

    For other exception (E.g. 404 error of incorrect controller name), please handle it in the Global:

     

    protected void Application_Error()
            {
    
                if (Context.IsCustomErrorEnabled)// You could remove this code
                    ShowError(Server.GetLastError());
    
            }
    
            private void ShowError(Exception exception)
            {
                var httpException = exception as HttpException ?? new HttpException(500, "Internal Server Error", exception);
    
                Response.Clear();
                var routeData = new RouteData();
                routeData.Values.Add("Controller", "ErrorTest");
                routeData.Values.Add("fromAppErrorEvent", true);
                routeData.Values.Add("ErrorMessage", httpException.Message);
                routeData.Values.Add("httpStatusCode", httpException.GetHttpCode());
                switch (httpException.GetHttpCode())
                {
                    case 403:
                        routeData.Values.Add("action", "HttpError403");                   
                        break;
                    case 404:
                        routeData.Values.Add("action", "HttpError404");                    
                        break;
                    case 500:
                        routeData.Values.Add("action", "HttpError500");                  
                        break;
                    default:
                        routeData.Values.Add("action", "GeneralError");                  
                        break;
                }
               
                Server.ClearError();
    
                IController controller = new MvcDemo2.Controllers.ErrorTestController();
                controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
                
            }
    public ActionResult HttpError404()
            {
            
               ViewBag.errormessage= this.RouteData.Values["ErrorMessage"];
               ViewBag.status = this.RouteData.Values["httpStatusCode"];
               Response.StatusCode=Convert.ToInt32(this.RouteData.Values["httpStatusCode"]);
               Response.TrySkipIisCustomErrors=true;
               return View();
            }
    
    
    <h2>HttpError404</h2>
    ErrorMessge: @ViewBag.errormessage
    <br />
    Status: @ViewBag.status

    Thanks

    Best Regards

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, July 1, 2014 4:13 AM

All replies

  • User543691468 posted

    Alright, after some source debugging I found something in HttpResponse::ReportRuntimeError (which is not called when I return a HttpNotFoundResult from a controller). Here TrySkipIisCustomErrors is set to true, but at the same time it is checking the customErrorSettings if it should redirect anywhere.

    And here we have the problem, CustomErrorsSection.GetSettings(_context, canThrow).CustomErrorsEnabled(Request) is returning false for some reason.

    Most of the information here is optimized away, and I can not step into CustomErrorsEnabled so I don't see what happens here. Is there anything wrong with my config? As stated, it works as expected when I return HttpNotFoundResult from a controller, so this feels very fishy...

    Friday, June 27, 2014 3:37 AM
  • User543691468 posted

    Forgive me Father, for I have crossposted: http://stackoverflow.com/questions/24465261/customerrors-vs-httperrors-a-significant-design-flaw

    Bacically, CustomErrorsSection is obviously not caring about HttpErrors, and because the ReportRuntimeError method sets TrySkipIssCustomErrors to true, we have lost the game and HttpErrors will not trigger if we have existingResponse set to Auto (which we have to if we want to handle some error pages manually and still having HttpErrors handle the rest)

    Saturday, June 28, 2014 6:35 AM
  • User-1454326058 posted

    Hi,

    However, when I enter an url that can not be resolved (controller not found or does not implement IController), I still get the default IIS 404 error page.

    In addition to add httpErrors in the system.webServer section, you also should add customErrors in the system.web section.

    Thanks

    Best Regards

    Sunday, June 29, 2014 10:35 PM
  • User543691468 posted

    But I don't want to use customErrors, because I dont'want to redirect, and I want to keep the true http status code.

    Sunday, June 29, 2014 11:10 PM
  • User-1454326058 posted

    Hi Intrance,

    For your requirement, please refer to:

    For inner exception, please custom HandleErrorAttribute:

     

    public class MyHandleErrorAttribute : HandleErrorAttribute
        {
            public override void OnException(ExceptionContext filterContext)
            {
                //If you don't want to enable custom error in the web.config, please remove the second condition
                if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
                {
                    return;
                }
    
                if (new HttpException(null, filterContext.Exception).GetHttpCode() != 500)
                {
                    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 = View,
                        MasterName = Master,
                        ViewData = new ViewDataDictionary(model),
                        TempData = filterContext.Controller.TempData
                    };
                    //filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "Controller", "ErrorTest" }, { "Action", "Index2" } });
    
                }
    
                // log the error by using your own method
                //LogError(filterContext.Exception.Message, filterContext.Exception);
    
                filterContext.ExceptionHandled = true;
                filterContext.HttpContext.Response.Clear();
                filterContext.HttpContext.Response.StatusCode = new HttpException(null, filterContext.Exception).GetHttpCode();
    
                filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            }
        }
     public class FilterConfig
        {
            public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            {
                //filters.Add(new HandleErrorAttribute());
                filters.Add(new MyHandleErrorAttribute());
            }
        }

    For other exception (E.g. 404 error of incorrect controller name), please handle it in the Global:

     

    protected void Application_Error()
            {
    
                if (Context.IsCustomErrorEnabled)// You could remove this code
                    ShowError(Server.GetLastError());
    
            }
    
            private void ShowError(Exception exception)
            {
                var httpException = exception as HttpException ?? new HttpException(500, "Internal Server Error", exception);
    
                Response.Clear();
                var routeData = new RouteData();
                routeData.Values.Add("Controller", "ErrorTest");
                routeData.Values.Add("fromAppErrorEvent", true);
                routeData.Values.Add("ErrorMessage", httpException.Message);
                routeData.Values.Add("httpStatusCode", httpException.GetHttpCode());
                switch (httpException.GetHttpCode())
                {
                    case 403:
                        routeData.Values.Add("action", "HttpError403");                   
                        break;
                    case 404:
                        routeData.Values.Add("action", "HttpError404");                    
                        break;
                    case 500:
                        routeData.Values.Add("action", "HttpError500");                  
                        break;
                    default:
                        routeData.Values.Add("action", "GeneralError");                  
                        break;
                }
               
                Server.ClearError();
    
                IController controller = new MvcDemo2.Controllers.ErrorTestController();
                controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
                
            }
    public ActionResult HttpError404()
            {
            
               ViewBag.errormessage= this.RouteData.Values["ErrorMessage"];
               ViewBag.status = this.RouteData.Values["httpStatusCode"];
               Response.StatusCode=Convert.ToInt32(this.RouteData.Values["httpStatusCode"]);
               Response.TrySkipIisCustomErrors=true;
               return View();
            }
    
    
    <h2>HttpError404</h2>
    ErrorMessge: @ViewBag.errormessage
    <br />
    Status: @ViewBag.status

    Thanks

    Best Regards

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, July 1, 2014 4:13 AM
  • User543691468 posted

    Thanks for your solution, works fine. Still interesting to note that to handle error pages in a good consistent way, we can not rely on either two of the interfaces provided by ASP.NET/IIS.

    Friday, July 11, 2014 11:05 PM