ASP.NET MVC - 如何抛出类似于StackOverflow的404页面

12

我目前有一个继承自System.Web.Mvc.Controller的BaseController类。 在该类上,我使用了HandleError属性,将用户重定向到“500-糟糕,我们搞砸了”的页面。 目前这个功能正常工作。

这个功能可行

<HandleError()> _
Public Class BaseController : Inherits System.Web.Mvc.Controller

''# do stuff
End Class

我还将我的404页面设置为每个操作结果一个,这也能按预期工作。
这是有效的。
    Function Details(ByVal id As Integer) As ActionResult
        Dim user As Domain.User = UserService.GetUserByID(id)

        If Not user Is Nothing Then
            Dim userviewmodel As Domain.UserViewModel = New Domain.UserViewModel(user)
            Return View(userviewmodel)
        Else
            ''# Because of RESTful URL's, some people will want to "hunt around"
            ''# for other users by entering numbers into the address.  We need to
            ''# gracefully redirect them to a not found page if the user doesn't
            ''# exist.
            Response.StatusCode = CInt(HttpStatusCode.NotFound)
            Return View("NotFound")
        End If

    End Function

再次强调,这个方法非常实用。如果一个用户输入像http://example.com/user/999这样的内容(其中userID 999不存在),他们将看到相应的404页面,但URL不会改变(他们不会被重定向到错误页面)。

我无法实现此想法

这就是我的问题所在。如果用户输入http://example.com/asdf-,他们将被转到通用的404页面。 我想要的是保留URL不变(即不重定向到任何其他页面),但只需显示“NotFound”视图并将HttpStatusCode.NotFound推送给客户端。

例如,请访问https://stackoverflow.com/asdf,您将看到自定义的404页面,并且URL保持不变。

显然我错过了什么,但我想不出来。由于“asdf”实际上没有指向任何控制器,因此我的基础控制器类不起作用,因此我无法在那里使用“HandleError”过滤器。

感谢您提前的帮助。

注:我绝对不希望将用户重定向到404页面,我希望他们留在现有的URL,并且我希望MVC向用户推送404 VIEW。

编辑:

我也尝试了以下方法但不成功。

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.RouteExistingFiles = False
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
    routes.IgnoreRoute("Assets/{*pathInfo}")
    routes.IgnoreRoute("{*robotstxt}", New With {.robotstxt = "(.*/)?robots.txt(/.*)?"})

    routes.AddCombresRoute("Combres")

    ''# MapRoute allows for a dynamic UserDetails ID
    routes.MapRouteLowercase("UserProfile", _
        "Users/{id}/{slug}", _
        New With {.controller = "Users", .action = "Details", .slug = UrlParameter.Optional}, _
        New With {.id = "\d+"} _
    )


    ''# Default Catch All Valid Routes
    routes.MapRouteLowercase( _
        "Default", _
        "{controller}/{action}/{id}/{slug}", _
        New With {.controller = "Events", .action = "Index", .id = UrlParameter.Optional, .slug = UrlParameter.Optional} _
    )

    ''# Catch All InValid (NotFound) Routes
    routes.MapRoute( _
        "NotFound", _
        "{*url}", _
        New With {.controller = "Error", .action = "NotFound"})

End Sub

我的“NotFound”路由没有任何作用。

6个回答

9

我在我的另一个SO问题上找到了答案。非常感谢Anh-Kiet Ngo提供的解决方案。

protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();

    // A good location for any error logging, otherwise, do it inside of the error controller.

    Response.Clear();
    HttpException httpException = exception as HttpException;
    RouteData routeData = new RouteData();
    routeData.Values.Add("controller", "YourErrorController");

    if (httpException != null)
    {
        if (httpException.GetHttpCode() == 404)
        {
            routeData.Values.Add("action", "YourErrorAction");

            // We can pass the exception to the Action as well, something like
            // routeData.Values.Add("error", exception);

            // Clear the error, otherwise, we will always get the default error page.
            Server.ClearError();

            // Call the controller with the route
            IController errorController = new ApplicationName.Controllers.YourErrorController();
            errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
        }
    }
}

4
这是一个好的解决方案,但在调用 errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); 之前,需要添加这行代码 Response.StatusCode = 404; 如果没有添加这行代码,无论为用户呈现了什么内容,页面响应仍然是200。 - Paul
IController.Execute在MVC 4中是受保护的,因此我只需要从我的控制器中公开它并进行包装即可: public void Exec(RequestContext requestContext) { this.Execute(requestContext); } - Cameron Taggart

