Как я могу правильно обрабатывать 404 в ASP.NET MVC?

Я использую RC2

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

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

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

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

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

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

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

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

Ответ 1

Код берется из http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx и работает также в ASP.net MVC 1.0

Вот как я обрабатываю исключения http:

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 an Http Exception, Let 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));
}

Ответ 2

Требования для 404

Ниже приведены мои требования к решению 404 и ниже. Я показываю, как я его реализую:

  • Я хочу обрабатывать согласованные маршруты с плохими действиями.
  • Я хочу обрабатывать согласованные маршруты с плохими контроллерами.
  • Я хочу обрабатывать несогласованные маршруты (произвольные URL-адреса, которые мое приложение не может понять) - я не хочу, чтобы эти пузырились до Global.asax или IIS, потому что тогда я не могу перенаправить обратно в свое MVC-приложение правильно
  • Я хочу, чтобы способ обрабатывался таким же образом, как и выше, пользовательские 404s - например, когда идентификатор отправляется для объекта, который не существует (возможно, удален)
  • Я хочу, чтобы все мои 404s возвращали MVC-представление (а не статическую страницу), в которое я могу направить больше данных позже, если необходимо (хорошие 404 дизайна), и они должны вернуть код статуса HTTP 404

Решение

Я думаю, что вы должны сохранить Application_Error в Global.asax для более высоких вещей, таких как необработанные исключения и ведение журнала (например, ответ Shay Jacoby), но не 404 обработки, Вот почему мое предложение содержит 404 материала из файла Global.asax.

Шаг 1: используйте место для ошибки 404 ошибки

Это хорошая идея для ремонтопригодности. Используйте ErrorController, чтобы будущие улучшения вашей хорошо продуманной страницы 404 могли легко адаптироваться. Кроме того, убедитесь, что ваш ответ содержит код 404!

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

Шаг 2: используйте базовый класс контроллера, чтобы вы могли легко вызвать свое пользовательское действие 404 и подключить HandleUnknownAction

404s в ASP.NET MVC необходимо поймать в нескольких местах. Первый - HandleUnknownAction.

Метод InvokeHttp404 создает общее место для перенаправления на ErrorController и наше новое действие Http404. Подумайте DRY!

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
}

Шаг 3: Используйте инъекцию зависимостей в вашем контроллере Factory и подключите 404 HttpExceptions

Так же (это не должно быть StructureMap):

Пример MVC1.0:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

Пример MVC2.0:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

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

Это второе место, чтобы поймать 404s.

Шаг 4: добавьте маршрут NotFound в Global.asax для URL-адресов, которые не могут быть проанализированы в вашем приложении

Этот маршрут должен указывать на наше действие Http404. Обратите внимание, что параметр url будет относительным URL-адресом, потому что механизм маршрутизации лишает часть домена здесь? Вот почему у нас есть вся эта условная логика url на шаге 1.

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

Это третье и последнее место, чтобы поймать 404s в приложении MVC, которое вы не вызываете сами. Если вы не поймаете непревзойденные маршруты здесь, то MVC передаст проблему до ASP.NET(Global.asax), и вы действительно не хотите, чтобы в этой ситуации.

Шаг 5: Наконец, вызовите 404s, когда ваше приложение не сможет найти что-то

Как когда плохой идентификатор отправляется на мой контроллер Loans (происходит от MyController):

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

Было бы неплохо, если бы все это могло быть подключено в меньшем количестве мест с меньшим количеством кода, но я думаю, что это решение более поддается обслуживанию, более проверяемо и довольно прагматично.

Спасибо за отзывы. Мне бы хотелось получить больше.

ПРИМЕЧАНИЕ. Это было значительно изменено из моего первоначального ответа, но цель/требования одинаковы - вот почему я еще не добавил новый ответ

Ответ 3

ASP.NET MVC не поддерживает пользовательские 404 страницы очень хорошо. Пользовательский контроллер factory, маршрут catch-all, базовый класс контроллера с HandleUnknownAction - argh!

Страницы пользовательской ошибки IIS являются лучшей альтернативой:

web.config

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" />
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
  </httpErrors>
</system.webServer>

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

Пример проекта

Ответ 4

Быстрый ответ /TL; DR

enter image description here

Для ленивых людей там:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Затем удалите эту строку из global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

