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

432

Я использую 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 на прямой доступ.

Брайан
источник
3
это ошибка 404, я бы не стал беспокоиться об этом. пусть он отображает 404. как пользователь определенно что-то опечатал. или если это что-то перенесено, то ваше приложение должно принять этот запрос и сделать перенаправление постоянным. 404 принадлежит веб-серверу, а не приложению. Вы всегда можете настроить страницы IIS для ошибок.
Маму
Вы также можете взглянуть на это решение. blog.dantup.com/2009/04/…
Разработчик,
ben.onfabrik.com/posts/aspnet-mvc-custom-error-pages также содержит полезную информацию
Крис С
4
Жаль, что 4 стабильных релиза позже и более 5 лет, ситуация для обработки 404-х в asp.net MVC + IIS действительно не улучшилась, и это все еще переход к Q & A для того, как с этим справиться.
Joelmdev

Ответы:

271

Код взят из 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's an Http Exception, Let's 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));
}
Шей Якоби
источник
23
обновление: проверка на http 404 определенно требуется, но я все еще не совсем уверен, когда вы когда-нибудь получите 500. Также вам нужно также явно установить Response.StatusCode = 404 или 500, иначе Google начнет индексировать эти страницы если вы возвращаете код состояния 200, который в настоящее время делает этот код
Simon_Weaver
6
@Simon_Weaver: согласился! Для этого необходимо вернуть 404 кода состояния. По сути, он разбит как решение 404, пока не получится. Проверьте это: codinghorror.com/blog/2007/03/…
Мэтт Кочай
1
В этом предложении есть основной недостаток - к тому времени, когда выполнение всплыло до Global.asax, слишком много HttpContext отсутствует. Вы не можете перенаправить обратно на свои контроллеры, как показано в примере. Обратитесь к комментариям в блоге по ссылке вверху.
Мэтт Кодж
3
Как отмечают некоторые из приведенных выше комментариев и связанных постов, это не работает. Ошибка контроллера, но пустой экран возвращается. (используя mvc 3)
RyanW
4
что-то не так, вся цель MVC состоит в том, чтобы удалить всю эту абстракцию, и все же здесь это снова ...
Алекс Ноласко
255

Требования к 404

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

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

Решение

Я думаю, что вы должны сохранить Application_Errorв Global.asax более высокие вещи, такие как необработанные исключения и ведение журнала (как показывает ответ Шей Джейкоби ), но не обработку 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: Используйте базовый класс Controller, чтобы вы могли легко вызвать свое собственное действие 404 и подключиться HandleUnknownAction

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

InvokeHttp404Метод создает общее место для повторной маршрутизации к ErrorControllerи нашему новому Http404действию. Думай СУХОЙ !

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: Используйте Dependency Injection в вашей фабрике контроллеров и подключите 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обработчику.

Это второе место для ловли 404-х годов.

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

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

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

Это третье и последнее место, где можно поймать 404-е в приложении 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);
    }

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

Спасибо за отзыв до сих пор. Я хотел бы получить больше.

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

Matt Kocaj
источник
12
Спасибо за полную рецензию. Одним из добавлений является то, что при работе в IIS7 необходимо добавить значение свойства «TrySkipIisCustomErrors» в значение true. В противном случае IIS все равно вернет страницу 404 по умолчанию. Мы добавили Response.TrySkipIiisCustomErrors = true; после строки в шаге 5, которая устанавливает код состояния. msdn.microsoft.com/en-us/library/…
Рик
1
@Ryan customErrorsРаздел web.config определяет статические страницы перенаправления, которые обрабатываются на высоком уровне в aspnet, если не в IIS. Это не то, что я хотел, так как мне нужно было иметь MVC Views (чтобы я мог иметь в них данные и т. Д.). Я бы не сказал категорически, что « customErrorsустарел в MVC», но для меня и этого решения 404 они, безусловно, есть.
Мэтт Коджан
1
Кроме того, кто-то может обновить Шаг 3, чтобы StructureMap не использовался? Может быть, просто универсальный ControllerFactory, который будет легко реализовать, если вы еще не используете ControllerFactory.
Дэвид Мердок
7
Это отлично работает для MVC3. Я переключился ObjectFactory.GetInstanceна MVC3 DependencyResolver.Current.GetServiceвместо этого, чтобы он был более общим. Я использую Ninject.
kamranicus
122
Кто-нибудь еще находит это явно безумным, что такая обычная вещь, как 404 в веб-фреймворке, настолько чертовски сложна?
Квентин-старин
235

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

Пользовательские страницы ошибок 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();
    }
}

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

