Wie kann ich mit 404 in ASP.NET MVC richtig umgehen?


432

Ich benutze RC2

Verwenden des URL-Routings:

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

Das Obige scheint sich um Anfragen wie diese zu kümmern (unter der Annahme, dass Standard-Routentabellen vom ersten MVC-Projekt eingerichtet wurden): "/ blah / blah / blah / blah"

Überschreiben von HandleUnknownAction () im Controller selbst:

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

Die vorherigen Strategien verarbeiten jedoch keine Anforderung an einen fehlerhaften / unbekannten Controller. Zum Beispiel habe ich kein "/ IDoNotExist". Wenn ich dies anfordere, erhalte ich die generische 404-Seite vom Webserver und nicht meine 404, wenn ich Routing + Override verwende.

Schließlich lautet meine Frage: Gibt es eine Möglichkeit, diese Art von Anforderung über eine Route oder etwas anderes im MVC-Framework selbst abzufangen?

ODER sollte ich standardmäßig Web.Config customErrors als meinen 404-Handler verwenden und all dies vergessen? Ich gehe davon aus, dass ich bei Verwendung von customErrors die generische 404-Seite aufgrund der Einschränkungen von Web.Config für den direkten Zugriff außerhalb von / Views speichern muss.


3
Es ist ein 404-Fehler, ich würde mich einfach nicht darum kümmern. Lassen Sie es 404 anzeigen. Als definitiv hat der Benutzer etwas falsch geschrieben. oder wenn es etwas ist, das verschoben wurde, sollte Ihre Anwendung diese Anforderung annehmen und permanent umleiten. 404 gehört zum Webserver nicht zur Anwendung. Sie können diese Seiten jederzeit für Fehler anpassen.
Mamu

Sie können sich auch diese Lösung ansehen blog.dantup.com/2009/04/…
Entwickler


4
Es ist eine Schande, dass 4 stabile Releases später und mehr als 5 Jahre später die Situation für den Umgang mit 404s in asp.net MVC + IIS nicht wirklich verbessert hat und dies immer noch die Frage und Antwort ist, wie man damit umgeht.
Joelmdev

Antworten:


271

Der Code stammt von http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx und funktioniert auch in ASP.net MVC 1.0

So gehe ich mit http-Ausnahmen um:

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
Update: Die Suche nach einem http 404 ist definitiv erforderlich, aber ich bin mir immer noch nicht ganz sicher, wann Sie jemals einen 500 bekommen würden. Außerdem müssen Sie den Response.StatusCode = 404 oder 500 explizit festlegen. Andernfalls beginnt Google mit der Indizierung dieser Seiten Wenn Sie einen 200-Statuscode zurückgeben, den dieser Code derzeit
ausführt

6
@ Simon_Weaver: vereinbart! Dies muss 404 Statuscodes zurückgeben. Es ist im Wesentlichen als 404-Lösung kaputt, bis es tut. Überprüfen Sie dies: Kodierunghorror.com/blog/2007/03/…
Matt Kocaj

1
Diesem Vorschlag liegt ein Fehler zugrunde - bis die Ausführung auf Global.asax übergegangen ist, fehlt zu viel HttpContext. Sie können nicht wie im Beispiel vorgeschlagen in Ihre Controller zurückleiten. Beachten Sie die Kommentare im Blog-Link oben.
Matt Kocaj

3
Wie einige der obigen Kommentare und der verlinkte Beitrag erwähnen, scheint dies nicht zu funktionieren. Der Fehlercontroller wird getroffen, aber ein leerer Bildschirm wird zurückgegeben. (mit MVC 3)
RyanW

4
etwas fühlt sich nicht richtig an, der ganze Zweck von MVC ist es, all diese Abstraktion zu entfernen und doch ist es wieder da ...
Alex Nolasco

255

Anforderungen an 404

Das Folgende sind meine Anforderungen für eine 404-Lösung und unten zeige ich, wie ich sie implementiere:

  • Ich möchte übereinstimmende Routen mit schlechten Aktionen behandeln
  • Ich möchte übereinstimmende Routen mit schlechten Controllern behandeln
  • Ich möchte nicht übereinstimmende Routen verarbeiten (beliebige URLs, die meine App nicht verstehen kann). Ich möchte nicht, dass diese auf Global.asax oder IIS übertragen werden, da ich dann nicht richtig in meine MVC-App umleiten kann
  • Ich möchte eine Möglichkeit, benutzerdefinierte 404s wie oben beschrieben zu behandeln - beispielsweise wenn eine ID für ein Objekt gesendet wird, das nicht vorhanden ist (möglicherweise gelöscht).
  • Ich möchte, dass alle meine 404 eine MVC-Ansicht (keine statische Seite) zurückgeben, auf die ich später bei Bedarf weitere Daten pumpen kann ( gute 404-Designs ), und sie müssen den HTTP 404-Statuscode zurückgeben

Lösung

