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

后端 未结 19 2761
爱一瞬间的悲伤
爱一瞬间的悲伤 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:32

    Try NotFoundMVC on nuget. It works , no setup.

    0 讨论(0)
  • 2020-11-21 10:34

    In MVC4 WebAPI 404 can be handle in the following way,

    COURSES APICONTROLLER

        // GET /api/courses/5
        public HttpResponseMessage<Courses> Get(int id)
        {
            HttpResponseMessage<Courses> resp = null;
    
            var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();
    
            resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);
    
            return resp;
        }
    

    HOME CONTROLLER

    public ActionResult Course(int id)
    {
        return View(id);
    }
    

    VIEW

    <div id="course"></div>
    <script type="text/javascript">
        var id = @Model;
        var course = $('#course');
        $.ajax({    
            url: '/api/courses/' + id,
            success: function (data) {
                course.text(data.Name);
            },
            statusCode: {
                404: function() 
                {
                    course.text('Course not available!');    
                }
            }
        });
    </script>
    

    GLOBAL

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
        routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
    

    RESULTS

    enter image description here

    0 讨论(0)
  • 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.

    <customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
        <error statusCode="404" redirect="404.aspx" />
        <error statusCode="500" redirect="~/MVCErrorPage" />
    </customErrors>
    

    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 {
    
        /// <summary>
        /// Enables support for CustomErrors ResponseRewrite mode in MVC.
        /// </summary>
        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<int, string> errorPaths = new Dictionary<int, string>();
    
                        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

      <system.web>
        <httpModules>
          <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
        </httpModules>
      </system.web>
    
      <!-- IIS7+ -->
      <system.webServer>
        <modules>
          <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
        </modules>
      </system.webServer>
    

    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());
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 10:37

    1) Make abstract Controller class.

    public abstract class MyController:Controller
    {
        public ActionResult NotFound()
        {
            Response.StatusCode = 404;
            return View("NotFound");
        }
    
        protected override void HandleUnknownAction(string actionName)
        {
            this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
        }
        protected override void OnAuthorization(AuthorizationContext filterContext) { }
    }  
    

    2) Make inheritence from this abstract class in your all controllers

    public class HomeController : MyController
    {}  
    

    3) And add a view named "NotFound" in you View-Shared folder.

    0 讨论(0)
  • 2020-11-21 10:38

    I've investigated A LOT on how to properly manage 404s in MVC (specifically MVC3), and this, IMHO is the best solution I've come up with:

    In global.asax:

    public class MvcApplication : HttpApplication
    {
        protected void Application_EndRequest()
        {
            if (Context.Response.StatusCode == 404)
            {
                Response.Clear();
    
                var rd = new RouteData();
                rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
                rd.Values["controller"] = "Errors";
                rd.Values["action"] = "NotFound";
    
                IController c = new ErrorsController();
                c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
            }
        }
    }
    

    ErrorsController:

    public sealed class ErrorsController : Controller
    {
        public ActionResult NotFound()
        {
            ActionResult result;
    
            object model = Request.Url.PathAndQuery;
    
            if (!Request.IsAjaxRequest())
                result = View(model);
            else
                result = PartialView("_NotFound", model);
    
            return result;
        }
    }
    

    (Optional)

    Explanation:

    AFAIK, there are 6 different cases that an ASP.NET MVC3 apps can generate 404s.

    (Automatically generated by ASP.NET Framework:)

    (1) An URL does not find a match in the route table.

    (Automatically generated by ASP.NET MVC Framework:)

    (2) An URL finds a match in the route table, but specifies a non-existent controller.

    (3) An URL finds a match in the route table, but specifies a non-existant action.

    (Manually generated:)

    (4) An action returns an HttpNotFoundResult by using the method HttpNotFound().

    (5) An action throws an HttpException with the status code 404.

    (6) An actions manually modifies the Response.StatusCode property to 404.

    Normally, you want to accomplish 3 objectives:

    (1) Show a custom 404 error page to the user.

    (2) Maintain the 404 status code on the client response (specially important for SEO).

    (3) Send the response directly, without involving a 302 redirection.

    There are various ways to try to accomplish this:

    (1)

    <system.web>
        <customErrors mode="On">
            <error statusCode="404" redirect="~/Errors/NotFound"/>
        </customError>
    </system.web>
    

    Problems with this solution:

    1. Does not comply with objective (1) in cases (1), (4), (6).
    2. Does not comply with objective (2) automatically. It must be programmed manually.
    3. Does not comply with objective (3).

    (2)

    <system.webServer>
        <httpErrors errorMode="Custom">
            <remove statusCode="404"/>
            <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
        </httpErrors>
    </system.webServer>
    

    Problems with this solution:

    1. Only works on IIS 7+.
    2. Does not comply with objective (1) in cases (2), (3), (5).
    3. Does not comply with objective (2) automatically. It must be programmed manually.

    (3)

    <system.webServer>
        <httpErrors errorMode="Custom" existingResponse="Replace">
            <remove statusCode="404"/>
            <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
        </httpErrors>
    </system.webServer>
    

    Problems with this solution:

    1. Only works on IIS 7+.
    2. Does not comply with objective (2) automatically. It must be programmed manually.
    3. It obscures application level http exceptions. E.g. can't use customErrors section, System.Web.Mvc.HandleErrorAttribute, etc. It can't only show generic error pages.

    (4)

    <system.web>
        <customErrors mode="On">
            <error statusCode="404" redirect="~/Errors/NotFound"/>
        </customError>
    </system.web>
    

    and

    <system.webServer>
        <httpErrors errorMode="Custom">
            <remove statusCode="404"/>
            <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
        </httpErrors>
    </system.webServer>
    

    Problems with this solution:

    1. Only works on IIS 7+.
    2. Does not comply with objective (2) automatically. It must be programmed manually.
    3. Does not comply with objective (3) in cases (2), (3), (5).

    People that have troubled with this before even tried to create their own libraries (see http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html). But the previous solution seems to cover all the cases without the complexity of using an external library.

    0 讨论(0)
  • 2020-11-21 10:39

    ASP.NET MVC doesn't support custom 404 pages very well. Custom controller factory, catch-all route, base controller class with HandleUnknownAction - argh!

    IIS custom error pages are better alternative so far:

    web.config

    <system.webServer>
      <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404" />
        <error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
      </httpErrors>
    </system.webServer>
    

    ErrorController

    public class ErrorController : Controller
    {
        public ActionResult PageNotFound()
        {
            Response.StatusCode = 404;
            return View();
        }
    }
    

    Sample Project

    • Test404 on GitHub
    • Live website
    0 讨论(0)
提交回复
热议问题