Jak prawidłowo obsługiwać 404 w ASP.NET MVC?


432

Używam RC2

Korzystanie z routingu adresów URL:

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

Powyższe wydaje się zajmować takimi żądaniami (przy założeniu domyślnej konfiguracji tabel tras przez początkowy projekt MVC): „/ bla / bla / bla / bla”

Przesłanianie HandleUnknownAction () w samym kontrolerze:

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

Jednak poprzednie strategie nie obsługują żądania do złego / nieznanego kontrolera. Na przykład nie mam „/ IDoNotExist”, jeśli o to poproszę, otrzymam ogólną stronę 404 z serwera WWW, a nie moją 404, jeśli użyję routingu + przesłonięcia.

Wreszcie moje pytanie brzmi: czy jest jakiś sposób na złapanie tego typu żądania przy użyciu trasy lub czegoś innego w samym środowisku MVC?

LUB czy powinienem po prostu domyślnie używać WebConfig customErrors jako mojego modułu obsługi 404 i zapomnieć o tym wszystkim? Zakładam, że jeśli pójdę z customErrors, będę musiał przechowywać ogólną stronę 404 poza / Views ze względu na ograniczenia Web.Config dotyczące bezpośredniego dostępu.


3
to błąd 404, po prostu nie zawracałbym sobie tym głowy. niech wyświetli 404. jako zdecydowanie użytkownik coś źle wpisał. lub jeśli jest to coś, co zostało przeniesione, twoja aplikacja powinna przyjąć to żądanie i dokonać przekierowania na stałe. 404 należy do serwera WWW, a nie aplikacji. zawsze możesz dostosować strony iis pod kątem błędów.
mamu

możesz również przyjrzeć się temu rozwiązaniu blog.dantup.com/2009/04/…
Developer

ben.onfabrik.com/posts/aspnet-mvc-custom-error-pages również ma kilka dobrych informacji
Chris S

4
Szkoda, że ​​4 stabilne wydania później i ponad 5 lat później sytuacja w obsłudze 404 w asp.net MVC + IIS tak naprawdę nie uległa poprawie i wciąż jest to kwestia pytań i odpowiedzi na temat tego, jak sobie z tym poradzić.
joelmdev

Odpowiedzi:


271

Kod pochodzi z http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx i działa również w ASP.net MVC 1.0

Oto jak obsługuję wyjątki 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
aktualizacja: sprawdzanie http 404 jest zdecydowanie wymagane, ale wciąż nie jestem pewien, kiedy otrzymasz 500. musisz też jawnie ustawić Response.StatusCode = 404 lub 500, w przeciwnym razie Google zacznie indeksować te strony jeśli
zwracany jest

6
@Simon_Weaver: uzgodniono! Musi to zwrócić 404 kody stanu. Zasadniczo jest zepsuty jako rozwiązanie 404, dopóki tak nie jest. Sprawdź to: codinghorror.com/blog/2007/03/…
Matt Kocaj

1
Z całą tą sugestią wiąże się podstawowa wada - do czasu, gdy wykonanie pojawiło się na Global.asax, brakuje zbyt wiele HttpContext. Nie można przekierowywać z powrotem do kontrolerów, jak sugeruje to przykład. Odwołaj się do komentarzy w łączu na blogu u góry.
Matt Kocaj

3
Jak wspomniano w niektórych komentarzach powyżej i w powiązanym poście, wydaje się, że to nie działa. Kontroler błędów został trafiony, ale powraca pusty ekran. (using mvc 3)
RyanW

4
coś nie jest w porządku, głównym celem MVC jest usunięcie całej tej abstrakcji, a jednak znowu jest ...
Alex Nolasco

255

Wymagania dla 404

Oto moje wymagania dotyczące rozwiązania 404, a poniżej pokazuję, jak go wdrożyć:

  • Chcę poradzić sobie z dopasowanymi trasami przy złych działaniach
  • Chcę obsługiwać dopasowane trasy ze złymi kontrolerami
  • Chcę obsłużyć niedopasowane trasy (dowolne adresy URL, których moja aplikacja nie może zrozumieć) - nie chcę, aby pojawiały się one na Global.asax lub IIS, ponieważ wtedy nie mogę poprawnie przekierować z powrotem do mojej aplikacji MVC
  • Chcę sposób obsługiwać w taki sam sposób jak powyżej, niestandardowe 404 - na przykład, gdy identyfikator jest przesyłany dla obiektu, który nie istnieje (może zostać usunięty)
  • Chcę wszystkie moje błędy 404 powrót widok na MVC (nie strona statyczna), do którego mogę pompować więcej danych później, jeśli to konieczne ( dobry 404 projekty ) i oni muszą wrócić HTTP kod stanu 404