Ich denke, Sie sollten Application_Errorin der Global.asax für höhere Dinge wie nicht behandelte Ausnahmen und Protokollierung (wie Shay Jacobys Antwort zeigt) speichern, aber nicht für die 404-Behandlung. Aus diesem Grund hält mein Vorschlag das 404-Material aus der Global.asax-Datei heraus.

Schritt 1: Haben Sie einen gemeinsamen Platz für die 404-Fehlerlogik

Dies ist eine gute Idee für die Wartbarkeit. Verwenden Sie einen ErrorController, damit sich zukünftige Verbesserungen an Ihrer gut gestalteten 404-Seite problemlos anpassen lassen. Auch stellen Sie sicher , Ihre Antwort hat den 404 - Code !

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
}

Schritt 2: Verwenden Sie eine Basis-Controller-Klasse, damit Sie Ihre benutzerdefinierte 404-Aktion einfach aufrufen und verkabeln können HandleUnknownAction

404s in ASP.NET MVC müssen an mehreren Stellen abgefangen werden. Der erste ist HandleUnknownAction.

Die InvokeHttp404Methode schafft einen gemeinsamen Ort für die Umleitung zur ErrorControllerund unserer neuen Http404Aktion. Denken Sie trocken !

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
}

Schritt 3: Verwenden Sie Dependency Injection in Ihrer Controller Factory und verbinden Sie 404 HttpExceptions

So (es muss nicht StructureMap sein):

MVC1.0-Beispiel:

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

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

Ich denke, es ist besser, Fehler näher an ihrem Ursprungsort zu erkennen. Deshalb ziehe ich das Obige dem Application_ErrorHandler vor.

Dies ist der zweite Ort, um 404s zu fangen.

Schritt 4: Fügen Sie Global.asax eine NotFound-Route für URLs hinzu, die nicht in Ihre App analysiert werden können

Diese Route sollte auf unser Http404Handeln hinweisen . Beachten Sie, dass der urlParameter eine relative URL ist, da die Routing-Engine den Domain-Teil hier entfernt? Deshalb haben wir in Schritt 1 all diese bedingte URL-Logik.

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

Dies ist der dritte und letzte Ort, an dem Sie 404s in einer MVC-App abfangen können, die Sie nicht selbst aufrufen. Wenn Sie hier keine nicht übereinstimmenden Routen abfangen, leitet MVC das Problem an ASP.NET (Global.asax) weiter, und das möchten Sie in dieser Situation nicht wirklich.

Schritt 5: Rufen Sie abschließend 404s auf, wenn Ihre App etwas nicht finden kann

Zum Beispiel, wenn eine schlechte ID an meinen Loans Controller gesendet wird (abgeleitet von 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);
    }

Es wäre schön, wenn all dies an weniger Stellen mit weniger Code verbunden werden könnte, aber ich denke, diese Lösung ist wartbarer, testbarer und ziemlich pragmatisch.

Vielen Dank für das bisherige Feedback. Ich würde gerne mehr bekommen.

HINWEIS: Dies wurde erheblich von meiner ursprünglichen Antwort geändert, aber der Zweck / die Anforderungen sind dieselben - aus diesem Grund habe ich keine neue Antwort hinzugefügt


12
Vielen Dank für die vollständige Beschreibung. Eine Ergänzung ist, dass Sie unter IIS7 die Eigenschaft "TrySkipIisCustomErrors" auf true setzen müssen. Andernfalls gibt IIS weiterhin die Standardseite 404 zurück. Wir haben Response.TrySkipIiisCustomErrors = true hinzugefügt. nach der Zeile in Schritt 5, in der der Statuscode festgelegt wird. msdn.microsoft.com/en-us/library/…
Rick

1
@Ryan Der customErrorsAbschnitt der Datei web.config definiert statische Umleitungsseiten, die in aspnet auf hoher Ebene behandelt werden, wenn nicht in IIS. Dies ist nicht das, was ich wollte, da ich das Ergebnis MVC Views haben musste (damit ich Daten darin haben kann usw.). Ich würde nicht kategorisch sagen, dass " customErrorsin MVC veraltet ist", aber für mich und diese 404-Lösung sind sie es mit Sicherheit.
Matt Kocaj

1
Kann jemand auch Schritt 3 aktualisieren, damit StructureMap nicht verwendet wird? Vielleicht nur eine generische ControllerFactory, die einfach zu implementieren ist, wenn Sie noch keine ControllerFactory verwenden.
David Murdoch

7
Dies funktioniert gut für MVC3. Ich ObjectFactory.GetInstancehabe DependencyResolver.Current.GetServicestattdessen zu MVC3 gewechselt, damit es allgemeiner ist. Ich benutze Ninject.
Kamranicus

122
Findet es sonst noch jemand offensichtlich verrückt, dass so etwas wie 404 in einem Webframework so verdammt kompliziert ist?
Quentin-Starin

235

ASP.NET MVC unterstützt benutzerdefinierte 404-Seiten nicht sehr gut. Benutzerdefinierte Controller-Fabrik, Sammelroute, Basis-Controller-Klasse mit HandleUnknownAction- argh!

