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


432

Я використовую RC2

Використання маршрутизації URL:

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

Наведене вище стосується таких запитів (припускаючи налаштування таблиць маршрутів за замовчуванням за допомогою початкового проекту MVC): "/ blah / blah / blah / blah"

Переопределення HandleUnknownAction () у самому контролері:

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

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

Отже, нарешті, моє запитання таке: чи є спосіб зафіксувати цей тип запиту за допомогою маршруту або чогось іншого в самій структурі 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 також має добру інформацію
Chris S

4
Прикро, що 4 стабільні версії пізніше і більше 5 років ситуація з обробкою 404 в asp.net MVC + IIS насправді не покращилася, і це все ще перехід на запитання та відповіді, як з цим впоратися.
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 - зняти всю цю абстракцію, і все-таки це знову ...
Alex Nolasco

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: Використовуйте базовий клас контролера, щоб ви могли легко викликати свої власні дії 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: Використовуйте введення залежностей на заводі контролера та підключіть 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: Нарешті, запустіть 404, коли ваша програма не може щось знайти

Наприклад, коли неправильний ідентифікатор подається моєму контролеру позик (походить від 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);
    }

Було б добре, якби все це можна було підключити в меншій мірі з меншим кодом, але я думаю, що це рішення є більш ретельним, більш перевіреним і досить прагматичним.

Дякуємо за відгук. Я хотів би отримати більше.

ПРИМІТКА. Це було відредаговано значно з моєї оригінальної відповіді, але мета / вимоги однакові - саме тому я не додав нової відповіді


12
Дякуємо за повне оформлення. Одним із доповнень є те, що при запуску під IIS7 потрібно додати властивість "TrySkipIisCustomErrors" встановити true. Інакше IIS все ще поверне 404 сторінку за замовчуванням. Ми додали Response.TrySkipIiisCustomErrors = true; після рядка на кроці 5, який встановлює код статусу. msdn.microsoft.com/en-us/library/…
Rick

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

Зразок проекту


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! Bonus bonus ignore route, для вас!) Тоді (і замовлення ІМПЕРАТИВНЕ ТУТ), у мене є два явні маршрути щодо обробки помилок. Далі слід будь-які інші маршрути. У цьому випадку за замовчуванням. Звичайно, у мене є більше, але це особливо для мого веб-сайту. Просто переконайтеся, що маршрути помилок знаходяться вгорі списку. Порядок обов'язковий .

Нарешті, перебуваючи всередині нашого global.asaxфайлу, ми НЕ глобально реєструємо атрибут HandleError. Ні, ні, ні пане. Надя. Ні. Нієн. Негативний. 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] атрибута NO . Чому? Тому що вбудованийASP.NET рамка вже обробляє помилки І ми вказали все лайно, яке нам потрібно зробити, щоб впоратися з помилкою :) Це в цьому методі!

Далі я маю два способи дії. Нічого жорсткого там немає. Якщо ви хочете показати будь-яку інформацію про виключення, тоді ви можете скористатись Server.GetLastError()цією інформацією.

Бонус WTF: Так, я застосував третій метод дій, щоб перевірити обробку помилок.

Крок 4 - Створіть подання

І нарешті, створіть два погляди. Помістіть em у звичайне місце для цього контролера.

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

Бонусні коментарі

  • Вам не потрібен Application_Error(object sender, EventArgs e)
  • Наведені вище кроки всі працюють на 100% ідеально з Elmah . Elmah розбиваючи ангели!

І це, друзі, повинно бути так.

Тепер, привітання, щоб прочитати це багато і мати Єдиноріг як приз!

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


Тому я спробував реалізувати це, але декілька проблем ... по-перше, вам потрібен ~ до шляху в weeb.config або він не працює для віртуальних каталогів. 2 - Якщо користувальницькі помилки IIS запускаються, а представлення використовує макет, він взагалі не відображається, а лише біла сторінка. Я вирішив це, додавши цей рядок у контролер "Response.TrySkipIisCustomErrors = true;" . Однак це все ще не працює, якщо ви переходите до URL-адреси, який є файлом, але 404 .. як mysite / what / fake.html отримує білу сторінку.
Роберт Ноак

3
-1, вибачте, для мене будь-яке рішення, яке робить URL-адресу зміни 404, є неправильним. а з webconfig у MVC немає можливості обробити це, не змінюючи URL, або вам потрібно створити статичні HTML-файли або aspx (так, звичайні старі файли aspx), щоб мати можливість це зробити. ваше рішення добре, якщо ви хочете ?aspxerrorpath=/er/not/foundмати URL-адреси.
Гутек