Rozwiązanie

Myślę, że powinieneś oszczędzać Application_Errorw Global.asax na wyższe rzeczy, takie jak nieobsługiwane wyjątki i logowanie (jak pokazuje odpowiedź Shaya Jacoby'ego ), ale nie obsługę 404. Właśnie dlatego moja sugestia trzyma 404 rzeczy poza plikiem Global.asax.

Krok 1: Wspólne miejsce dla logiki błędu 404

To dobry pomysł na łatwość konserwacji. Użyj narzędzia ErrorController , aby przyszłe ulepszenia dobrze zaprojektowanej strony 404 można było łatwo dostosować. Upewnij się również, że twoja odpowiedź ma kod 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
}

Krok 2: Użyj podstawowej klasy kontrolera, aby łatwo wywołać niestandardową akcję 404 i połączyć się HandleUnknownAction

404 w ASP.NET MVC trzeba złapać w wielu miejscach. Pierwszy to HandleUnknownAction.

InvokeHttp404Metoda tworzy wspólne miejsce do ponownego trasy do ErrorControlleri naszej nowej Http404akcji. Pomyśl o SUCHYM !

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
}

Krok 3: Użyj wstrzykiwania zależności w fabryce sterowników i podłącz 404 wyjątki HTTP

Podobnie (nie musi to być StructureMap):

Przykład 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;
    }
}

Przykład 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;
    }

Myślę, że lepiej jest wychwytywać błędy bliżej ich źródła. Dlatego wolę powyższe niż przewodnik Application_Error.

To drugie miejsce na złapanie 404.

Krok 4: Dodaj trasę NotFound do Global.asax dla adresów URL, które nie mogą zostać przeanalizowane w Twojej aplikacji

Ta trasa powinna wskazywać na nasze Http404działanie. Zauważ, że urlparametr będzie względnym adresem URL, ponieważ silnik routingu usuwa część domeny tutaj? Właśnie dlatego mamy całą tę logikę warunkowego adresu URL w kroku 1.

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

To trzecie i ostatnie miejsce do złapania 404 w aplikacji MVC, której sam nie wywołujesz. Jeśli nie złapiesz niedopasowanych tras tutaj, MVC przekaże problem do ASP.NET (Global.asax) i tak naprawdę nie chcesz tego w tej sytuacji.

Krok 5: W końcu wywołaj 404s, gdy aplikacja nie może czegoś znaleźć

Na przykład, gdy zły identyfikator jest przesyłany do mojego kontrolera pożyczek (pochodzi od 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);
    }

Byłoby miło, gdyby wszystko to można było podłączyć w mniejszej liczbie miejsc z mniejszym kodem, ale myślę, że to rozwiązanie jest łatwiejsze w utrzymaniu, bardziej testowalne i dość pragmatyczne.

Dziękujemy za opinie do tej pory. Chciałbym dostać więcej.

UWAGA: Zostało to znacznie zmienione z mojej oryginalnej odpowiedzi, ale cel / wymagania są takie same - dlatego nie dodałem nowej odpowiedzi


12
Dziękuję za pełny napis. Jednym z dodatków jest to, że podczas uruchamiania w IIS7 należy dodać ustawić właściwość „TrySkipIisCustomErrors” na true. W przeciwnym razie IIS nadal zwróci domyślną stronę 404. Dodaliśmy Response.TrySkipIiisCustomErrors = true; po wierszu w kroku 5, który ustawia kod stanu. msdn.microsoft.com/en-us/library/…
Rick

1
@Ryan customErrorsSekcja web.config definiuje statyczne strony przekierowujące, które są obsługiwane na wysokim poziomie w aspnet, jeśli nie IIS. To nie jest to, czego chciałem, ponieważ potrzebowałem, aby wynikiem były Widoki MVC (więc mogę mieć w nich dane itp.). Nie powiedziałbym kategorycznie, że „ customErrorsjest przestarzałe w MVC”, ale dla mnie i dla tego rozwiązania 404 na pewno są.
Matt Kocaj,

1
Ponadto, czy ktoś może zaktualizować Krok 3, aby StructureMap nie był używany? Może po prostu ogólny ControllerFactory, który będzie łatwy do wdrożenia, jeśli jeszcze nie korzystasz z ControllerFactory.
David Murdoch,

7
Działa to dobrze dla MVC3. Zamiast tego przełączyłem się ObjectFactory.GetInstancena MVC3, DependencyResolver.Current.GetServicewięc jest bardziej ogólny. Używam Ninject.
kamranicus

122
Czy ktokolwiek uważa za szalenie szalone, że tak powszechna sprawa, jak 404 w frameworku internetowym, jest tak cholernie skomplikowana.
quentin-starin

235