Benutzerdefinierte IIS-Fehlerseiten sind bisher eine bessere Alternative:

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

Beispielprojekt


38
Dies sollte die akzeptierte Antwort gewesen sein !!! Funktioniert hervorragend unter ASP.NET MVC 3 mit IIS Express.
Andrei Rînea

7
Wenn Sie IIS7 + verwenden, ist dies definitiv der richtige Weg. +1!
elo80ka

3
Ist es möglich, nur den Status 404 zurückzugeben, wenn Sie in JSON innerhalb desselben Projekts arbeiten?
VinnyG

6
Dies funktioniert in iis express hervorragend, aber sobald ich die Site in Production IIS 7.5 bereitstelle, erhalte ich nur eine weiße Seite anstelle der Fehleransicht.
Moulde

2
Nach meinen Tests (mit MVC3) bricht dies customErrors mode="On"zusammen mit dem HandleErrorAttribute, um funktionsfähig zu sein. Benutzerdefinierte Fehlerseiten für nicht behandelte Ausnahmen in Controller-Aktionen werden nicht mehr bereitgestellt.
Slauma

153

Schnelle Antwort / TL; DR

Geben Sie hier die Bildbeschreibung ein

Für die faulen Leute da draußen:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Entfernen Sie dann diese Zeile aus global.asax

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

Dies gilt nur für IIS7 + und IIS Express.

Wenn Sie Cassini benutzen ... na ja ... ähm ... umständlich ... peinlich


Lange, erklärte Antwort

Ich weiß, dass dies beantwortet wurde. Aber die Antwort ist WIRKLICH EINFACH (Prost an David Fowler und Damian Edwards wirklich beantwortet haben).

Es ist nicht erforderlich, benutzerdefinierte Aktionen auszuführen .

Zum ASP.NET MVC3 alle Kleinigkeiten sind da.

Schritt 1 -> Aktualisieren Sie Ihre web.config an ZWEI Stellen.

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

und

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

Beachten Sie nun sorgfältig die ROUTEN, für die ich mich entschieden habe. Sie können alles verwenden, aber meine Routen sind

  • /NotFound <- für eine 404 nicht gefunden, Fehlerseite.
  • /ServerError<- Fügen Sie für jeden anderen Fehler Fehler hinzu, die in meinem Code auftreten. Dies ist ein 500 Internal Server Error

Sehen Sie, wie der erste Abschnitt in <system.web>nur einen benutzerdefinierten Eintrag enthält? Der statusCode="404"Eintrag? Ich habe nur einen Statuscode aufgelistet, da alle anderen Fehler, einschließlich des 500 Server Error(dh des lästigen Fehlers, der auftritt, wenn Ihr Code einen Fehler aufweist und die Benutzeranforderung abstürzt) .. alle anderen Fehler von der Einstellung behandelt werden defaultRedirect="/ServerError".. die besagt Wenn Sie keine 404-Seite nicht gefunden haben, gehen Sie bitte zur Route/ServerError .

OK. das ist aus dem Weg .. jetzt zu meinen Routen in aufgeführtglobal.asax

Schritt 2 - Erstellen der Routen in Global.asax

Hier ist mein vollständiger Streckenabschnitt.

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

Darin sind zwei Ignorierrouten aufgeführt -> axd'sund favicons(ooo! Bonus-Ignorierroute für Sie!) Dann (und die Reihenfolge ist HIER IMPERATIV) habe ich meine zwei expliziten Fehlerbehandlungsrouten .. gefolgt von anderen Routen. In diesem Fall die Standardeinstellung. Natürlich habe ich mehr, aber das ist etwas Besonderes für meine Website. Stellen Sie einfach sicher, dass die Fehlerrouten ganz oben auf der Liste stehen. Ordnung ist unerlässlich .

Während wir uns in unserer global.asaxDatei befinden, registrieren wir das HandleError-Attribut NICHT global. Nein, nein, nein, Sir. Nadda. Nee. Nien. Negativ. Noooooooooo ...

Entfernen Sie diese Zeile von global.asax

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

Schritt 3 - Erstellen Sie den Controller mit den Aktionsmethoden

Nun ... fügen wir einen Controller mit zwei Aktionsmethoden hinzu ...

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, lass uns das überprüfen. Erstens gibt es hier KEIN [HandleError] Attribut. Warum? Weil das eingebaut istASP.NET Framework bereits Fehler behandelt UND wir alle Dinge angegeben haben, die wir tun müssen, um einen Fehler zu behandeln :) Es ist in dieser Methode!

Als nächstes habe ich die beiden Aktionsmethoden. Da ist nichts Schwieriges. Wenn Sie Ausnahmeinformationen anzeigen möchten, können Server.GetLastError()Sie diese Informationen abrufen.

Bonus WTF: Ja, ich habe eine dritte Aktionsmethode entwickelt, um die Fehlerbehandlung zu testen.

Schritt 4 - Erstellen Sie die Ansichten

Und schließlich erstellen Sie zwei Ansichten. Setzen Sie sie für diesen Controller in die normale Ansicht.