И это только для IIS7+ и IIS Express.

Если вы используете Кассини.. ну.......... неудобно... awkward


Долго, объяснил ответ

Я знаю, что на это был дан ответ. Но ответ ДЕЙСТВИТЕЛЬНО ПРОСТО (приветствует Дэвида Фаулера и Дамиана Эдвардса, чтобы действительно ответить на это).

Нет необходимости делать что-либо обычай.

Для ASP.NET MVC3 все биты и части есть.

Шаг 1 → Обновите свой web.config в двух местах.

<system.web>
    <customErrors mode="On" defaultRedirect="/ServerError">
      <error statusCode="404" redirect="/NotFound" />
    </customErrors>

а также

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>

Теперь обратите внимание на МАРШРУТЫ, которые я решил использовать. Вы можете использовать что угодно, но мои маршруты

  • /NotFound <- для 404 не найдена страница с ошибкой.
  • /ServerError <- для любой другой ошибки включают ошибки, которые происходят в моем коде. это 500 внутренних ошибок сервера

Посмотрите, как первый раздел в <system.web> имеет только одну пользовательскую запись? Запись statusCode="404"? Я только перечислил один код состояния, потому что все другие ошибки, в том числе 500 Server Error (т. defaultRedirect="/ServerError" ошибка, которая возникает, когда ваш код имеет ошибку и выдает запрос пользователя).. все остальные ошибки обрабатываются параметром defaultRedirect="/ServerError".., который говорит, что если вы не являетесь 404 страницей, не найденной, то, пожалуйста, перейдите по маршруту /ServerError.

ОК. это из пути.. теперь к моим маршрутам, перечисленным в global.asax

Шаг 2 - Создание маршрутов в Global.asax

Здесь мой полный маршрут.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

Это перечисляет два маршрута игнорирования → axd's и favicons (ooo! axd's игнорировать маршрут для вас!) Затем (и порядок ОБЯЗАТЕЛЬНО ЗДЕСЬ), у меня есть два моих явных маршрута обработки ошибок.. за которыми следуют любые другие маршруты. В этом случае используется по умолчанию. Конечно, у меня больше, но что особенного для моего веб-сайта. Просто убедитесь, что маршруты ошибок находятся в верхней части списка. Заказ является обязательным.

Наконец, пока мы находимся внутри нашего файла global.asax, мы НЕ регистрируем глобальный атрибут HandleError. Нет, нет, нет, сэр. Nadda. Неа. Nien. Negative. Noooooooooo...

Удалите эту строку из global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Шаг 3 - Создайте контроллер с помощью методов действий

Теперь добавим контроллер с двумя методами действий...

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult ServerError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Todo: Pass the exception into the view model, which you can make.
        //       That an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

Хорошо, давайте проверим это. Прежде всего, здесь нет [HandleError]. Зачем? Поскольку встроенная инфраструктура ASP.NET уже обрабатывает ошибки, мы указали все, что нам нужно сделать, чтобы справиться с ошибкой :) Это в этом методе!

Затем у меня есть два метода действий. Ничего сложного. Если вы хотите показать информацию об исключении, то вы можете использовать Server.GetLastError() чтобы получить эту информацию.

Бонус WTF: Да, я сделал третий метод действий, чтобы проверить обработку ошибок.

Шаг 4 - Создайте представления

И, наконец, создайте два вида. Поместите em в обычное место просмотра для этого контроллера.

enter image description here

Бонусные комментарии

  • Вам не нужен Application_Error(object sender, EventArgs e)
  • Вышеуказанные шаги все работают на 100% отлично с Elmah. Elmah fraking wroxs!

И это, мои друзья, должно быть так.

Теперь, поздравляю за это много, и у Единорога в качестве приза!

enter image description here

Ответ 5

Я исследовал A LOT о том, как правильно управлять 404 в MVC (в частности, MVC3), и это, IMHO - лучшее решение, которое я придумал:

В global.asax:

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

ErrorsController:

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

(Необязательно)

Объяснение:

AFAIK, существует 6 различных случаев, когда приложения ASP.NET MVC3 могут генерировать 404s.

(автоматически генерируется ASP.NET Framework:)

(1) URL-адрес не найден в таблице маршрутов.

(автоматически создается ASP.NET MVC Framework:)