ASP.NET MVC nie obsługuje zbyt dobrze niestandardowych stron 404. Fabryka kontrolerów niestandardowych, trasa uniwersalna, podstawowa klasa kontrolerów z HandleUnknownAction- argh!

Niestandardowe strony błędów IIS są jak dotąd lepszą alternatywą:

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

Przykładowy projekt


38
TO POWINIEN BYĆ PRZYJMOWANA ODPOWIEDŹ !!! Działa doskonale na ASP.NET MVC 3 z IIS Express.
Andrei Rînea

7
Jeśli używasz IIS7 +, jest to zdecydowanie najlepsza droga. +1!
elo80ka

3
czy możliwe jest zwrócenie tylko statusu 404, jeśli pracujesz w JSON w ramach tego samego projektu?
VinnyG,

6
Działa to świetnie w iis express, ale jak tylko wdrożę witrynę w IIS produkcyjnym 7.5, otrzymuję tylko białą stronę zamiast widoku błędu.
Moulde,

2
Według moich testów (z MVC3) psuje customErrors mode="On"się to, HandleErrorAttributeaby działać. Niestandardowe strony błędów dla nieobsługiwanych wyjątków w działaniach kontrolera nie są już wyświetlane.
Slauma

153

Szybka odpowiedź / TL; DR

wprowadź opis zdjęcia tutaj

Dla leniwych ludzi:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Następnie usuń ten wiersz z global.asax

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

Dotyczy to tylko IIS7 + i IIS Express.

Jeśli używasz Cassini ... cóż ... hmm ... niezręcznie ... niezręczny


Długa, wyjaśniona odpowiedź

Wiem, że na to odpowiedziano. Ale odpowiedź jest NAPRAWDĘ PROSTA (wiwaty dla Davida Fowlera i Damiana Edwardsa za naprawdę udzielenie odpowiedzi).

Nie trzeba robić nic niestandardowego .

Dla ASP.NET MVC3 wszystkie bity są tam.

Krok 1 -> Zaktualizuj swój plik web.config w DWÓCH miejscach.

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

i

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

Teraz zwróć uwagę na TRASY, które zdecydowałem się użyć. Możesz użyć wszystkiego, ale moje trasy są

  • /NotFound <- dla 404 nie znaleziono, strona błędu.
  • /ServerError<- w przypadku każdego innego błędu dołącz błędy, które występują w moim kodzie. jest to wewnętrzny błąd serwera 500

Zobacz, jak pierwsza sekcja <system.web>ma tylko jeden wpis niestandardowy? statusCode="404"Wpis? Wymieniłem tylko jeden kod stanu, ponieważ wszystkie inne błędy, w tym 500 Server Error(np. Te brzydkie błędy, które występują, gdy kod zawiera błąd i powoduje awarię żądania użytkownika) .. wszystkie pozostałe błędy są obsługiwane przez ustawienie defaultRedirect="/ServerError".. które mówi , jeśli nie znaleziono strony 404, proszę przejść do trasy /ServerError.

Ok. to jest na uboczu .. teraz do moich tras wymienionych wglobal.asax

Krok 2 - Tworzenie tras w Global.asax

Oto mój pełny odcinek trasy ..

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

To pokazuje dwie trasy ignorowania -> axd'si favicons(ooo! Bonus ignoruj ​​trasę, dla ciebie!) Następnie (i kolejność jest IMPERATIVE TUTAJ), mam dwie wyraźne trasy obsługi błędów .. a następnie wszelkie inne trasy. W takim przypadku domyślny. Oczywiście mam ich więcej, ale jest to coś specjalnego na mojej stronie. Upewnij się tylko, że trasy błędów znajdują się na górze listy. Porządek jest konieczny .

Wreszcie, gdy jesteśmy w naszym global.asaxpliku, NIE rejestrujemy globalnie atrybutu HandleError. Nie, nie, nie proszę pana. Nadda Nie. Nien. Negatywny. Nieeeeeeee ...

Usuń tę linię z global.asax

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

Krok 3 - Utwórz kontroler za pomocą metod działania

Teraz .. dodajemy kontroler z dwiema metodami działania ...

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

Ok, sprawdźmy to. Przede wszystkim, nie ma NO [HandleError] atrybut tutaj. Dlaczego? Ponieważ wbudowanyASP.NET platforma już obsługuje błędy ORAZ określiliśmy wszystkie gówna, które musimy zrobić, aby obsłużyć błąd :) Jest w tej metodzie!

Następnie mam dwie metody działania. Nic trudnego. Jeśli chcesz wyświetlić informacje o wyjątku, możesz użyć Server.GetLastError()tej informacji.

Dodatkowy WTF: Tak, podjąłem trzecią metodę działania, aby przetestować obsługę błędów.