Geben Sie hier die Bildbeschreibung ein

Bonuskommentare

  • Du brauchst keine Application_Error(object sender, EventArgs e)
  • Die obigen Schritte funktionieren alle zu 100% perfekt mit Elmah . Elmah fraking wroxs!

Und das, meine Freunde, sollte es sein.

Herzlichen Glückwunsch zum Lesen und ein Einhorn als Preis!

Geben Sie hier die Bildbeschreibung ein


Also habe ich versucht, dies zu implementieren, aber ein paar Probleme ... Zuerst benötigen Sie ein ~ vor dem Pfad in weeb.config oder es funktioniert nicht für virtuelle Verzeichnisse. 2-Wenn benutzerdefinierte IIS-Fehler ausgelöst werden und die Ansicht ein Layout verwendet, wird sie überhaupt nicht gerendert, sondern nur eine weiße Seite. Ich habe das gelöst, indem ich diese Zeile in den Controller "Response.TrySkipIisCustomErrors = true;" eingefügt habe. . Es funktioniert jedoch immer noch nicht, wenn Sie zu einer URL gehen, die eine Datei ist, aber 404 .. wie mysite / Whatever / fake.html eine weiße Seite erhält.
Robert Noack

3
-1, sorry, für mich ist jede Lösung, die die URL für 404 ändert, falsch. und mit webconfig gibt es in MVC keine Möglichkeit, damit umzugehen, ohne die URL zu ändern, oder Sie müssen statische HTML-Dateien oder Aspx (ja, einfache alte Aspx-Dateien) erstellen, um dies zu tun. Ihre Lösung ist in Ordnung, wenn Sie ?aspxerrorpath=/er/not/foundin URLs haben möchten.
Gutek

7
Das klingt vielleicht wirklich komisch - aber meine Antwort wurde vor langer Zeit gegeben und ich stimme Ihrem @Gutek zu. Ich mag es nicht mehr, auf eine Fehlerseite umzuleiten . Ich pflegte (siehe meine Antwort: P). Wenn der Fehler bei / some / resource aufgetreten ist, sollte DIESE Ressource 404 oder 500 usw. zurückgeben. Andernfalls MASSIVE SEO-Implikationen. Ahh .. wie sich die Zeiten ändern :)
Pure.Krome

@Gutek Kennen Sie customErrors redirectMode = "ResponseRewrite"? Und die Rückgabe von 404 ist aus Sicherheitsgründen nicht ideal
Jowen

1
@Chris <füge hier deine Lieblingsgottheit ein> verdammt. Ich kann mich nicht einmal erinnern, was es jetzt war. Nun, meine Memesammlung zur Rettung ... und ... behoben.
Pure.Krome

86

Ich habe untersucht A LOT auf , wie man richtig verwalten 404s in MVC (speziell MVC3) , und dies, meiner Meinung nach ist die beste Lösung , die ich habe kommen mit:

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

(Optional)

Erläuterung:

AFAIK, es gibt 6 verschiedene Fälle, in denen eine ASP.NET MVC3-App 404s generieren kann.

(Automatisch generiert von ASP.NET Framework :)

(1) Eine URL findet keine Übereinstimmung in der Routentabelle.

(Automatisch generiert von ASP.NET MVC Framework :)

(2) Eine URL findet eine Übereinstimmung in der Routentabelle, gibt jedoch einen nicht vorhandenen Controller an.

(3) Eine URL findet eine Übereinstimmung in der Routentabelle, gibt jedoch eine nicht vorhandene Aktion an.

(Manuell generiert :)

(4) Eine Aktion gibt ein HttpNotFoundResult mit der Methode HttpNotFound () zurück.

(5) Eine Aktion löst eine HttpException mit dem Statuscode 404 aus.

(6) Durch eine Aktion wird die Response.StatusCode-Eigenschaft manuell in 404 geändert.

Normalerweise möchten Sie drei Ziele erreichen:

(1) Zeigen Sie dem Benutzer eine benutzerdefinierte 404-Fehlerseite an.

(2) Pflegen Sie den 404-Statuscode in der Kundenantwort (besonders wichtig für SEO).

(3) Senden Sie die Antwort direkt, ohne dass eine 302-Umleitung erforderlich ist.

Es gibt verschiedene Möglichkeiten, dies zu erreichen:

(1)

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

Probleme mit dieser Lösung:

  1. Entspricht nicht dem Ziel (1) in den Fällen (1), (4), (6).
  2. Entspricht Ziel (2) nicht automatisch. Es muss manuell programmiert werden.
  3. Entspricht nicht dem Ziel (3).

(2)

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

Probleme mit dieser Lösung:

  1. Funktioniert nur unter IIS 7+.
  2. Entspricht nicht dem Ziel (1) in den Fällen (2), (3), (5).
  3. Entspricht Ziel (2) nicht automatisch. Es muss manuell programmiert werden.

(3)

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