3

如果错误在WebForms页面中,而重定向是MVC页面,则ResponseRewrite有时会出现问题,反之亦然。 - Keith
@Keith,你能详细解释一下吗?我遇到了这些情况,想要尝试理解为什么它只有在某些时候才有效... - Raul Vejar
@RaulVejar 不是很确定,这是一篇旧帖子(我认为问题出在MVC2上),我们只是通过为MVC和WebForms使用不同的自定义错误来避免它。 - Keith
@Keith,不想说,但MVC4仍然存在问题。我们最终不得不在global asax上手动处理错误并完全关闭自定义错误。 - Raul Vejar

1
我通过简单地返回在基控制器中定义的动作来使它工作,而不是重定向到它。
[HandleError]
public ActionResult NotFound()
{
    Response.StatusCode = 404;
    Response.TrySkipIisCustomErrors = true;     
    return View("PageNotFound", SearchUtilities.GetPageNotFoundModel(HttpContext.Request.RawUrl));          
}

“SearchUtilities.GetPageNotFoundModel”这一部分是通过将URL输入到我们的搜索引擎中来生成建议。

在任何继承基类的操作方法中,我可以简单地调用这个:

return NotFound();

每当我捕获到无效的参数时,我都会这样做。而 catch-all 路由也是如此。


这样做是可以的,但现在我能够在不需要 NotFound 操作的情况下完成它。这样会使事情更加清晰。 - Chase Florell
我明白了,这很吸引人。你如何处理匹配路由但传递无效参数的情况,以避免抛出404错误? - Faust
1
我有一个自定义的HandleNotFoundException,可以从有效的控制器中抛出。它将加载适当的共享视图Shared/NotFound.cshtml,但仍保持URI不变。throw HandleNotFoundException - Chase Florell
我的上面的回答已经过时了...我需要用我的解决方案来更新它。 - Chase Florell

1

路由是模式匹配。你的未找到路由无法工作,因为你错误的URL的模式与先前的路由匹配。

所以:

''# Default Catch All Valid Routes
routes.MapRouteLowercase( _
    "Default", _
    "{controller}/{action}/{id}/{slug}", _
    New With {.controller = "Events", .action = "Index", .id = UrlParameter.Optional, .slug = UrlParameter.Optional} _
)

''# Catch All InValid (NotFound) Routes
routes.MapRoute( _
    "NotFound", _
    "{*url}", _
    New With {.controller = "Error", .action = "NotFound"})

如果您输入:http://example.com/asdf-,那么它将匹配上面的“默认”路由 - MVC会寻找asdf-Controller,但找不到,因此会抛出异常。
如果您转到http://example.com/asdf-/action/id/slug/extra,则“默认”不再匹配,而将跟随“未找到”的路由。
您可以为所有已知控制器添加一个过滤器到“默认”路由,但是当您添加新控制器时,这将很麻烦。
您不应该需要完全钩入Application_Error事件,但我还没有找到更好的解决缺少控制器的方法 - 我将其作为另一个问题提出。

这对我帮助很大。非常感谢。 - Zac

0

在 web config 中进行设置:

<system.web>
      <customErrors mode="On" defaultRedirect="error.html"/>
</system.web>

error.html 是您可以使用的自定义页面,用于向用户显示任意 HTML 内容。


2
就像我说的那样,我不想重定向用户。 - Chase Florell
如果您在路由字典中设置了一个未知路由,将其路由到您的错误控制器/ActionResult并返回所需的视图,那会怎样呢?这将保留URL不变。 - Chris Kooken
嗯,这样的路由会是什么样子呢? - Chase Florell
-1 是因为答案是“重定向”,而这恰恰不是我想要的。 - Chase Florell

0

这是 ASP.NET MVC 框架的一个大缺陷,因为你不能自定义(我指的是真正的自定义,而不仅仅是针对任何错误页面的一个)404 页面,而不需要像 1 answer 那样费力。你可以使用一个 404 错误页面来处理所有未找到的原因,或者你可以通过 [HandleError] 自定义多个 500 错误页面,但你无法声明式地自定义 404 错误。由于 SEO 原因,500 错误很糟糕。404 错误是好的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接