Krok 4 - Utwórz widoki

Na koniec utwórz dwa widoki. Umieść je w normalnym miejscu widoku dla tego kontrolera.

wprowadź opis zdjęcia tutaj

Komentarze do bonusów

  • Nie potrzebujesz Application_Error(object sender, EventArgs e)
  • Powyższe kroki działają w 100% idealnie z Elmah . Elmah piekący wokry!

I to, moi przyjaciele, powinno być to.

Gratulacje za przeczytanie tak dużo i zdobycie Jednorożca jako nagrody!

wprowadź opis zdjęcia tutaj


Więc próbowałem zaimplementować to, ale kilka problemów ... po pierwsze, potrzebujesz ~ przed ścieżką weeb.config lub to nie działa dla katalogów wirtualnych. 2-Jeśli uruchomią się niestandardowe błędy IIS, a widok używa układu, którego w ogóle nie renderuje, tylko biała strona. Rozwiązałem to, dodając ten wiersz do kontrolera „Response.TrySkipIisCustomErrors = true;” . Jednak nadal nie działa, jeśli przejdziesz do adresu URL, który jest plikiem, ale 404 .. jak mysite / cokolwiek / fake.html dostaje białą stronę.
Robert Noack

3
-1, przepraszam, dla mnie każde rozwiązanie, które zmienia adres URL dla 404 jest złe. a dzięki webconfig nie ma sposobu w MVC, abyś mógł go obsłużyć bez zmiany adresu URL, lub musisz utworzyć statyczne pliki HTML lub aspx (tak, zwykłe stare pliki aspx), aby móc to zrobić. Twoje rozwiązanie jest w porządku, jeśli chcesz ?aspxerrorpath=/er/not/foundmieć w adresach URL.
Gutek

7
Ta potęga brzmi naprawdę dziwne - ale moja odpowiedź była warunkiem wieki temu i zgadzam się z @Gutek, Nie lubię robić przekierowanie na stronę błędu już . Kiedyś (patrz moja odpowiedź: P). Jeśli błąd wystąpił na / some / resource .., to TEN zasób powinien zwrócić 404 lub 500, itp. MASYWNE implikacje SEO inaczej. Achh ... jak czasy się zmieniają :)
Pure.Krome

@Gutek Czy wiesz o customErrors redirectMode = "ResponseRewrite"? A zwracanie 404 nie jest idealne z punktu widzenia bezpieczeństwa
Jowen

1
@Chris <wstaw tutaj swoje ulubione bóstwo> cholera. Nie pamiętam nawet, co było teraz. Cóż, moja kolekcja memów na ratunek ... i ... naprawiona.
Pure.Krome

86

Sprawdziłem DUŻO, jak prawidłowo zarządzać 404 w MVC (a konkretnie MVC3) , i to, IMHO jest najlepszym rozwiązaniem, jakie wymyśliłem:

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

(Opcjonalny)

Wyjaśnienie:

AFAIK, istnieje 6 różnych przypadków, w których aplikacje ASP.NET MVC3 mogą generować 404.

(Automatycznie generowane przez ASP.NET Framework :)

(1) Adres URL nie znajduje dopasowania w tabeli tras.

(Automatycznie generowane przez ASP.NET MVC Framework :)

(2) Adres URL znajduje dopasowanie w tabeli tras, ale określa nieistniejący kontroler.

(3) Adres URL znajduje dopasowanie w tabeli tras, ale określa nieistniejące działanie.

(Generowane ręcznie :)

(4) Akcja zwraca HttpNotFoundResult przy użyciu metody HttpNotFound ().

(5) Akcja zgłasza wyjątek HttpException o kodzie stanu 404.

(6) Działania ręcznie modyfikują właściwość Response.StatusCode na 404.

Zwykle chcesz osiągnąć 3 cele:

(1) Pokaż użytkownikowi niestandardową stronę błędu 404.

(2) Zachowaj kod statusu 404 w odpowiedzi klienta (szczególnie ważne dla SEO).

(3) Wyślij odpowiedź bezpośrednio, bez udziału przekierowania 302.

Istnieją różne sposoby osiągnięcia tego celu:

(1)

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

Problemy z tym rozwiązaniem:

  1. Nie jest zgodny z celem (1) w przypadkach (1), (4), (6).
  2. Automatycznie nie spełnia celu (2). Musi zostać zaprogramowany ręcznie.
  3. Nie jest zgodny z celem (3).

(2)

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

Problemy z tym rozwiązaniem:

  1. Działa tylko w IIS 7+.
  2. Nie jest zgodny z celem (1) w przypadkach (2), (3), (5).
  3. Automatycznie nie spełnia celu (2). Musi zostać zaprogramowany ręcznie.

(3)

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