Probleme mit dieser Lösung:

  1. Funktioniert nur unter IIS 7+.
  2. Entspricht Ziel (2) nicht automatisch. Es muss manuell programmiert werden.
  3. Es verdeckt http-Ausnahmen auf Anwendungsebene. Beispielsweise kann der Abschnitt customErrors, System.Web.Mvc.HandleErrorAttribute usw. nicht verwendet werden. Es können nicht nur generische Fehlerseiten angezeigt werden.

(4)

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

und

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

Probleme mit dieser Lösung:

  1. Funktioniert nur unter IIS 7+.
  2. Entspricht Ziel (2) nicht automatisch. Es muss manuell programmiert werden.
  3. Entspricht nicht dem Ziel (3) in den Fällen (2), (3), (5).

Personen, die sich zuvor damit befasst haben, haben sogar versucht, ihre eigenen Bibliotheken zu erstellen (siehe http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Die vorherige Lösung scheint jedoch alle Fälle abzudecken, ohne die Komplexität der Verwendung einer externen Bibliothek.


Gute Antwort. Viele weitere positive Stimmen wert. Warum funktioniert Ihr global.asax-Code nicht in Application_Error?
NinjaNye

7
Vielen Dank! Dies kann unter Application_Error nicht durchgeführt werden, da explizite 404s, die von einem Controller ausgelöst werden, in ASP.NET nicht als Fehler betrachtet werden. Wenn Sie ein HttpNotFound () von einem Controller zurückgeben, wird das Application_Error-Ereignis niemals ausgelöst.
Marco

1
Ich denke du hast es public ActionResult NotFound() {}in deinem ErrorsController vergessen . Können Sie auch erklären, wie Ihr _NotFoundTeil für AJAX-Anfragen aussehen würde?
d4n3

2
Mit MVC 4 bin ich immer MissingMethodException: Cannot create an abstract classauf dem Laufenden. c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Irgendwelche Ideen?
µBio

1
Wenn die URL "nicht gefunden" einen Punkt im Pfad enthält (z. B. example.com/hi.bob ), wird Application_EndRequest überhaupt nicht ausgelöst, und ich erhalte die generische 404-Seite des IE.
Bob.at.Indigo.Health

13

Ich mag Cottsaks Lösung sehr und denke, es ist sehr klar erklärt. Meine einzige Ergänzung bestand darin, Schritt 2 wie folgt zu ändern

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
}

Grundsätzlich verhindert dies, dass URLs mit ungültigen Aktionen UND Controllern die Ausnahmeroutine zweimal auslösen. zB für URLs wie asdfsdf / dfgdfgd


4
Das ist ausgezeichnet. Diese "zweimaligen" Fälle begannen mich zu stören. hat meine Antwort aktualisiert
Matt Kocaj

Funktioniert die oben genannte Lösung überhaupt, wenn der Benutzer einen falschen Controller- und Aktionsnamen eingibt?
Monojit Sarkar

6

Die einzige Möglichkeit, die Methode von @ cottsak für ungültige Controller zum Laufen zu bringen, bestand darin, die vorhandene Routenanforderung in der CustomControllerFactory wie folgt zu ändern:

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

Ich sollte erwähnen, dass ich MVC 2.0 verwende.


Wissen Sie, warum? (MVC2-spezifisch?)
Matt Kocaj

Ich denke, der Schlüssel war, die bestehende Anfrage zu ändern, anstatt eine neue zu erstellen, aber ich habe dies vor einiger Zeit getan, daher bin ich mir nicht sicher, ob es das war. Der "InvokeHttp404" funktionierte ab Controller-Fabrik nicht.
Dave K

Ich habe meine Antwort heute mit einigen MVC2-Details aktualisiert. Können Sie mir bitte sagen, ob meine oben beschriebene Lösung bei Ihnen immer noch nicht funktioniert?
Matt Kocaj

4

Hier ist eine andere Methode, die MVC-Tools verwendet, mit denen Sie Anforderungen an fehlerhafte Controllernamen, fehlerhafte Routennamen und andere Kriterien verarbeiten können, die in eine Aktionsmethode passen. Ich persönlich bevorzuge es, so viele web.config-Einstellungen wie möglich zu vermeiden, da sie die 302/200-Umleitung durchführen und ResponseRewrite nicht unterstützen (Server.Transfer ) mit Razor-Ansichten . Ich würde aus SEO-Gründen lieber einen 404 mit einer benutzerdefinierten Fehlerseite zurückgeben.

Einiges davon ist eine neue Version der obigen Technik von Cottsak.

Diese Lösung verwendet auch minimale web.config-Einstellungen, die stattdessen die MVC 3-Fehlerfilter bevorzugen.

Verwendungszweck

Wirf einfach eine HttpException aus einer Aktion oder einem benutzerdefinierten ActionFilterAttribute.

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

Schritt 1

Fügen Sie Ihrer web.config die folgende Einstellung hinzu. Dies ist erforderlich, um das HandleErrorAttribute von MVC zu verwenden.

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

Schritt 2

Fügen Sie ein benutzerdefiniertes HandleHttpErrorAttribute hinzu, das dem HandleErrorAttribute des MVC-Frameworks ähnelt, mit Ausnahme von HTTP-Fehlern:

<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

Schritt 3

Fügen Sie der GlobalFilterCollection ( GlobalFilters.Filters) in Filter hinzu Global.asax. In diesem Beispiel werden alle InternalServerError (500) -Fehler an die gemeinsam genutzte Fehleransicht ( Views/Shared/Error.vbhtml) weitergeleitet. NotFound (404) -Fehler werden auch in den freigegebenen Ansichten an ErrorHttp404.vbhtml gesendet. Ich habe hier einen 401-Fehler hinzugefügt, um Ihnen zu zeigen, wie dieser für zusätzliche HTTP-Fehlercodes erweitert werden kann. Beachten Sie, dass dies gemeinsam genutzte Ansichten sein müssen und alle das System.Web.Mvc.HandleErrorInfoObjekt als Modell verwenden.

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

Schritt 4

Erstellen Sie eine Basis-Controller-Klasse und erben Sie diese in Ihren Controllern. Dieser Schritt ermöglicht es uns, unbekannte Aktionsnamen zu behandeln und den HTTP 404-Fehler auf unser HandleHttpErrorAttribute zu setzen.

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

Schritt 5

Erstellen Sie eine ControllerFactory-Überschreibung und überschreiben Sie sie in Ihrer Global.asax-Datei in Application_Start. Mit diesem Schritt können wir die HTTP 404-Ausnahme auslösen, wenn ein ungültiger Controllername angegeben wurde.

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)

Schritt 6

Fügen Sie eine spezielle Route in Ihre RoutTable ein. Routen für die Aktion "BaseController unbekannt". Dies hilft uns, einen 404 auszulösen, wenn ein Benutzer auf einen unbekannten Controller oder eine unbekannte Aktion zugreift.

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

Zusammenfassung

In diesem Beispiel wurde gezeigt, wie mit dem MVC-Framework 404 HTTP-Fehlercodes ohne Umleitung mithilfe von Filterattributen und freigegebenen Fehleransichten an den Browser zurückgegeben werden können. Es wird auch gezeigt, dass dieselbe benutzerdefinierte Fehlerseite angezeigt wird, wenn ungültige Controllernamen und Aktionsnamen angegeben werden.

Ich werde einen Screenshot eines ungültigen Controllernamens, eines Aktionsnamens und eines benutzerdefinierten 404 hinzufügen, der aus der Aktion Home / TriggerNotFound ausgelöst wurde, wenn ich genügend Stimmen bekomme, um eine zu posten =). Fiddler gibt eine 404-Nachricht zurück, wenn ich mit dieser Lösung auf die folgenden URLs zugreife:

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

cottsaks Beitrag oben und diese Artikel waren gute Referenzen.


Hmm, ich konnte das nicht zum Laufen bringen: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- Irgendwelche Ideen, warum ich das bekommen würde?
Enashnash

redirectMode = "ResponseRedirect". Dies gibt einen 302 Found + einen 200 OK zurück, was für SEO nicht gut ist!
PussInBoots

4

Meine verkürzte Lösung, die mit nicht behandelten Bereichen, Controllern und Aktionen funktioniert:

  1. Erstellen Sie eine Ansicht 404.cshtml.

  2. Erstellen Sie eine Basisklasse für Ihre Controller:

    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. Erstellen Sie eine benutzerdefinierte Controller-Factory, die Ihren Basis-Controller als Fallback zurückgibt:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
  4. Fügen Sie Application_Start()der folgenden Zeile hinzu:

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

3

In MVC4 kann WebAPI 404 folgendermaßen behandelt werden:

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

HOME CONTROLLER

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

AUSSICHT

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

GLOBAL

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

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

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

ERGEBNISSE

Geben Sie hier die Bildbeschreibung ein


2

Versuchen Sie NotFoundMVC auf Nuget. Es funktioniert, kein Setup.


http://localhost/Views/Shared/NotFound.cshtmlführt nicht zu einer benutzerdefinierten 404-Seite.
Dan Friedman

Es ist sehr einfach anzupassen. Sie haben Zugriff auf die angeforderte URL und den Referrer, sodass Sie tun können, was Sie möchten. Ich benutze dieses Paket und es funktioniert wirklich gut.
Avrohom Yisroel

Dies ist ein großartiges Paket, vorausgesetzt, Sie verwenden keine asynchronen Task <ActionResult> -Aktionen (oder ähnliche asynchrone Aktionen). Unter MVC 5 ist dies ein fehlerhaftes Szenario. Es gibt eine Gabelung auf GitHub, um das zu umgehen, aber für mich ist es ein Nein, nein.
Stargazer

2

Meine Lösung für den Fall, dass jemand sie nützlich findet.

In Web.config:

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

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

Fügen Sie ein PageNotFound.cshtmlin den SharedOrdner, und das war's.


2
Gibt dies nicht eine 302-Umleitung und dann einen 200-Status (OK) an den Client aus? Sollten sie nicht immer noch einen 404-Status erhalten?
Sam

