Как я могу правильно обрабатывать 404 в ASP.NET MVC? - PullRequest
422 голосов
/ 06 марта 2009

Я использую RC2

Использование маршрутизации URL:

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

Вышеприведенное, кажется, обрабатывает подобные запросы (при условии, что таблицы маршрутов по умолчанию настроены первоначальным проектом MVC): "/ бла / бла / бла / бла"

Переопределение HandleUnknownAction () в самом контроллере:

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  

Однако предыдущие стратегии не обрабатывают запрос к плохому / неизвестному контроллеру. Например, у меня нет «/ IDoNotExist», если я запрашиваю это, я получаю общую страницу 404 с веб-сервера, а не мою 404, если я использую маршрутизацию + переопределение.

Итак, наконец, мой вопрос: Есть ли способ перехватить этот тип запроса, используя маршрут или что-то еще в самой структуре MVC?

ИЛИ мне просто по умолчанию использовать Web.Config customErrors в качестве моего обработчика 404 и забыть все это? Я предполагаю, что если я пойду с customErrors, мне придется хранить общую страницу 404 вне / Views из-за ограничений Web.Config на прямой доступ.

Ответы [ 19 ]

2 голосов
/ 01 октября 2013

Мое решение, если кто-то найдет его полезным.

В Web.config:

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

В 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();
    }
}

Добавьте PageNotFound.cshtml в папку Shared, и все.

2 голосов
/ 09 июля 2013

Попробуйте NotFoundMVC на nuget. Работает, без настроек.

2 голосов
/ 31 августа 2016

Устранение ошибок в ASP.NET MVC - это просто боль в заднице. Я перепробовал много предложений на этой странице и на других вопросах и сайтах, и ничего не работает хорошо. Одно из предложений заключалось в обработке ошибок на web.config внутри system.webserver , но только возвращает пустые страницы .

Моя цель при создании этого решения была:

  • НЕ НАПРАВЛЕНО
  • Возвращает PROPER STATUS CODES, а не 200 / Ok, как обработка ошибок по умолчанию

Вот мое решение.

1 . Добавьте следующее к system.web section

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

Выше обрабатываются любые URL, не обработанные rout.config , и необработанные исключения, особенно те, которые встречаются в представлениях. Обратите внимание, что я использовал aspx , а не html . Поэтому я могу добавить код ответа к коду позади.

2 . Создайте папку с именем Ошибка (или что вы предпочитаете) в корне вашего проекта и добавьте две веб-формы. Ниже моя страница 404;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

И на коде позади я установил код ответа

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

Сделайте то же самое для страницы 500

3 . Для обработки ошибок в контроллерах. Есть много способов сделать это. Это то, что сработало для меня. Все мои контроллеры наследуются от базового контроллера. В базовом контроллере у меня есть следующие методы

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4 . Добавьте файл CustomError.cshtml в папку Shared views. Ниже мое;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

Теперь в вашем контроллере приложения вы можете сделать что-то вроде этого;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

Теперь для предостережения . Он не будет обрабатывать статические ошибки файла. Поэтому, если у вас есть маршрут, такой как example.com / widgets , и пользователь меняет его на example.com / widgets.html , он получит страницу ошибки IIS по умолчанию, поэтому для обработки ошибок уровня IIS другим способом.

2 голосов
/ 31 марта 2015

Мне кажется, что стандартная CustomErrors конфигурация должна просто работать , однако из-за зависимости от Server.Transfer кажется, что внутренняя реализация ResponseRewrite не совместима с MVC.

Для меня это похоже на явную дыру в функциональности, поэтому я решил повторно реализовать эту функцию с помощью модуля HTTP. Приведенное ниже решение позволяет вам обрабатывать любой код состояния HTTP (включая 404) путем перенаправления на любой действительный маршрут MVC, как вы это обычно делаете.

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

Это было проверено на следующих платформах;

  • MVC4 в режиме интегрированного конвейера (IIS Express 8)
  • MVC4 в классическом режиме (VS Development Server, Cassini)
  • MVC4 в классическом режиме (IIS6)

Преимущества

  • Универсальное решение, которое можно добавить в любой проект MVC
  • Включает поддержку настройки традиционных пользовательских ошибок
  • Работает как в интегрированном конвейерном, так и в классическом режимах

Решение

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() {

        }


    }

}

Использование

Включите это как последний HTTP-модуль в ваш 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>

Для тех из вас, кто обратил внимание, вы заметите, что в режиме Integrated Pipeline он всегда отвечает HTTP 200 из-за способа работы Server.TransferRequest. Чтобы вернуть правильный код ошибки, я использую следующий контроллер ошибок.

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());
    }

}
1 голос
/ 20 февраля 2017

1) Сделать абстрактный класс Controller.

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) Сделайте наследование от этого абстрактного класса во всех ваших контроллерах

public class HomeController : MyController
{}  

3) И добавьте представление с именем «NotFound» в папку View-Shared.