Problemy z tym rozwiązaniem:

  1. Działa tylko w IIS 7+.
  2. Automatycznie nie spełnia celu (2). Musi zostać zaprogramowany ręcznie.
  3. Zasłania wyjątki http na poziomie aplikacji. Np. Nie można użyć sekcji customErrors, System.Web.Mvc.HandleErrorAttribute itp. Nie można wyświetlić tylko ogólnych stron błędów.

(4)

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

i

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

Problemy z tym rozwiązaniem:

  1. Działa tylko w IIS 7+.
  2. Automatycznie nie spełnia celu (2). Musi zostać zaprogramowany ręcznie.
  3. Nie jest zgodny z celem (3) w przypadkach (2), (3), (5).

Ludzie, którzy mieli z tym problem, nawet próbowali stworzyć własne biblioteki (patrz http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Ale poprzednie rozwiązanie wydaje się obejmować wszystkie przypadki bez złożoności korzystania z zewnętrznej biblioteki.


Świetna odpowiedź. Godny wielu innych pozytywnych opinii. Dlaczego Twój kod global.asax nie działa / należy do błędu_pliku_aplikacji.
NinjaNye

7
Dzięki! Nie można tego zrobić w Application_Error, ponieważ jawne 404 wyrzucone z kontrolera nie są uważane za błędy w ASP.NET. Jeśli zwrócisz HttpNotFound () z kontrolera, zdarzenie Application_Error nigdy się nie uruchomi.
Marco

1
Myślę, że zapomniałeś public ActionResult NotFound() {}w swoim ErrorsController. Czy możesz również wyjaśnić, jak wyglądałaby Twoja _NotFoundczęściowa dla żądań AJAX?
d4n3

2
Z MVC 4 zawsze wchodzę MissingMethodException: Cannot create an abstract classna linię c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Jakieś pomysły?
µBio

1
Jeśli adres URL „nie znaleziono” zawiera kropkę na ścieżce (np. Przyklad.com/hi.bob ), wówczas Application_EndRequest w ogóle się nie uruchamia, a ja otrzymuję ogólną stronę 404 przeglądarki IE.
Bob.at.Indigo.Health

13

Naprawdę podoba mi się rozwiązanie Cottsaks i myślę, że jest bardzo jasno wyjaśnione. moim jedynym dodatkiem była zmiana kroku 2 w następujący sposób

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
}

Zasadniczo powstrzymuje to adresy URL zawierające nieprawidłowe działania ORAZ kontrolery przed dwukrotnym uruchomieniem procedury wyjątku. np. dla adresów URL takich jak asdfsdf / dfgdfgd


4
To jest doskonałe. Te „podwójne” przypadki zaczęły mnie niepokoić. zaktualizowałem moją odpowiedź
Matt Kocaj,

czy powyższe rozwiązanie w ogóle działa, jeśli użytkownik wpisze niewłaściwy kontroler i nazwę działania?
Monojit Sarkar

6

Jedynym sposobem, w jaki mogłem zmusić metodę @ cottsak do pracy z nieprawidłowymi kontrolerami, było zmodyfikowanie istniejącego żądania trasy w CustomControllerFactory, tak jak to:

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

Powinienem wspomnieć, że używam MVC 2.0.


Wiesz dlaczego? (Specyficzny dla MVC2?)
Matt Kocaj

Myślę, że kluczem było zmodyfikowanie istniejącego żądania, a nie utworzenie nowego, ale zrobiłem to chwilę temu, więc nie jestem pewien, czy to było to. „InvokeHttp404” nie działało w fabryce kontrolerów.
Dave K

Zaktualizowałem dziś swoją odpowiedź o niektóre szczegóły dotyczące MVC2. Czy możesz mi powiedzieć, czy moje opisane powyżej rozwiązanie nadal nie działa dla Ciebie?
Matt Kocaj,

4

Oto kolejna metoda wykorzystująca narzędzia MVC, które mogą obsługiwać żądania dotyczące złych nazw kontrolerów, złych nazw tras i wszelkich innych kryteriów, które uważasz za odpowiednie w metodzie akcji. Osobiście wolę unikać jak największej liczby ustawień web.config, ponieważ powodują one przekierowanie 302/200 i nie obsługują ResponseRewrite (Server.Transfer ) przy użyciu widoków Razor. Wolę zwrócić 404 z niestandardową stroną błędu z powodów SEO.

Niektóre z nich to nowe spojrzenie na powyższą technikę Cottsaka.

To rozwiązanie wykorzystuje także minimalne ustawienia web.config faworyzujące filtry błędów MVC 3.

Stosowanie

Wystarczy rzucić HttpException z akcji lub niestandardowego ActionFilterAttribute.

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

Krok 1