(2) URL-адрес находит соответствие в таблице маршрутов, но указывает несуществующий контроллер.

(3) URL-адрес находит соответствие в таблице маршрутов, но указывает несуществующее действие.

(сгенерировано вручную:)

(4). Действие возвращает HttpNotFoundResult с помощью метода HttpNotFound().

(5) Действие выдает исключение HttpException кодом состояния 404.

(6) Действия вручную изменяют свойство Response.StatusCode на 404.

Обычно вы хотите выполнить 3 цели:

(1) Показать пользователю пользовательскую страницу ошибок 404.

(2) Поддерживайте код статуса 404 в ответе клиента (особенно важно для SEO).

(3) Отправьте ответ напрямую, не включив перенаправление 302.

Существуют различные способы сделать это:

(1)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

Проблемы с этим решением:

  • Не соответствует объективному (1) в случаях (1), (4), (6).
  • Не соответствует объективному (2) автоматически. Он должен быть запрограммирован вручную.
  • Не соответствует объективному (3).

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Проблемы с этим решением:

  • Работает только с IIS 7 +.
  • Не соответствует объективному (1) в случаях (2), (3), (5).
  • Не соответствует объективному (2) автоматически. Он должен быть запрограммирован вручную.

(3)

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Проблемы с этим решением:

  • Работает только с IIS 7 +.
  • Не соответствует объективному (2) автоматически. Он должен быть запрограммирован вручную.
  • Он скрывает исключения http-приложений уровня приложения. Например. не может использовать раздел customErrors, System.Web.Mvc.HandleErrorAttribute и т.д. Он может не только показывать общие страницы ошибок.

(4)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

и

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Проблемы с этим решением:

  • Работает только с IIS 7 +.
  • Не соответствует объективному (2) автоматически. Он должен быть запрограммирован вручную.
  • Не соответствует объективному (3) в случаях (2), (3), (5).

Люди, которые столкнулись с этим, даже пытались создать свои собственные библиотеки (см. http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html). Но предыдущее решение, похоже, охватывает все случаи без сложности использования внешней библиотеки.

Ответ 6

Мне очень нравится решение cottsaks и думаю, что это очень понятно. мое единственное дополнение - изменить шаг 2 следующим образом

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
}

В основном это останавливает URL-адреса, содержащие недействительные действия, и контроллеры дважды запускают процедуру исключения. например, для URL-адресов, таких как asdfsdf/dfgdfgd

Ответ 7

Единственный способ получить метод @cottsak для работы с недействительными контроллерами - это изменить существующий запрос маршрута в CustomControllerFactory, например:

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

Я должен упомянуть, что я использую MVC 2.0.

Ответ 8

Вот еще один метод с использованием MVC-инструментов, с помощью которого вы можете обрабатывать запросы к неправильным именам контроллеров, неправильным именам маршрутов и любым другим критериям, которые вы видите внутри метода Action. Лично я предпочитаю избегать как можно большего количества настроек web.config, потому что они перенаправляют 302/200 и не поддерживают ResponseRewrite (Server.Transfer), используя представления Razor. Я бы предпочел вернуть 404 с пользовательской страницей ошибок по причинам SEO.

Некоторые из них - это новый подход к методу cottsak выше.

Это решение также использует минимальные настройки web.config, предпочитающие фильтры MVC 3 Error.

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

Просто отбросьте HttpException из действия или пользовательского ActionFilterAttribute.

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

Шаг 1

Добавьте следующий параметр в свой web.config. Это необходимо для использования MVC HandleErrorAttribute.

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

Шаг 2

Добавьте собственный атрибут HandleHttpErrorAttribute, аналогичный MVC-фреймворку HandleErrorAttribute, за исключением ошибок HTTP:

<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

Шаг 3

Добавить фильтры в GlobalFilterCollection (GlobalFilters.Filters) в Global.asax. В этом примере все ошибки InternalServerError (500) будут перенаправлены в общий доступ Error (Views/Shared/Error.vbhtml). Ошибки NotFound (404) будут отправлены в ErrorHttp404.vbhtml в общих представлениях. Я добавил здесь ошибку 401, чтобы показать вам, как это можно расширить для дополнительных кодов ошибок HTTP. Обратите внимание, что это должны быть общие представления, и все они используют объект System.Web.Mvc.HandleErrorInfo в качестве модели.

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"})