Павел Чучува
источник
38
ЭТО ДОЛЖНО БЫТЬ ПРИНЯТОМ ОТВЕТА !!! Отлично работает на ASP.NET MVC 3 с IIS Express.
Андрей Ринея
7
Если вы используете IIS7 +, это, безусловно, путь. +1!
elo80ka
3
возможно ли вернуть только статус 404, когда вы работаете в JSON в том же проекте?
VinnyG
6
Это отлично работает в iis express, но как только я разверну сайт в рабочей версии IIS 7.5, я получу только белую страницу вместо сообщения об ошибке.
Moulde
2
По моим тестам (с MVC3) эти разрывы customErrors mode="On"вместе с HandleErrorAttributeбыть функциональным. Пользовательские страницы ошибок для необработанных исключений в действиях контроллера больше не обслуживаются.
Слаума
153

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

введите описание изображения здесь

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

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

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

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

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

Если вы используете Кассини .. ну .. эм .. эээ ... неловко ... неловко


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

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

Там нет необходимости делать что-либо на заказ .

За 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"... которая говорит , если вы не нашли страницу 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! Бонус игнорировать маршрут, для вас!) Затем (и порядок здесь ИМПЕРАТИВНЫЙ ЗДЕСЬ), у меня есть два явных маршрута обработки ошибок ..., за которыми следуют любые другие маршруты. В этом случае по умолчанию. Конечно, у меня есть больше, но это специально для моего веб-сайта. Просто убедитесь, что маршруты ошибок находятся вверху списка. Порядок обязателен .

Наконец, пока мы находимся внутри нашего 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's 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 - Создание представлений

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

введите описание изображения здесь

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

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

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

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

введите описание изображения здесь

Pure.Krome
источник
Поэтому я попытался реализовать это, но пара проблем ... во-первых, вам нужно ~ до пути в weeb.config или он не работает для виртуальных каталогов. 2 - Если пользовательские ошибки IIS возникают, и представление использует макет, который он вообще не отображает, просто белая страница. Я решил это, добавив эту строку в контроллер "Response.TrySkipIisCustomErrors = true;" , Тем не менее, он все равно не работает, если вы переходите по URL-адресу, который является файлом, но 404 .. как mysite / whats / fake.html получает белую страницу.
Роберт Ноак
3
-1, извините, для меня любое решение, которое меняет URL на 404, неверно. и с помощью webconfig в MVC нет никакого способа, которым вы можете справиться с этим без изменения url, или вам нужно создать статические html-файлы или aspx (да, обычные старые aspx-файлы), чтобы это можно было сделать. Ваше решение хорошо, если вы хотели ?aspxerrorpath=/er/not/foundбы иметь в URL.
Гутек
7
Это может звучит очень странно - но мой ответ был предоставлен давным - давно , и я согласен с вашим @Gutek, я не люблю делать редирект на страницу ошибки больше . Я привык (см. Мой ответ: P). Если ошибка произошла в / some / resource .. то ресурс TH должен возвращать 404 или 500 и т. Д. В противном случае это может привести к значительным последствиям для SEO. Ааа .. как времена меняются :)
Pure.Krome
@Gutek Знаете ли вы о customErrors redirectMode = "ResponseRewrite"? И возвращение 404-х годов не является идеальным с точки зрения безопасности
Jowen
1
@Chris <вставь свое любимое божество сюда>, черт побери. Я даже не могу вспомнить, что это было сейчас. Ну, моя коллекция мемов на помощь ... и ... исправлена.
Pure.Krome
86

Я исследовал МНОГО о том , как правильно управлять в MVC 404 - х ( в частности MVC3) , и это, ИМХО это лучшее решение , которое я придумал:

В 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 могут генерировать 404.

(Автоматически генерируется 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) в случаях (1), (4), (6).
  2. Не соответствует цели (2) автоматически. Это должно быть запрограммировано вручную.
  3. Не соответствует цели (3).

(2)

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

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

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

(3)

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

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

  1. Работает только на IIS 7+.
  2. Не соответствует цели (2) автоматически. Это должно быть запрограммировано вручную.
  3. Это скрывает 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>

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

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

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

Marco
источник
Отличный ответ. Достойны многих других голосов. Почему ваш код global.asax не работает / принадлежит в Application_Error.
NinjaNye
7
Спасибо! Это не может быть сделано в Application_Error, потому что явные 404, выданные из контроллера, не считаются ошибками в ASP.NET. Если вы возвращаете HttpNotFound () из контроллера, событие Application_Error никогда не сработает.
Марко
1
Я думаю, что вы забыли public ActionResult NotFound() {}в вашем ErrorsController. Кроме того, можете ли вы объяснить, как ваш _NotFoundчастичный будет выглядеть для запросов AJAX?
d4n3
2
С MVC 4 я всегда MissingMethodException: Cannot create an abstract classв курсе. c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Есть идеи?
µBio
1
Если URL «not found» содержит точку в пути (например, example.com/hi.bob ), Application_EndRequest вообще не запускается, и я получаю общую страницу 404 IE.
Bob.at.Indigo.Health
13