@Konamiman Sind Sie sicher, dass die Zeile in Ihrem Code gelesen werden sollte model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;und nicht model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(& anstelle von &&)?
Jean-François Beauchamp

2

Es scheint mir , dass die Standard - CustomErrorsKonfiguration soll nur arbeiten jedoch aufgrund der Abhängigkeit von der Server.Transferes scheint , dass die interne Umsetzung derResponseRewrite nicht kompatibel mit MVC ist.

Dies fühlt sich für mich wie eine eklatante Funktionslücke an, daher habe ich beschlossen, diese Funktion mithilfe eines HTTP-Moduls erneut zu implementieren. Mit der folgenden Lösung können Sie jeden HTTP-Statuscode (einschließlich 404) verarbeiten, indem Sie wie gewohnt zu einer gültigen MVC-Route umleiten.

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

Dies wurde auf den folgenden Plattformen getestet.

  • MVC4 im integrierten Pipeline-Modus (IIS Express 8)
  • MVC4 im klassischen Modus (VS Development Server, Cassini)
  • MVC4 im klassischen Modus (IIS6)

Leistungen

  • Generische Lösung, die in jedes MVC-Projekt eingefügt werden kann
  • Aktiviert die Unterstützung für die herkömmliche Konfiguration benutzerdefinierter Fehler
  • Funktioniert sowohl im integrierten Pipeline- als auch im klassischen Modus

Die Lösung

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

        }


    }

}

Verwendungszweck

Fügen Sie dies als letztes HTTP-Modul in Ihre web.config ein

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

Wenn Sie aufpassen, werden Sie feststellen, dass dies im integrierten Pipeline-Modus aufgrund der Funktionsweise immer mit HTTP 200 reagiert Server.TransferRequest. Um den richtigen Fehlercode zurückzugeben, verwende ich den folgenden Fehlercontroller.

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

Der Umgang mit Fehlern in ASP.NET MVC ist nur ein Problem. Ich habe viele Vorschläge auf dieser Seite und auf anderen Fragen und Websites ausprobiert und nichts funktioniert gut. Ein Vorschlag war, Fehler in web.config in system.webserver zu behandeln, die jedoch nur leere Seiten zurückgeben .

Mein Ziel bei der Entwicklung dieser Lösung war:

  • NICHT UMLEITEN
  • Geben Sie PROPER STATUS CODES nicht 200 / Ok wie bei der Standardfehlerbehandlung zurück

Hier ist meine Lösung.

1. Fügen Sie dem Abschnitt system.web Folgendes hinzu

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

Im obigen Abschnitt werden alle URLs behandelt, die nicht von route.config behandelt werden, sowie nicht behandelte Ausnahmen, insbesondere solche, die in den Ansichten auftreten. Beachten Sie, dass ich aspx nicht html verwendet habe . Auf diese Weise kann ich dem Code dahinter einen Antwortcode hinzufügen .

2 . Erstellen Sie im Stammverzeichnis Ihres Projekts einen Ordner mit dem Namen " Fehler" (oder was auch immer Sie bevorzugen) und fügen Sie die beiden Webformulare hinzu. Unten ist meine 404 Seite;

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

Und auf dem Code dahinter habe ich den Antwortcode festgelegt

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

Machen Sie dasselbe für die 500-Seite

3. Um Fehler in den Controllern zu behandeln. Es gibt viele Möglichkeiten, dies zu tun. Das hat bei mir funktioniert. Alle meine Controller erben von einem Basis-Controller. Im Basis-Controller habe ich die folgenden Methoden

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 die CustomError.cshtml auf Ihre freigegebenen Ordner Sichten. Unten ist meins;

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

Jetzt können Sie in Ihrem Anwendungscontroller so etwas tun.

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

Nun zum Vorbehalt . Statische Dateifehler werden nicht behandelt. Wenn Sie also eine Route wie example.com/widgets haben und der Benutzer diese in example.com/widgets.html ändert , wird die IIS-Standardfehlerseite angezeigt, sodass Sie Fehler auf IIS-Ebene auf andere Weise behandeln müssen.


1

Eine Antwort posten, da mein Kommentar zu lang war ...

Es ist sowohl ein Kommentar als auch eine Frage zum Einhorn-Beitrag / zur Antwort:

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

Ich bevorzuge diese Antwort gegenüber den anderen, weil sie einfach ist und anscheinend einige Leute bei Microsoft konsultiert wurden. Ich habe jedoch drei Fragen und wenn sie beantwortet werden können, werde ich diese Antwort den heiligen Gral aller 404/500 Fehlerantworten in den Interwebs für eine ASP.NET MVC (x) -App nennen.