Шаг 4

Создайте класс базового контроллера и наследуйте его от своих контроллеров. Этот шаг позволяет нам обрабатывать неизвестные имена действий и поднять ошибку HTTP 404 на наш 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

Шаг 5

Создайте переопределение ControllerFactory и переопределите его в файле Global.asax в Application_Start. Этот шаг позволяет нам повысить исключение HTTP 404, когда указано недопустимое имя контроллера.

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)

Шаг 6

Включите специальный маршрут в свой RoutTable.Routes для действия BaseController Unknown. Это поможет нам собрать 404 в случае, когда пользователь обращается к неизвестному контроллеру или к неизвестному действию.

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

Резюме

В этом примере показано, как можно использовать среду MVC для возврата 404 кодов ошибок Http в браузер без перенаправления с использованием атрибутов фильтра и общих представлений об ошибках. Он также демонстрирует показ той же страницы пользовательских ошибок, когда указаны недопустимые имена контроллеров и имена действий.

Я добавлю скриншот недопустимого имени контроллера, имени действия и пользовательского 404, поднятого из действия Home/TriggerNotFound, если я получу достаточно голосов, чтобы опубликовать one =). Fiddler возвращает сообщение 404, когда я получаю доступ к следующим URL-адресам, используя это решение:

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

cottsak post выше, и эти статьи были хорошими ссылками.

Ответ 9

Мое сокращенное решение, которое работает с необработанными областями, контроллерами и действиями:

  • Создать представление 404.cshtml.

  • Создайте базовый класс для ваших контроллеров:

    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");
        }
    }
    
  • Создайте пользовательский контроллер factory, вернув базовый контроллер в качестве резервной копии:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
    
  • Добавьте в Application_Start() следующую строку:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
    

Ответ 10

В MVC4 WebAPI 404 можно обрабатывать следующим образом:

КУРСЫ APICONTROLLER

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

ГЛАВНЫЙ КОНТРОЛЛЕР

public ActionResult Course(int id)
{
    return View(id);
}

VIEW

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

ГЛОБАЛЬНОЙ

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Результаты

enter image description here

Ответ 11

Попробуйте NotFoundMVC на nuget. Он работает без установки.

Ответ 12

Мое решение, если кто-то сочтет это полезным.

В 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 и это.

Ответ 13

Мне кажется, что стандартная конфигурация 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
  • Включает поддержку конфигурации традиционных пользовательских ошибок.
  • Работает как в режиме Integrated Pipeline, так и в Classic.

Решение

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

}

Ответ 14

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

Моя цель при достижении этого решения заключалась в следующем:

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

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

1. Добавьте в раздел system.web следующее:

   <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-адреса, которые не обрабатываются с помощью route.config и необработанных исключений, особенно тех, которые встречаются в представлениях. Заметьте, я использовал aspx not html. Это значит, что я могу добавить код ответа на код позади.

2. Создайте папку с именем Error (или что бы вы ни выбрали) в корне вашего проекта и добавьте две веб-формы. Ниже моя страница 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 в свою папку общих разделов. Ниже мой;

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

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

Ответ 15

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

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

fooobar.com/questions/16780/...

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

@Pure.Krome

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

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

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

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

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

Ответ 16

Добавление моего решения, которое почти идентично Herman Kan's, с небольшой морщиной, чтобы он мог работать для моего проекта.

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

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

Затем создайте пользовательский контроллер factory:

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.

Ответ 17

1) Сделайте класс абстрактного контроллера.

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" в вашей папке "Просмотр".

Ответ 18

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

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

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

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

Первая часть его происходит от @Guillaume в fooobar.com/questions/17006/.... Их решение заботится о любых 404, которые были вызваны недействительным маршрутом, недействительным контроллером и недействительными действиями.

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


Вторая часть его происходит от @Germán в fooobar.com/questions/17007/.... Их решение заботится о любых 404, возвращаемых вашими действиями в виде HttpNotFoundResult() или выкидывает новое HttpException()!

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


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

Ответ 19

Я просмотрел все статьи, но ничего не работает для меня: Мое требование пользовательского типа что-либо на странице вашего пользовательского 404 страницы должно показать.Я думал, что это очень прямо вперед. Но вы должны понимать обработку 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>

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