ASP.NET MVC — как создать страницу 404, аналогичную странице StackOverflow

В настоящее время у меня есть класс BaseController, который наследуется от System.Web.Mvc.Controller. В этом классе у меня есть атрибут HandleError, который перенаправляет пользователей на страницу «500 — Ой, мы облажались». В настоящее время это работает, как и ожидалось.

ЭТО РАБОТАЕТ

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

''# do stuff
End Class

У меня также есть мои страницы 404, работающие на основе Per-ActionResult, которые снова работают, как и ожидалось.

ЭТО РАБОТАЕТ

    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-адрес нетронутым (IE: не перенаправлять на любую другую страницу), а просто отобразить представление «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» ничего не делает.


person Chase Florell    schedule 07.08.2010    source источник


Ответы (6)


Нашел свой ответ на моем другом ТАК вопрос. Большое спасибо 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));
        }
    }
}
person Chase Florell    schedule 19.08.2010
comment
Однако это хорошее решение перед errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); вызывается, эту строку нужно добавить Response.StatusCode = 404; Если эта строка не добавлена, отклик страницы по-прежнему равен 200 независимо от того, что отображается для пользователя. - person Paul; 19.10.2010
comment
IController.Execute защищен, по крайней мере, в MVC 4, поэтому мне просто нужно было выставить его обернуть его из моего контроллера: public void Exec(RequestContext requestContext) { this.Execute(requestContext); } - person Cameron Taggart; 12.07.2012

Вы можете попробовать:

<customErrors mode="On" redirectMode="ResponseRewrite">
  <error statusCode="404" redirect="~/Error.aspx"/>
</customErrors>

http://msdn.microsoft.com/en-us/library/system.web.configuration.customerrorssection.redirectmode.aspx

Если для свойства RedirectMode установлено значение ResponseRewrite, пользователь отправляется на страницу с ошибкой, а исходный URL-адрес в браузере не изменяется.

person mathieu    schedule 19.08.2010
comment
ResponseRewrite иногда возникают проблемы, если ошибка находится на странице WebForms, а перенаправление является MVC, и наоборот. - person Keith; 18.04.2011
comment
@ Кит, не могли бы вы рассказать об этом подробнее? Я сталкиваюсь с этими случаями и хочу попытаться понять, почему это работает только несколько раз... - person Raul Vejar; 15.05.2013
comment
@RaulVejar не совсем, это старый пост (я думаю, проблема была с MVC2), и мы просто избегали его с помощью различных пользовательских ошибок для MVC и WebForms. - person Keith; 16.05.2013
comment
@ Кейт, ненавижу это говорить, но в MVC4 все еще есть проблемы. Нам пришлось в конечном итоге обрабатывать ошибки вручную на глобальном asax и полностью отключать пользовательские ошибки. - person Raul Vejar; 17.05.2013

Маршруты соответствуют шаблону. Ваш ненайденный маршрут не работает, потому что шаблон вашего неправильного URL-адреса соответствует более раннему маршруту.

So:

''# 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, то «Default» больше не соответствует, и вместо этого будет использоваться маршрут «NotFound».

Вы можете добавить фильтр для всех ваших известных контроллеров в маршрут «По умолчанию», но это будет сложно поддерживать при добавлении новых контроллеров.

Вам вообще не нужно подключаться к событию Application_Error, но я еще не нашел лучшего способа обойти отсутствующий контроллер - я спросил его как еще один вопрос.

person Keith    schedule 18.04.2011
comment
Это мне очень помогло. Огромное спасибо - person Zac; 26.09.2011

Я заставил это работать, просто возвращая действие, определенное в базовом контроллере, а не перенаправляя на него.

[HandleError]
public ActionResult NotFound()
{
    Response.StatusCode = 404;
    Response.TrySkipIisCustomErrors = true;     
    return View("PageNotFound", SearchUtilities.GetPageNotFoundModel(HttpContext.Request.RawUrl));          
}

(Бит «SearchUtilities.GetPageNotFoundModel» предназначен для создания предложений путем ввода URL-адреса в нашу поисковую систему)

В любом методе действия, который наследует базу, я могу просто вызвать это:

return NotFound();

... всякий раз, когда я ловлю недопустимый параметр. И универсальный маршрут делает то же самое.

person Faust    schedule 18.10.2011
comment
Это нормально, но теперь я могу сделать это без действия NotFound. Делает вещи намного чище. - person Chase Florell; 18.10.2011
comment
Я понимаю. Это привлекательно. Как вы справляетесь с маршрутами, которые совпадают, поэтому не выдаете 404, а передаете недопустимые параметры? - person Faust; 18.10.2011
comment
У меня есть пользовательский HandleNotFoundException, который я могу сгенерировать из действительного контроллера. Он загрузит соответствующее общее представление Shared/NotFound.cshtml, но сохранит URI нетронутым. throw HandleNotFoundException - person Chase Florell; 18.10.2011
comment
Мой ответ выше устарел... Мне нужно обновить его своим решением. - person Chase Florell; 18.10.2011

Настройте его в веб-конфигурации:

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

error.html — это ваша пользовательская страница, на которой вы можете отображать любой произвольный HTML-код для пользователя.

person Chris Kooken    schedule 07.08.2010
comment
Как я уже сказал, я НЕ хочу перенаправлять пользователя. - person Chase Florell; 07.08.2010
comment
Что, если вы настроили маршрут в словаре маршрутизации, который имел неизвестные маршруты маршрута к вашему контроллеру ошибок/результату действия и вернул желаемое представление. Это оставило бы URL в такте. - person Chris Kooken; 07.08.2010
comment
хм, а как бы выглядел такой маршрут? - person Chase Florell; 07.08.2010
comment
-1, потому что ответ перенаправляет, а это именно то, чего я НЕ хочу. - person Chase Florell; 20.08.2010

Это большой промах от платформы ASP.NET MVC, потому что вы не можете настроить (реальная настройка, я имею в виду не просто одну страницу с ошибкой для чего-либо) 404 страницы без «хрустов», таких как 1 ответ. Вы можете использовать одну ошибку 404 для всех ненайденных причин, или вы можете настроить несколько страниц ошибок 500 через [HandleError], но вы НЕ МОЖЕТЕ декларативно настроить ошибки 404. 500 ошибок — это плохо по причинам SEO. 404 ошибки - хорошие ошибки.

person user403579    schedule 23.12.2010