@ Pure.Krome

  1. Können Sie Ihre Antwort mit dem SEO-Material aus den Kommentaren von GWB aktualisieren (dies wurde in Ihrer Antwort nie erwähnt) - <customErrors mode="On" redirectMode="ResponseRewrite">und <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Können Sie Ihre ASP.NET-Teamfreunde fragen, ob es in Ordnung ist, dies so zu tun - wäre schön, eine Bestätigung zu haben - vielleicht ist es ein großes Nein-Nein, sich zu ändern redirectModeund existingResponseauf diese Weise in der Lage zu sein, gut mit SEO zu spielen?!

  3. Können Sie einige Klärung rund um das ganze Zeug (addieren customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", REMOVE customErrorsVOLLSTÄNDIG als jemand vorschlug) nach dem Gespräch mit Ihren Freunden bei Microsoft?

Wie ich sagte; Es wäre überragend, wenn wir Ihre Antwort vollständiger machen könnten, da dies eine ziemlich beliebte Frage mit mehr als 54 000 Ansichten zu sein scheint.

Update : Die Antwort von Unicorn führt zu 302 Found und 200 OK und kann nicht geändert werden, um nur 404 über eine Route zurückzugeben. Es muss eine physische Datei sein, die nicht sehr MVC: ish ist. Fahren Sie also mit einer anderen Lösung fort. Schade, denn dies schien die ultimative MVC zu sein: ish Antwort bis jetzt.


1

Hinzufügen meiner Lösung, die fast identisch mit der von Herman Kan ist, mit einer kleinen Falte, damit sie für mein Projekt funktioniert.

Erstellen Sie einen benutzerdefinierten Fehlercontroller:

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

Erstellen Sie dann eine benutzerdefinierte Controller-Factory:

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

Fügen Sie abschließend dem benutzerdefinierten Fehlercontroller eine Überschreibung hinzu:

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

Und das ist es. Web.config muss nicht geändert werden.


1

1) Machen Sie eine abstrakte Controller-Klasse.

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) Vererbung dieser abstrakten Klasse in allen Controllern

public class HomeController : MyController
{}  

3) Fügen Sie in Ihrem View-Shared-Ordner eine Ansicht mit dem Namen "NotFound" hinzu.


0

Ich habe die meisten in diesem Thread veröffentlichten Lösungen durchgesehen. Obwohl diese Frage alt sein mag, ist sie auch jetzt noch für neue Projekte sehr zutreffend. Daher habe ich viel Zeit damit verbracht, die hier und anderswo gestellten Antworten zu lesen.

Als @Marco auf die verschiedenen Fälle hinwies, in denen ein 404 auftreten kann, überprüfte ich die Lösung, die ich zusammengestellt hatte, anhand dieser Liste. Zusätzlich zu seiner Liste der Anforderungen habe ich noch eine hinzugefügt.

  • Die Lösung sollte in der Lage sein, MVC- und AJAX / WebAPI-Aufrufe auf die am besten geeignete Weise zu verarbeiten. (dh wenn 404 in MVC auftritt, sollte die Seite Nicht gefunden angezeigt werden, und wenn 404 in WebAPI auftritt, sollte die XML / JSON-Antwort nicht entführt werden, damit das konsumierende Javascript sie leicht analysieren kann.)

Diese Lösung ist zweifach:

Der erste Teil stammt von @Guillaume unter https://stackoverflow.com/a/27354140/2310818 . Ihre Lösung kümmert sich um alle 404, die aufgrund einer ungültigen Route, eines ungültigen Controllers und einer ungültigen Aktion verursacht wurden.

Die Idee ist, ein WebForm zu erstellen und es dann die NotFound-Aktion Ihres MVC Errors Controllers aufrufen zu lassen. Dies alles ohne Umleitung, sodass Sie in Fiddler keine einzige 302 sehen. Die ursprüngliche URL bleibt ebenfalls erhalten, was diese Lösung fantastisch macht!


Der zweite Teil stammt von @ Germán unter https://stackoverflow.com/a/5536676/2310818 . Ihre Lösung kümmert sich um alle 404, die von Ihren Aktionen in Form von HttpNotFoundResult () zurückgegeben werden, oder löst eine neue HttpException () aus!

Die Idee ist, einen Filter-Blick auf die Antwort sowie die von Ihren MVC-Controllern ausgelöste Ausnahme zu werfen und die entsprechende Aktion in Ihrem Fehler-Controller aufzurufen. Auch diese Lösung funktioniert ohne Weiterleitung und die ursprüngliche URL bleibt erhalten!


Wie Sie sehen können, bieten beide Lösungen zusammen einen sehr robusten Mechanismus zur Fehlerbehandlung und erfüllen alle von @Marco aufgeführten Anforderungen sowie meine Anforderungen. Wenn Sie ein funktionierendes Beispiel oder eine Demo dieser Lösung sehen möchten, hinterlassen Sie bitte in den Kommentaren und ich würde es gerne zusammenstellen.


0

Ich habe alle Artikel durchgesehen, aber nichts funktioniert für mich: Mein Anforderungsbenutzer gibt etwas in Ihre URL ein. Die benutzerdefinierte 404-Seite sollte angezeigt werden. Ich dachte, es ist sehr einfach. Aber Sie sollten den Umgang mit 404 richtig verstehen:

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

Ich fand diesen Artikel sehr hilfreich. Sollte sofort gelesen werden. Kundenspezifische Fehlerseite - Ben Foster

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.