How can I properly handle 404 in ASP.NET MVC?

后端 未结 19 2762
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-21 10:14

I am using RC2

Using URL Routing:

routes.MapRoute(
    \"Error\",
     \"{*url}\",
     new { controller = \"Errors\", action = \"N         


        
19条回答
  •  花落未央
    2020-11-21 10:36

    It seems to me that the standard CustomErrors configuration should just work however, due to the reliance on Server.Transfer it seems that the internal implementation of ResponseRewrite isn't compatible with MVC.

    This feels like a glaring functionality hole to me, so I decided to re-implement this feature using a HTTP module. The solution below allows you to handle any HTTP status code (including 404) by redirecting to any valid MVC route just as you would do normally.

    
        
        
    
    

    This has been tested on the following platforms;

    • MVC4 in Integrated Pipeline Mode (IIS Express 8)
    • MVC4 in Classic Mode (VS Development Server, Cassini)
    • MVC4 in Classic Mode (IIS6)

    Benefits

    • Generic solution which can be dropped into any MVC project
    • Enables support for traditional custom errors configuration
    • Works in both Integrated Pipeline and Classic modes

    The Solution

    namespace Foo.Bar.Modules {
    
        /// 
        /// Enables support for CustomErrors ResponseRewrite mode in MVC.
        /// 
        public class ErrorHandler : IHttpModule {
    
            private HttpContext HttpContext { get { return HttpContext.Current; } }
            private CustomErrorsSection CustomErrors { get; set; }
    
            public void Init(HttpApplication application) {
                System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
                CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");
    
                application.EndRequest += Application_EndRequest;
            }
    
            protected void Application_EndRequest(object sender, EventArgs e) {
    
                // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
                if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {
    
                    int statusCode = HttpContext.Response.StatusCode;
    
                    // if this request has thrown an exception then find the real status code
                    Exception exception = HttpContext.Error;
                    if (exception != null) {
                        // set default error status code for application exceptions
                        statusCode = (int)HttpStatusCode.InternalServerError;
                    }
    
                    HttpException httpException = exception as HttpException;
                    if (httpException != null) {
                        statusCode = httpException.GetHttpCode();
                    }
    
                    if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {
    
                        Dictionary errorPaths = new Dictionary();
    
                        foreach (CustomError error in CustomErrors.Errors) {
                            errorPaths.Add(error.StatusCode, error.Redirect);
                        }
    
                        // find a custom error path for this status code
                        if (errorPaths.Keys.Contains(statusCode)) {
                            string url = errorPaths[statusCode];
    
                            // avoid circular redirects
                            if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {
    
                                HttpContext.Response.Clear();
                                HttpContext.Response.TrySkipIisCustomErrors = true;
    
                                HttpContext.Server.ClearError();
    
                                // do the redirect here
                                if (HttpRuntime.UsingIntegratedPipeline) {
                                    HttpContext.Server.TransferRequest(url, true);
                                }
                                else {
                                    HttpContext.RewritePath(url, false);
    
                                    IHttpHandler httpHandler = new MvcHttpHandler();
                                    httpHandler.ProcessRequest(HttpContext);
                                }
    
                                // return the original status code to the client
                                // (this won't work in integrated pipleline mode)
                                HttpContext.Response.StatusCode = statusCode;
    
                            }
                        }
    
                    }
    
                }
    
            }
    
            public void Dispose() {
    
            }
    
    
        }
    
    }
    

    Usage

    Include this as the final HTTP module in your web.config

      
        
          
        
      
    
      
      
        
          
        
      
    

    For those of you paying attention you will notice that in Integrated Pipeline mode this will always respond with HTTP 200 due to the way Server.TransferRequest works. To return the proper error code I use the following error controller.

    public class ErrorController : Controller {
    
        public ErrorController() { }
    
        public ActionResult Index(int id) {
            // pass real error code to client
            HttpContext.Response.StatusCode = id;
            HttpContext.Response.TrySkipIisCustomErrors = true;
    
            return View("Errors/" + id.ToString());
        }
    
    }
    

提交回复
热议问题