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

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

    
    

    Step 2

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

    
    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

提交回复
热议问题