Мне действительно нравится решение коттсаков, и я думаю, что оно очень четко объяснено. мое единственное дополнение было изменить шаг 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

Дэйв Лоу
источник
4
Это отлично. Эти «двойные» случаи начинали беспокоить меня. обновил мой ответ
Мэтт Kocaj
работает ли приведенное выше решение вообще, если пользователь ввел неправильный контроллер и имя действия?
Моножит Саркар
6

Единственный способ заставить метод @ 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.

Дейв К
источник
Ты знаешь почему? (Специфично для MVC2?)
Мэтт Кодж
Я думаю, что ключ был в том, чтобы изменить существующий запрос, а не сделать новый, но я сделал это некоторое время назад, поэтому я не уверен, что это так. «InvokeHttp404» не работал с фабрики контроллеров.
Дэйв К
Я обновил свой ответ сегодня с некоторыми особенностями MVC2. Не могли бы вы сказать мне, если мое решение, как описано выше, все еще не работает для вас?
Мэтт Кодж
4

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

Отчасти это новый взгляд на технику Котцака выше.

Это решение также использует минимальные настройки web.config вместо фильтров ошибок MVC 3.

Применение

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

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

Шаг 1

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

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

Шаг 2

Добавьте пользовательский атрибут HandleHttpErrorAttribute, аналогичный атрибуту HandleErrorAttribute инфраструктуры MVC, за исключением ошибок 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, если я наберу достаточно голосов, чтобы опубликовать один =). Fiddler возвращает сообщение 404, когда я получаю доступ к следующим URL-адресам, используя это решение:

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

Пост Котцака выше и эти статьи были хорошими ссылками.

небесно-DEV
источник
Хм, я не мог заставить это работать: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- есть идеи, почему я получу это?
enashnash
redirectMode = "ResponseRedirect". Это вернет 302 Found + 200 OK, что не хорошо для SEO!
PussInBoots
4

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

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

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

    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");
        }
    }
  3. Создайте фабрику пользовательских контроллеров, возвращающую базовый контроллер как запасной вариант:

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

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
Герман Кан
источник
3

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

ПОСМОТРЕТЬ

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

РЕЗУЛЬТАТЫ

введите описание изображения здесь

Диганта Кумар
источник
2

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

Дарт Вейдер
источник
http://localhost/Views/Shared/NotFound.cshtmlне приводит к пользовательской странице 404.
Дэн Фридман
Это очень легко настроить. У вас есть доступ к запрошенному URL и рефереру, так что вы можете делать то, что вам нравится. Я использую этот пакет, и он работает очень хорошо.
Аврохом Исроэль
Это отличный пакет, при условии, что вы не будете использовать асинхронные действия Task <ActionResult> (или другие подобные асинхронные) действия. На MVC 5 это сломанный сценарий. На GitHub есть форк, чтобы обойти это, но для меня это нет, нет.
Stargazer
2

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

В 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папку, и все.

Konamiman
источник
2
Разве это не приводит к перенаправлению 302, а затем 200 (OK) статусу клиента? Разве они не должны все еще получать статус 404?
Сэм
@Konamiman Вы уверены, что строка в вашем коде должна читаться, model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;а не model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(& вместо &&)?
Жан-Франсуа Бошан
2

Мне кажется, что стандартная 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());
    }

}
Красный Таз
источник
2

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

Моя цель при разработке этого решения заключалась в том, чтобы:

  • НЕ НАПРАВЛЕНО
  • Вернуть PROPER STATUS CODES, а не 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, а не 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();
    }
  }
}

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

Моисей Мачуа
источник
1

Публикация ответа, так как мой комментарий был слишком длинным ...

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

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", REMOVE customErrorsПОЛНОСТЬЮ , как кто - то предложил) после разговора с друзьями в Microsoft?

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

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

Кот в сапогах
источник
1

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

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

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

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.

Мехмет
источник
0

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

Как @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, а также моим требованиям. Если вы хотите увидеть рабочий образец или демонстрацию этого решения, пожалуйста, оставьте в комментариях, и я был бы рад собрать его вместе.

Parth Shah
источник
0

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

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

Тхакур Рок
источник