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

Я использую RC2

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

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

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

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

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

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

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

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

Ответы [ 19 ]

262 голосов
/ 07 марта 2009

Код взят из 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));
}
252 голосов
/ 05 апреля 2010

Требования к 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);
    }

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

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

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

232 голосов
/ 01 апреля 2011

ASP.NET MVC не очень хорошо поддерживает пользовательские страницы 404. Фабрика пользовательских контроллеров, универсальный маршрут, базовый класс контроллеров с 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();
    }
}

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

153 голосов
/ 21 сентября 2011

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

enter image description here

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

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

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

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

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

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


Long, explained answer

I know this has been answered. But the answer is REALLY SIMPLE (cheers to Дэвид Фаулер и Дамиан Эдвардс за искренний ответ на этот вопрос).

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

Для 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 не найдено, страница ошибки. </li>
  • /ServerError <- для любой другой ошибки, включите ошибки, которые происходят в моем коде. это 500 внутренняя ошибка сервера </li>

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

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

Наконец, пока мы находимся в нашем файле 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");
    }
}

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

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

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

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

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

enter image description here

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

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

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

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

enter image description here

84 голосов
/ 27 января 2012

Я исследовал ЛОТ о том, как правильно управлять 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 могут генерировать 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). Но предыдущее решение, похоже, охватывает все случаи без сложности использования внешней библиотеки.

13 голосов
/ 30 мая 2010

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

6 голосов
/ 30 июля 2010

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

4 голосов
/ 01 ноября 2011

Вот еще один метод, использующий инструменты 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

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

4 голосов
/ 23 мая 2014

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

  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 голосов
/ 08 мая 2012

В 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>

GLOBAL

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

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