Dodaj następujące ustawienie do pliku web.config. Jest to wymagane, aby użyć HandleErrorAttribute MVC.

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

Krok 2

Dodaj niestandardowy HandleHttpErrorAttribute podobny do HandleErrorAttribute środowiska MVC, z wyjątkiem błędów 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

Krok 3

Dodaj filtry do GlobalFilterCollection ( GlobalFilters.Filters) w Global.asax. Ten przykład przekieruje wszystkie błędy InternalServerError (500) do widoku współdzielonego Błąd ( Views/Shared/Error.vbhtml). Błędy NotFound (404) zostaną również wysłane do ErrorHttp404.vbhtml również w widokach udostępnionych. Dodałem tutaj błąd 401, aby pokazać, jak można go rozszerzyć o dodatkowe kody błędów HTTP. Pamiętaj, że muszą to być wspólne widoki i wszystkie używają System.Web.Mvc.HandleErrorInfoobiektu jako modelu.

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

Krok 4

Utwórz podstawową klasę kontrolera i dziedzicz po niej w swoich kontrolerach. Ten krok pozwala nam obsłużyć nieznane nazwy akcji i podnieść błąd HTTP 404 do naszego 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

Krok 5

Utwórz przesłonięcie ControllerFactory i zastąp go w pliku Global.asax w Application_Start. Ten krok pozwala nam zgłosić wyjątek HTTP 404, gdy określono niepoprawną nazwę kontrolera.

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)

Krok 6

Uwzględnij specjalną trasę w swoim RoutTable.Routes dla działania BaseController Nieznany. Pomoże nam to podnieść 404 w przypadku, gdy użytkownik uzyska dostęp do nieznanego kontrolera lub nieznanej akcji.

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

Podsumowanie

W tym przykładzie pokazano, jak można użyć frameworka MVC do zwrócenia 404 kodów błędów HTTP do przeglądarki bez przekierowania przy użyciu atrybutów filtru i wspólnych widoków błędów. Pokazuje także tę samą niestandardową stronę błędu, gdy określono nieprawidłowe nazwy kontrolera i nazwy akcji.

Dodam zrzut ekranu z nieprawidłową nazwą kontrolera, nazwą akcji i niestandardowym 404 podniesionym z akcji Home / TriggerNotFound, jeśli otrzymam wystarczającą liczbę głosów, aby opublikować jedną =). Fiddler zwraca komunikat 404, gdy uzyskuję dostęp do następujących adresów URL za pomocą tego rozwiązania:

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

powyższy post Cottsaka i artykuły te stanowiły dobre referencje.


Hmm, nie mogłem tego uruchomić: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- jakieś pomysły, dlaczego to dostałem?
enashnash

redirectMode = "ResponseRedirect". Zwróci to 302 znalezionych + 200 OK, co nie jest dobre dla SEO!
PussInBoots

4

Moje skrócone rozwiązanie, które działa z nieobsługiwanymi obszarami, kontrolerami i akcjami:

  1. Utwórz widok 404.cshtml.

  2. Utwórz klasę bazową dla swoich kontrolerów:

    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. Utwórz niestandardową fabrykę kontrolera zwracającą kontroler podstawowy jako rezerwowy:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
  4. Dodaj do Application_Start()następującego wiersza:

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

3

W MVC4 WebAPI 404 można obsługiwać w następujący sposób:

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

KONTROLER DOMOWY

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

WIDOK

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

ŚWIATOWY

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

WYNIKI

wprowadź opis zdjęcia tutaj


2

Wypróbuj NotFoundMVC na nugecie. Działa, bez konfiguracji.


http://localhost/Views/Shared/NotFound.cshtmlnie powoduje utworzenia niestandardowej strony 404.
Dan Friedman,

Bardzo łatwo go dostosować. Masz dostęp do żądanego adresu URL i strony polecającej, dzięki czemu możesz robić, co chcesz. Korzystam z tego pakietu i działa naprawdę dobrze.
Avrohom Yisroel

To świetny pakiet, pod warunkiem, że nie będziesz używać asynchronicznych akcji Task <ActionResult> (lub innych podobnych akcji asynchronicznych). W MVC 5 jest to zepsuty scenariusz. Na GitHub jest widelec do obejścia tego, ale dla mnie to nie, nie.
Stargazer,

2

Moje rozwiązanie, na wypadek, gdyby ktoś uzna to za przydatne.

W Web.config:

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

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

Dodaj a PageNotFound.cshtmldo Sharedfolderu i to wszystko.


2
Czy to nie powoduje przekierowania 302, a następnie statusu 200 (OK) do klienta? Czy nie powinni oni nadal uzyskiwać statusu 404?
Sam