7
Це може звучить дуже дивно - але моя відповідь була надано давним - давно , і я згоден з вашим @Gutek, я не люблю робити редирект на сторінку помилки більше . Я звик (посилання на свою відповідь: P). Якщо помилка сталася на / деякому / ресурсі .., ЦЕ ресурс повинен повернути 404 або 500 і т.д. Ааа .. як змінюються часи :)
Pure.Krome

@Gutek Чи знаєте ви customErrors redirectMode = "ResponseRewrite"? І повернення 404-х не є ідеальним з точки зору безпеки
Джовен

1
@Chris <вставити сюди улюблене божество> чорт забирай. Я навіть не пам'ятаю, що це було зараз. Що ж, моя колекція мемів на допомогу ... і .. виправлена.
Pure.Krome

86

Я досліджував ЛОТ щодо того, як правильно керувати 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 ). Але попереднє рішення, схоже, охоплює всі випадки без складності використання зовнішньої бібліотеки.


Чудова відповідь. Варто ще багатьох подій. Чому ваш код 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-адреса "не знайдено" містить крапку в шляху (наприклад, 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
Це чудово. Ті "двічі" справи почали мене турбувати. оновив мою відповідь
Matt 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) до спільного перегляду помилок ( 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, піднятого в результаті дії "Дія" / "TriggerNotFound", якщо я наберу достатньо голосів, щоб опублікувати один =). Fiddler повертає повідомлення 404, коли я отримую доступ до таких URL-адрес, використовуючи це рішення:

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

Повідомлення коттсака вище, і ці статті були хорошими посиланнями.


Гм, я не міг змусити це працювати: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- якісь ідеї, чому я це отримаю?
енашнаш

redirectMode = "ResponseRedirect". Це поверне 302 знайдених + 200 ОК, що не годиться для 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 можна обробляти таким чином,

КУРСИ АПІКОНТРОЛЛЕР

    // 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-адреси та до реферала, щоб ви могли робити те, що вам подобається. Я використовую цей пакет, і він працює дуже добре.
Avrohom Yisroel

Це чудовий пакет, за умови, що ви не будете використовувати асинхронні завдання <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папку, і це все .


2
Чи це не видає клієнту переспрямування 302, а потім статус 200 (ОК)? Чи не повинні вони ще отримувати статус 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 Server Development, Cassini)
  • MVC4 в класичному режимі (IIS6)

Переваги

  • Загальне рішення, яке може бути включено до будь-якого проекту MVC
  • Вмикає підтримку традиційної конфігурації помилок
  • Працює в інтегрованому трубопроводі та класичному режимах

Рішення

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

Використання

Включіть це як кінцевий модуль HTTP у свій web.config

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

Для тих, хто звертає увагу, ви помітите, що в режимі інтегрального трубопроводу це завжди відповідатиме 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, але це лише повертає порожні сторінки .

Моєю метою, коли я придумав це рішення, було:

  • НЕ ПОВЕРНЕНО
  • Повертайте коди ПРОПЕРАЦІЙНОГО СТАТУСУ не 200 / Ок, як обробка помилок за замовчуванням

Ось моє рішення.

1. Додайте наступне до розділу system.web

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

Наведене вище обробляє будь-які URL-адреси, які не обробляються route.config, і необроблені винятки, особливо ті, які зустрічаються в представленнях даних. Зауважте, що я використовував aspx not html . Це так, я можу додати код відповіді на код позаду.

2 . Створіть папку під назвою Помилка (або все, що вам більше подобається) в корені проекту та додайте дві веб-форми. Нижче моя сторінка 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 .Add 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+ переглядами.

Оновлення : відповідь Єдиноріг робить OK 302 знайденим і 200, і його не можна змінити, щоб повернути лише 404 за маршрутом. Це має бути фізичний файл, який не дуже MVC: ish. Тож переходимо до іншого рішення. Шкода, тому що це здавалося остаточним MVC: ish відповісти досі.


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) Складіть абстрактний клас контролера.

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, він повинен показувати сторінку Not Found, а якщо 404 трапляється в WebAPI, він не повинен захоплювати відповідь XML / JSON, щоб споживач Javascript міг її легко розібрати).

Цей розчин у два рази:

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


0

Я ознайомився з усіма статтями, але нічого не працює для мене: Моя вимога користувача введіть що-небудь у вашій URL-адресі на користувальницькій сторінці 404. Подумав, що це дуже прямо вперед. Але ви повинні правильно зрозуміти обробку 404:

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

Я знайшов цю статтю дуже корисною. Слід прочитати відразу. Сторінка помилки під вартою - Бен Фостер

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.