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

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

    My solution, in case someone finds it useful.

    In Web.config:

    <system.web>
        <customErrors mode="On" defaultRedirect="Error" >
          <error statusCode="404" redirect="~/Error/PageNotFound"/>
        </customErrors>
        ...
    </system.web>
    

    In Controllers/ErrorController.cs:

    public class ErrorController : Controller
    {
        public ActionResult PageNotFound()
        {
            if(Request.IsAjaxRequest()) {
                Response.StatusCode = (int)HttpStatusCode.NotFound;
                return Content("Not Found", "text/plain");
            }
    
            return View();
        }
    }
    

    Add a PageNotFound.cshtml in the Shared folder, and that's it.

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

    The only way I could get @cottsak's method to work for invalid controllers was to modify the existing route request in the CustomControllerFactory, like so:

    public class CustomControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            try
            {
                if (controllerType == null)
                    return base.GetControllerInstance(requestContext, controllerType); 
                else
                    return ObjectFactory.GetInstance(controllerType) as Controller;
            }
            catch (HttpException ex)
            {
                if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
                {
                    requestContext.RouteData.Values["controller"] = "Error";
                    requestContext.RouteData.Values["action"] = "Http404";
                    requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);
    
                    return ObjectFactory.GetInstance<ErrorController>();
                }
                else
                    throw ex;
            }
        }
    }
    

    I should mention I'm using MVC 2.0.

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

    The code is taken from http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx and works in ASP.net MVC 1.0 as well

    Here's how I handle http exceptions:

    protected void Application_Error(object sender, EventArgs e)
    {
       Exception exception = Server.GetLastError();
       // Log the exception.
    
       ILogger logger = Container.Resolve<ILogger>();
       logger.Error(exception);
    
       Response.Clear();
    
       HttpException httpException = exception as HttpException;
    
       RouteData routeData = new RouteData();
       routeData.Values.Add("controller", "Error");
    
       if (httpException == null)
       {
           routeData.Values.Add("action", "Index");
       }
       else //It's an Http Exception, Let's handle it.
       {
           switch (httpException.GetHttpCode())
           {
              case 404:
                  // Page not found.
                  routeData.Values.Add("action", "HttpError404");
                  break;
              case 500:
                  // Server error.
                  routeData.Values.Add("action", "HttpError500");
                  break;
    
               // Here you can handle Views to other error codes.
               // I choose a General error template  
               default:
                  routeData.Values.Add("action", "General");
                  break;
          }
      }           
    
      // Pass exception details to the target error View.
      routeData.Values.Add("error", exception);
    
      // Clear the error on server.
      Server.ClearError();
    
      // Avoid IIS7 getting in the middle
      Response.TrySkipIisCustomErrors = true; 
    
      // Call target Controller and pass the routeData.
      IController errorController = new ErrorController();
      errorController.Execute(new RequestContext(    
           new HttpContextWrapper(Context), routeData));
    }
    
    0 讨论(0)
  • 2020-11-21 10:45

    I really like cottsaks solution and think its very clearly explained. my only addition was to alter step 2 as follows

    public abstract class MyController : Controller
    {
    
        #region Http404 handling
    
        protected override void HandleUnknownAction(string actionName)
        {
            //if controller is ErrorController dont 'nest' exceptions
            if(this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
        }
    
        public ActionResult InvokeHttp404(HttpContextBase httpContext)
        {
            IController errorController = ObjectFactory.GetInstance<ErrorController>();
            var errorRoute = new RouteData();
            errorRoute.Values.Add("controller", "Error");
            errorRoute.Values.Add("action", "Http404");
            errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
            errorController.Execute(new RequestContext(
                 httpContext, errorRoute));
    
            return new EmptyResult();
        }
    
        #endregion
    }
    

    Basically this stops urls containing invalid actions AND controllers from triggering the exception routine twice. eg for urls such as asdfsdf/dfgdfgd

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

    Here is another method using MVC tools which you can handle requests to bad controller names, bad route names, and any other criteria you see fit inside of an Action method. Personally, I prefer to avoid as many web.config settings as possible, because they do the 302 / 200 redirect and do not support ResponseRewrite (Server.Transfer) using Razor views. I'd prefer to return a 404 with a custom error page for SEO reasons.

    Some of this is new take on cottsak's technique above.

    This solution also uses minimal web.config settings favoring the MVC 3 Error Filters instead.

    Usage

    Just throw a HttpException from an action or custom ActionFilterAttribute.

    Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")
    

    Step 1

    Add the following setting to your web.config. This is required to use MVC's HandleErrorAttribute.

    <customErrors mode="On" redirectMode="ResponseRedirect" />
    

    Step 2

    Add a custom HandleHttpErrorAttribute similar to the MVC framework's HandleErrorAttribute, except for HTTP errors:

    <AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
    Public Class HandleHttpErrorAttribute
        Inherits FilterAttribute
        Implements IExceptionFilter
    
        Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"
    
        Private m_HttpCode As HttpStatusCode
        Private m_Master As String
        Private m_View As String
    
        Public Property HttpCode As HttpStatusCode
            Get
                If m_HttpCode = 0 Then
                    Return HttpStatusCode.NotFound
                End If
                Return m_HttpCode
            End Get
            Set(value As HttpStatusCode)
                m_HttpCode = value
            End Set
        End Property
    
        Public Property Master As String
            Get
                Return If(m_Master, String.Empty)
            End Get
            Set(value As String)
                m_Master = value
            End Set
        End Property
    
        Public Property View As String
            Get
                If String.IsNullOrEmpty(m_View) Then
                    Return String.Format(m_DefaultViewFormat, Me.HttpCode)
                End If
                Return m_View
            End Get
            Set(value As String)
                m_View = value
            End Set
        End Property
    
        Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
            If filterContext Is Nothing Then Throw New ArgumentException("filterContext")
    
            If filterContext.IsChildAction Then
                Return
            End If
    
            If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
                Return
            End If
    
            Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
            If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
                Return
            End If
    
            If ex.GetHttpCode <> Me.HttpCode Then
                Return
            End If
    
            Dim controllerName As String = filterContext.RouteData.Values("controller")
            Dim actionName As String = filterContext.RouteData.Values("action")
            Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)
    
            filterContext.Result = New ViewResult With {
                .ViewName = Me.View,
                .MasterName = Me.Master,
                .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
                .TempData = filterContext.Controller.TempData
            }
            filterContext.ExceptionHandled = True
            filterContext.HttpContext.Response.Clear()
            filterContext.HttpContext.Response.StatusCode = Me.HttpCode
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
        End Sub
    End Class
    

    Step 3

    Add Filters to the GlobalFilterCollection (GlobalFilters.Filters) in Global.asax. This example will route all InternalServerError (500) errors to the Error shared view (Views/Shared/Error.vbhtml). NotFound (404) errors will be sent to ErrorHttp404.vbhtml in the shared views as well. I've added a 401 error here to show you how this can be extended for additional HTTP error codes. Note that these must be shared views, and they all use the System.Web.Mvc.HandleErrorInfo object as a the model.

    filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
    filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
    filters.Add(New HandleErrorAttribute With {.View = "Error"})
    

    Step 4

    Create a base controller class and inherit from it in your controllers. This step allows us to handle unknown action names and raise the HTTP 404 error to our HandleHttpErrorAttribute.

    Public Class BaseController
        Inherits System.Web.Mvc.Controller
    
        Protected Overrides Sub HandleUnknownAction(actionName As String)
            Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
        End Sub
    
        Public Function Unknown() As ActionResult
            Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
            Return New EmptyResult
        End Function
    End Class
    

    Step 5

    Create a ControllerFactory override, and override it in your Global.asax file in Application_Start. This step allows us to raise the HTTP 404 exception when an invalid controller name has been specified.

    Public Class MyControllerFactory
        Inherits DefaultControllerFactory
    
        Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
            Try
                Return MyBase.GetControllerInstance(requestContext, controllerType)
            Catch ex As HttpException
                Return DependencyResolver.Current.GetService(Of BaseController)()
            End Try
        End Function
    End Class
    
    'In Global.asax.vb Application_Start:
    
    controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)
    

    Step 6

    Include a special route in your RoutTable.Routes for the BaseController Unknown action. This will help us raise a 404 in the case where a user accesses an unknown controller, or unknown action.

    'BaseController
    routes.MapRoute( _
        "Unknown", "BaseController/{action}/{id}", _
        New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
    )
    

    Summary

    This example demonstrated how one can use the MVC framework to return 404 Http Error Codes to the browser without a redirect using filter attributes and shared error views. It also demonstrates showing the same custom error page when invalid controller names and action names are specified.

    I'll add a screenshot of an invalid controller name, action name, and a custom 404 raised from the Home/TriggerNotFound action if I get enough votes to post one =). Fiddler returns a 404 message when I access the following URLs using this solution:

    /InvalidController
    /Home/InvalidRoute
    /InvalidController/InvalidRoute
    /Home/TriggerNotFound
    

    cottsak's post above and these articles were good references.

    • Problems using CustomErrors redirectMode=ResponseRewrite
    • Elmah + MVC HandleErrorAttribute
    0 讨论(0)
  • 2020-11-21 10:50

    My shortened solution that works with unhandled areas, controllers and actions:

    1. Create a view 404.cshtml.

    2. Create a base class for your controllers:

      public class Controller : System.Web.Mvc.Controller
      {
          protected override void HandleUnknownAction(string actionName)
          {
              Http404().ExecuteResult(ControllerContext);
          }
      
          protected virtual ViewResult Http404()
          {
              Response.StatusCode = (int)HttpStatusCode.NotFound;
              return View("404");
          }
      }
      
    3. Create a custom controller factory returning your base controller as a fallback:

      public class ControllerFactory : DefaultControllerFactory
      {
          protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
          {
              if (controllerType != null)
                  return base.GetControllerInstance(requestContext, controllerType);
      
              return new Controller();
          }
      }
      
    4. Add to Application_Start() the following line:

      ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
      
    0 讨论(0)
提交回复
热议问题