@Konamiman Czy jesteś pewien, że wiersz w kodzie powinien być czytany, model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;a nie model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(i zamiast &&)?
Jean-François Beauchamp

2

Wydaje mi się, że standardowa CustomErrorskonfiguracja powinna jednak po prostu działać , ponieważ poleganie na Server.Transferniej wydaje się, że wewnętrzna implementacja ResponseRewritenie jest kompatybilna z MVC.

Wydaje mi się to rażącą dziurą w funkcjonalności, dlatego postanowiłem zaimplementować tę funkcję za pomocą modułu HTTP. Poniższe rozwiązanie umożliwia obsługę dowolnego kodu stanu HTTP (w tym 404) poprzez przekierowanie na dowolną prawidłową trasę MVC, tak jak normalnie.

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

Zostało to przetestowane na następujących platformach;

  • MVC4 w zintegrowanym trybie potokowym (IIS Express 8)
  • MVC4 w trybie klasycznym (VS Development Server, Cassini)
  • MVC4 w trybie klasycznym (IIS6)

Korzyści

  • Ogólne rozwiązanie, które można upuścić w dowolnym projekcie MVC
  • Włącza obsługę tradycyjnej niestandardowej konfiguracji błędów
  • Działa zarówno w trybie zintegrowanego rurociągu, jak i klasycznym

Rozwiązanie

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

        }


    }

}

Stosowanie

Uwzględnij to jako końcowy moduł HTTP w pliku 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>

Dla tych, którzy zwracają uwagę, zauważysz, że w trybie zintegrowanego potoku zawsze będzie to odpowiadać HTTP 200 ze względu na sposób Server.TransferRequestdziałania. Aby zwrócić prawidłowy kod błędu, używam następującego kontrolera błędów.

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

Radzenie sobie z błędami w ASP.NET MVC to tylko problem w tyłku. Wypróbowałem wiele sugestii na tej stronie oraz na innych pytaniach i stronach i nic nie działa dobrze. Jedna z sugestii dotyczyła obsługi błędów w pliku web.config w systemie system.webserver, ale to po prostu zwraca puste strony .

Moim celem przy opracowywaniu tego rozwiązania było:

  • NIE ZREDYTOWANE
  • Zwraca PRAWIDŁOWE KODY STATUSU nie 200 / Ok jak domyślna obsługa błędów

Oto moje rozwiązanie.

1. Dodaj następujące elementy do sekcji 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>

Powyższe obsługuje wszystkie adresy URL, które nie są obsługiwane przez route.config i nieobsługiwane wyjątki, szczególnie te napotkane w widokach. Zauważ, że użyłem aspx a nie HTML . Dzięki temu mogę dodać kod odpowiedzi do kodu znajdującego się za nim.

2 . Utwórz folder o nazwie Błąd (lub cokolwiek wolisz) w katalogu głównym projektu i dodaj dwa formularze internetowe. Poniżej znajduje się moja strona 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>

A na kodzie ustawiłem kod odpowiedzi

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

Zrób to samo dla strony 500

3. Aby obsłużyć błędy w kontrolerach. Można to zrobić na wiele sposobów. To działało dla mnie. Wszystkie moje kontrolery dziedziczą od kontrolera podstawowego. W kontrolerze podstawowym mam następujące metody

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. Dodaj plik CustomError.cshtml do folderu Udostępnione widoki. Poniżej jest moje;

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

Teraz w kontrolerze aplikacji możesz zrobić coś takiego;

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

Teraz ostrzeżenie . Nie obsłuży błędów statycznych plików. Więc jeśli masz trasę, taką jak example.com/widgets, a użytkownik zmieni ją na example.com/widgets.html , otrzyma domyślną stronę błędów IIS, więc będziesz musiał obsługiwać błędy poziomu IIS w inny sposób.


1

Publikowanie odpowiedzi, ponieważ mój komentarz był zbyt długi ...

Jest to zarówno komentarz, jak i pytania do postu / odpowiedzi jednorożca:

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

Wolę tę odpowiedź od innych ze względu na jej prostotę i fakt, że najwyraźniej skonsultowano się z niektórymi ludźmi w firmie Microsoft. Mam jednak trzy pytania i jeśli można na nie odpowiedzieć, to nazywam tę odpowiedź świętym Graalem wszystkich odpowiedzi na błędy 404/500 w interwebach dla aplikacji ASP.NET MVC (x).