1 голос
/ 03 февраля 2017

Добавление моего решения, которое почти идентично решению Германа Кана, с небольшой складкой, чтобы оно могло работать для моего проекта.

Создание настраиваемого контроллера ошибок:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

Затем создайте фабрику пользовательских контроллеров:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

Наконец, добавьте переопределение к пользовательскому контроллеру ошибок:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

И это все. Нет необходимости вносить изменения в Web.config.

1 голос
/ 14 января 2014

Отправка ответа, так как мой комментарий был слишком длинным ...

Это комментарий и вопросы к сообщению / ответу единорога:

https://stackoverflow.com/a/7499406/687549

Я предпочитаю этот ответ другим за его простоту и тот факт, что, очевидно, с некоторыми людьми в Microsoft консультировались. Однако я получил три вопроса, и если на них можно будет ответить, я назову этот ответ святым Граалем всех ответов об ошибках 404/500 на веб-страницах для приложения ASP.NET MVC (x).

@Pure.Krome

  1. Можете ли вы обновить свой ответ с помощью материалов SEO из комментариев, указанных GWB (об этом никогда не упоминалось в вашем ответе) - <customErrors mode="On" redirectMode="ResponseRewrite"> и <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Можете ли вы спросить своих друзей из команды ASP.NET, можно ли так поступить - было бы неплохо получить какое-то подтверждение - может быть, это большое отрицательное изменение redirectMode и existingResponse в таким образом, чтобы иметь возможность хорошо играть с SEO ?!

  3. Можете ли вы добавить некоторые пояснения, касающиеся всего этого (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", УДАЛИТЬ customErrors ПОЛНОСТЬЮ, как кто-то предложил) после разговора с друзьями в Microsoft?

Как я уже говорил; было бы замечательно, если бы мы могли сделать ваш ответ более полным, поскольку этот вопрос кажется довольно популярным и имеет более 54 000 просмотров.

Обновление : ответ Единорога показывает 302 Найденного и 200 ОК и не может быть изменен, чтобы только возвратить 404, используя маршрут. Это должен быть физический файл, который не очень MVC: иш. Так что переходим к другому решению. Жаль, потому что это, казалось, было окончательным MVC: иш ответ так далеко.

0 голосов
/ 30 июля 2015

Я просмотрел все статьи, но у меня ничего не работает: Мое требование: пользователь должен ввести что-нибудь на вашей странице 404 URL-адреса. Я подумал, что это очень просто. Но вы должны правильно понимать обработку 404:

 <system.web>
    <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/PageNotFound.aspx"/>
    </customErrors>
  </system.web>
<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Я нашел эту статью очень полезной. Надо прочесть сразу. Страница ошибки обычая - Бен Фостер

0 голосов
/ 27 апреля 2015

Я просмотрел большинство решений, опубликованных в этой теме. Хотя этот вопрос может быть старым, он все еще очень применим к новым проектам даже сейчас, поэтому я потратил довольно много времени на чтение ответов, представленных здесь, а также где-то еще.

Как @Marco указал на различные случаи, при которых может произойти 404, я проверил решение, которое я скомпилировал, по этому списку. В дополнение к его списку требований я также добавил еще один.

  • Решение должно быть способно обрабатывать вызовы MVC и AJAX / WebAPI наиболее подходящим способом. (то есть, если 404 происходит в MVC, на нем должна отображаться страница «Не найдено», а если 404 происходит в WebAPI, он не должен перехватывать ответ XML / JSON, чтобы потребляющий Javascript мог легко его проанализировать).

Это решение в 2 раза:

Первая часть написана @Guillaume по адресу https://stackoverflow.com/a/27354140/2310818. Их решение устраняет любые 404, которые были вызваны из-за неверного маршрута, неверного контроллера и недопустимого действия.

Идея состоит в том, чтобы создать WebForm, а затем заставить его вызывать действие NotFound вашего контроллера ошибок MVC. Он делает все это без какого-либо перенаправления, поэтому вы не увидите ни одного 302 в Fiddler. Оригинальный URL также сохраняется, что делает это решение фантастическим!


Вторая часть написана @ Germán по адресу https://stackoverflow.com/a/5536676/2310818. Их решение заботится о любых 404, возвращаемых вашими действиями в виде HttpNotFoundResult () или при создании нового HttpException ()!

Идея состоит в том, чтобы фильтр посмотрел на ответ, а также на исключение, выданное вашими контроллерами MVC, и вызвал соответствующее действие в вашем контроллере ошибок. Опять же, это решение работает без какого-либо перенаправления и оригинальный URL сохраняется!


Как видите, оба эти решения вместе предлагают очень надежный механизм обработки ошибок, и они отвечают всем требованиям, указанным @Marco, а также моим требованиям. Если вы хотите увидеть рабочий образец или демонстрацию этого решения, пожалуйста, оставьте в комментариях, и я был бы рад собрать его вместе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...