@ Pure.Krome

  1. Czy możesz zaktualizować swoją odpowiedź pozycjami SEO z komentarzy wskazanych przez GWB (nigdy nie wspomniałem o tym w odpowiedzi) - <customErrors mode="On" redirectMode="ResponseRewrite">i <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Czy możesz zapytać znajomych z zespołu ASP.NET, czy można to zrobić w ten sposób - byłoby miło mieć jakieś potwierdzenie - może to wielka nie-nie, aby zmienić redirectModei existingResponsew ten sposób móc ładnie grać z SEO ?!

  3. Można dodać kilka wyjaśnień otaczającą wszystkie rzeczy ( customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", wyjąć customErrorscałkowicie, jak ktoś sugerował) po rozmowie do znajomych na Microsoft?

Jak mówiłem; byłoby supernice, gdybyśmy mogli uzupełnić twoją odpowiedź, ponieważ wydaje się to dość popularne pytanie z ponad 54 000 wyświetleń.

Aktualizacja : odpowiedź Unicorn powoduje odnalezienie 302 i 200 OK i nie można jej zmienić tak, aby zwracała tylko 404 przy użyciu trasy. Musi to być plik fizyczny, który nie jest zbyt MVC: ish. Przejdźmy więc do innego rozwiązania. Szkoda, ponieważ wydaje się, że jest to ostateczna MVC: jak dotąd odpowiedź.


1

Dodanie mojego rozwiązania, które jest prawie identyczne z rozwiązaniem Hermana Kana, z niewielką zmarszczką, aby umożliwić jego działanie w moim projekcie.

Utwórz niestandardowy kontroler błędów:

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

Następnie utwórz niestandardową fabrykę kontrolera:

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

Na koniec dodaj zastąpienie do niestandardowego kontrolera błędów:

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

I to wszystko. Nie ma potrzeby wprowadzania zmian w pliku Web.config.


1

1) Utwórz abstrakcyjną klasę kontrolera.

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) Dziedzicz tę klasę abstrakcyjną we wszystkich kontrolerach

public class HomeController : MyController
{}  

3) I dodaj widok o nazwie „NotFound” w folderze Udostępniony widok.


0

Przejrzałem większość rozwiązań opublikowanych w tym wątku. Chociaż to pytanie może być stare, wciąż ma ono zastosowanie do nowych projektów, więc spędziłem sporo czasu, czytając odpowiedzi przedstawione tutaj, a także gdzie indziej.

Kiedy @Marco wskazał różne przypadki, w których może wystąpić 404, sprawdziłem rozwiązanie, które razem skompilowałem z tą listą. Oprócz jego listy wymagań dodałem jeszcze jedną.

  • Rozwiązanie powinno obsługiwać połączenia MVC oraz AJAX / WebAPI w najbardziej odpowiedni sposób. (tzn. jeśli 404 dzieje się w MVC, powinien wyświetlić stronę Nie znaleziono, a jeśli 404 dzieje się w WebAPI, nie powinien przechwytywać odpowiedzi XML / JSON, aby konsumujący Javascript mógł ją łatwo przeanalizować).

To rozwiązanie jest 2-krotnie:

Pierwsza część pochodzi z @Guillaume na https://stackoverflow.com/a/27354140/2310818 . Ich rozwiązanie zajmuje się wszystkimi 404, które zostały spowodowane z powodu nieprawidłowej trasy, nieprawidłowego kontrolera i nieprawidłowego działania.

Chodzi o utworzenie formularza internetowego, a następnie wywołanie akcji NotFound kontrolera błędów MVC. Robi to wszystko bez żadnego przekierowania, więc nie zobaczysz ani jednego 302 w Fiddler. Oryginalny URL jest również zachowany, co czyni to rozwiązanie fantastycznym!


Druga część pochodzi z @ Germán na https://stackoverflow.com/a/5536676/2310818 . Ich rozwiązanie zajmuje się każdym 404 zwróconym przez twoje działania w postaci HttpNotFoundResult () lub zgłasza nowy HttpException ()!

Chodzi o to, aby filtr spojrzeć na odpowiedź, a także wyjątek zgłoszony przez kontrolery MVC i wywołać odpowiednią akcję w kontrolerze błędów. Ponownie to rozwiązanie działa bez przekierowań, a oryginalny adres URL zostaje zachowany!


Jak widać, oba te rozwiązania razem oferują bardzo solidny mechanizm obsługi błędów i spełniają wszystkie wymagania wymienione przez @Marco, a także moje wymagania. Jeśli chcesz zobaczyć działającą próbkę lub wersję demonstracyjną tego rozwiązania, zostaw komentarz i chętnie go zbiorę.


0

Przejrzałem wszystkie artykuły, ale nic dla mnie nie działa: mój użytkownik wymagający wpisania czegokolwiek na niestandardowej stronie 404 adresu URL powinien się wyświetlić. Myślałem, że jest to bardzo proste. Ale powinieneś zrozumieć obsługę 404 poprawnie:

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

Uważam, że ten artykuł jest bardzo pomocny. Powinien być czytany od razu. Strona błędu klienta - Ben Foster

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.