Come posso gestire correttamente 404 in ASP.NET MVC?


432

Sto usando RC2

Utilizzando il routing URL:

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

Quanto sopra sembra occuparsi di richieste come questa (presupponendo che le tabelle di route predefinite siano impostate dal progetto MVC iniziale): "/ blah / blah / blah / blah"

Sostituzione di HandleUnknownAction () nel controller stesso:

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

Tuttavia, le strategie precedenti non gestiscono una richiesta a un controller Bad / Unknown. Ad esempio, non ho un "/ IDoNotExist", se lo richiedo ottengo la pagina 404 generica dal server Web e non il mio 404 se utilizzo routing + override.

Quindi, alla fine, la mia domanda è: esiste un modo per intercettare questo tipo di richiesta utilizzando una route o qualcos'altro nel framework MVC stesso?

O dovrei semplicemente usare Web.Config customErrors come il mio gestore 404 e dimenticare tutto questo? Suppongo che se vado con customErrors dovrò memorizzare la pagina 404 generica al di fuori di / Views a causa delle restrizioni di Web.Config sull'accesso diretto.


3
è un errore 404, non me ne preoccuperei. lascialo visualizzare 404. come sicuramente l'utente ha sbagliato a digitare qualcosa. o se si tratta di qualcosa che viene spostato, l'applicazione deve accettare tale richiesta e reindirizzare permanentemente. 404 appartiene al server web non all'applicazione. puoi sempre personalizzare le pagine IIS per errore.
mamu,

puoi dare un'occhiata anche a questa soluzione blog.dantup.com/2009/04/…
Sviluppatore


4
È un peccato che 4 versioni stabili più tardi e più di 5 anni dopo, la situazione per gestire i 404 in asp.net MVC + IIS non sia davvero migliorata e questo è ancora il passo a domande e risposte su come gestirlo.
joelmdev,

Risposte:


271

Il codice è tratto da http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx e funziona anche in ASP.net MVC 1.0

Ecco come gestisco le eccezioni 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
aggiornamento: è sicuramente necessario verificare la presenza di un http 404, ma non sono ancora del tutto sicuro quando avresti mai ottenuto un 500. Inoltre, devi anche impostare esplicitamente Response.StatusCode = 404 o 500 altrimenti Google inizierà a indicizzare queste pagine se si restituisce un codice di stato 200 attualmente utilizzato da questo codice
Simon_Weaver l'

6
@Simon_Weaver: d'accordo! Questo deve restituire 404 codici di stato. È essenzialmente rotto come una soluzione 404 fino a quando non lo fa. Controlla questo: codinghorror.com/blog/2007/03/…
Matt Kocaj

1
C'è un difetto di fondo con tutto questo suggerimento: quando l'esecuzione si è trasformata in Global.asax, manca troppo di HttpContext. Non puoi tornare ai controller come suggerisce l'esempio. Fare riferimento ai commenti nel link del blog in alto.
Matt Kocaj,

3
Come alcuni dei commenti precedenti e del post collegato menzionano, questo non sembra funzionare. Il controller di errore viene colpito, ma viene visualizzata una schermata vuota. (usando mvc 3)
RyanW,

4
qualcosa non sembra giusto, tutto lo scopo di MVC è rimuovere tutta quell'astrazione e tuttavia eccola di nuovo ...
Alex Nolasco,

255

Requisiti per 404

Di seguito sono riportati i miei requisiti per una soluzione 404 e di seguito mostro come la implemento:

  • Voglio gestire percorsi abbinati con azioni sbagliate
  • Voglio gestire percorsi abbinati con controller difettosi
  • Voglio gestire percorsi non corrispondenti (URL arbitrari che la mia app non riesce a capire) - non voglio che questi gorgoglii verso Global.asax o IIS perché non riesco a reindirizzare correttamente nella mia app MVC
  • Voglio un modo per gestire come sopra, 404 personalizzati - come quando viene inviato un ID per un oggetto che non esiste (forse cancellato)
  • Voglio che tutti i miei 404s per tornare una vista MVC (non una pagina statica) a cui posso pompare più dati in seguito, se necessario ( buono 404 disegni ) e che devo restituire il codice di stato 404 HTTP

Soluzione

Penso che dovresti risparmiare Application_Errorin Global.asax per cose più alte, come eccezioni non gestite e registrazione (come mostra la risposta di Shay Jacoby ) ma non una gestione 404. Questo è il motivo per cui il mio suggerimento mantiene le cose 404 fuori dal file Global.asax.

Passaggio 1: disporre di un luogo comune per la logica degli errori 404

Questa è una buona idea per la manutenibilità. Usa un ErrorController in modo che i futuri miglioramenti alla tua pagina 404 ben progettata possano adattarsi facilmente. Inoltre, assicurati che la tua risposta abbia il codice 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
}

Passaggio 2: utilizzare una classe controller di base in modo da poter richiamare facilmente l'azione 404 personalizzata e collegarla HandleUnknownAction

404s in ASP.NET MVC devono essere catturati in diversi punti. Il primo è HandleUnknownAction.

Il InvokeHttp404metodo crea un luogo comune per il reinstradamento verso ErrorControllere la nostra nuova Http404azione. Pensa a SECCO !

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
}

Passaggio 3: utilizzare l'iniezione delle dipendenze nella propria fabbrica di controller e collegare 404 HttpExceptions

In questo modo (non deve essere StructureMap):

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

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

Penso che sia meglio individuare gli errori più vicini alla loro origine. Questo è il motivo per cui preferisco quanto sopra al Application_Errorgestore.

Questo è il secondo posto per catturare 404 secondi.

Passaggio 4: aggiungi una route NotFound a Global.asax per gli URL che non possono essere analizzati nella tua app

Questo percorso dovrebbe puntare alla nostra Http404azione. Notare che il urlparametro sarà un URL relativo perché il motore di routing sta eliminando la parte del dominio qui? Questo è il motivo per cui abbiamo tutta quella logica dell'URL condizionale nel passaggio 1.

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

Questo è il terzo e ultimo posto per catturare 404 in un'app MVC che non invochi. Se non si rilevano percorsi ineguagliati qui, MVC passerà il problema ad ASP.NET (Global.asax) e non lo si desidera davvero in questa situazione.

Passaggio 5: infine, invoca 404s quando l'app non riesce a trovare qualcosa

Come quando un ID errato viene inviato al mio controller di prestito (deriva da 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);
    }

Sarebbe bello se tutto ciò potesse essere collegato in meno posti con meno codice ma penso che questa soluzione sia più mantenibile, più testabile e abbastanza pragmatica.

Grazie per il feedback finora. Mi piacerebbe avere di più.

NOTA: questo è stato modificato in modo significativo dalla mia risposta originale ma lo scopo / i requisiti sono gli stessi - ecco perché non ho aggiunto una nuova risposta


12
Grazie per la recensione completa. Un'aggiunta è che quando si esegue sotto IIS7 è necessario aggiungere impostare la proprietà "TrySkipIisCustomErrors" su true. In caso contrario, IIS restituirà comunque la pagina 404 predefinita. Abbiamo aggiunto Response.TrySkipIiisCustomErrors = true; dopo la riga nel passaggio 5 che imposta il codice di stato. msdn.microsoft.com/en-us/library/…
Rick,

1
@Ryan La customErrorssezione web.config definisce le pagine di reindirizzamento statico che vengono gestite ad alto livello in aspnet se non IIS. Questo non è quello che volevo perché avevo bisogno che il risultato fosse MVC Views (quindi posso avere dei dati in essi ecc.). Non direi categoricamente che " customErrorsè obsoleto in MVC" ma per me e questa soluzione 404 lo sono sicuramente.
Matt Kocaj,

1
Inoltre, qualcuno può aggiornare il passaggio 3 in modo da non utilizzare StructureMap? Forse solo una ControllerFactory generica che sarà facile da implementare se non stai già utilizzando una ControllerFactory.
David Murdoch,

7
Funziona bene con MVC3. Sono passato invece ObjectFactory.GetInstancea MVC3, DependencyResolver.Current.GetServicequindi è più generico. Sto usando Ninject.
kamranicus,

122
Qualcun altro trova palesemente folle che una cosa così comune come i 404 in un framework web sia così dannatamente complicata.
Quentin-starin,

235

ASP.NET MVC non supporta molto bene le 404 pagine personalizzate. Fabbrica controller personalizzati, percorso catch-all, classe controller base con HandleUnknownAction- argh!

Finora le pagine di errore personalizzate IIS sono un'alternativa migliore:

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

Progetto di esempio


38
QUESTA DOVREBBE STATA LA RISPOSTA ACCETTATA !!! Funziona perfettamente su ASP.NET MVC 3 con IIS Express.
Andrei Rînea,

7
Se stai usando IIS7 + questa è sicuramente la strada da percorrere. +1!
elo80ka,

3
è possibile restituire solo uno stato 404 quando si lavora in JSON all'interno dello stesso progetto?
VinnyG,

6
Funziona benissimo in Iis Express, ma non appena distribuisco il sito in IIS 7.5 di produzione, tutto ciò che ottengo è una pagina bianca invece della visualizzazione degli errori.
Moulde,

2
Secondo i miei test (con MVC3) questo si interrompe customErrors mode="On"con il HandleErrorAttributefunzionamento. Le pagine di errore personalizzate per le eccezioni non gestite nelle azioni del controller non vengono più pubblicate.
Slauma,

153

Risposta rapida / TL; DR

inserisci qui la descrizione dell'immagine

Per le persone pigre là fuori:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Quindi rimuovere questa riga da global.asax

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

E questo è solo per IIS7 + e IIS Express.

Se stai usando Cassini .. beh .. um .. er .. imbarazzante ... scomodo


Risposta lunga e spiegata

So che è stata data una risposta. Ma la risposta è DAVVERO SEMPLICE (saluti a David Fowler e Damian Edwards per aver davvero risposto a questa domanda).

Non è necessario fare nulla di personalizzato .

Perché ASP.NET MVC3, tutti i pezzi ci sono.

Passaggio 1:> Aggiorna web.config in DUE spot.

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

e

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

Ora prendi nota degli ITINERARI che ho deciso di utilizzare. Puoi usare qualsiasi cosa, ma i miei percorsi lo sono

  • /NotFound <- per un 404 non trovato, pagina di errore.
  • /ServerError<- per qualsiasi altro errore, includi gli errori che si verificano nel mio codice. questo è un errore del server interno 500

Vedi come la prima sezione in <system.web>ha solo una voce personalizzata? La statusCode="404"voce? Ho elencato un solo codice di stato perché tutti gli altri errori, incluso il 500 Server Error(cioè quegli fastidiosi errori che si verificano quando il tuo codice ha un bug e si blocca la richiesta dell'utente) .. tutti gli altri errori sono gestiti dall'impostazione defaultRedirect="/ServerError".. che dice , se non sei una pagina 404 non trovata, vai al percorso/ServerError .

Ok. questo è fuori mano .. ora per i miei percorsi elencati inglobal.asax

Passaggio 2: creazione dei percorsi in Global.asax

Ecco la mia sezione del percorso completo ..

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

Questo elenca due percorsi ignorati -> axd'se favicons(ooo! Bonus ignora percorso, per te!) Quindi (e l'ordine è IMPERATIVO QUI), ho i miei due percorsi espliciti di gestione degli errori .. seguiti da qualsiasi altro percorso. In questo caso, quello predefinito. Certo, ne ho di più, ma è speciale per il mio sito web. Assicurati solo che i percorsi di errore siano in cima all'elenco. L'ordine è imperativo .

Infine, mentre siamo all'interno del nostro global.asaxfile, NON registriamo globalmente l'attributo HandleError. No, no, no signore. Nadda. No. Nien. Negativo. Noooooooooo ...

Rimuovi questa linea da global.asax

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

Passaggio 3: creare il controller con i metodi di azione

Ora .. aggiungiamo un controller con due metodi di azione ...

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, diamo un'occhiata. Prima di tutto, NON esiste alcun [HandleError]attributo qui. Perché? Perché il ASP.NETframework integrato sta già gestendo gli errori E abbiamo specificato tutta la merda che dobbiamo fare per gestire un errore :) È in questo metodo!

Successivamente, ho i due metodi di azione. Niente di difficile lì. Se desideri mostrare informazioni sull'eccezione, puoi utilizzarle Server.GetLastError()per ottenere tali informazioni.

Bonus WTF: Sì, ho realizzato un terzo metodo di azione per testare la gestione degli errori.

Passaggio 4: creare le viste

E infine, creare due viste. Mettili nel normale punto di vista, per questo controller.

inserisci qui la descrizione dell'immagine

Commenti bonus

  • Non hai bisogno di un Application_Error(object sender, EventArgs e)
  • I passaggi precedenti funzionano perfettamente al 100% con Elmah . Elmah sfilaccia i guai!

E quello, amici miei, dovrebbe essere.

Ora, congratulazioni per aver letto così tanto e avere un Unicorno come premio!

inserisci qui la descrizione dell'immagine


Quindi ho provato ad implementare questo, ma un paio di problemi ... in primo luogo, è necessario un ~ prima del percorso in weeb.config o non funziona per le directory virtuali. 2-Se vengono generati errori personalizzati IIS e la vista utilizza un layout che non viene visualizzato affatto, solo una pagina bianca. Ho risolto ciò aggiungendo questa riga nel controller "Response.TrySkipIisCustomErrors = true;" . Tuttavia, non funziona ancora se vai a un URL che è un file ma 404 .. come mysite / qualunque / fake.html ottiene una pagina bianca.
Robert Noack,

3
-1, scusate, per me qualsiasi soluzione che cambia l'URL per 404 è sbagliata. e con webconfig non c'è modo in MVC di poterlo gestire senza cambiare url, oppure è necessario creare file html statici o aspx (sì, semplici vecchi file aspx) per poterlo fare. la tua soluzione va bene se ti piace ?aspxerrorpath=/er/not/foundavere negli URL.
Gutek,

7
Questo potrebbe sembrare davvero strano - ma la mia risposta è stata fornita tempo fa e sono d'accordo con la vostra @Gutek, non mi piace fare un redirect ad una pagina di errore più . Prima (riferimento alla mia risposta: P). Se l'errore si è verificato su / some / resource .. allora QUELLA risorsa dovrebbe restituire un 404 o 500, ecc. MASSIVE implicazioni SEO altrimenti. Ahh .. come cambiano i tempi :)
Pure.Krome il

@Gutek Sei a conoscenza di customErrors redirectMode = "ResponseRewrite"? E restituire i 404 non è l'ideale dal punto di vista della sicurezza
Jowen,

1
@Chris <inserisci qui la tua divinità preferita> accidenti. Non ricordo nemmeno cosa fosse adesso. Bene, la mia raccolta di meme in soccorso ... e ... risolta.
Pure.Krome,

86

Ho studiato un sacco su come gestire correttamente 404s in MVC (in particolare MVC3) , e questo, secondo me, è la soluzione migliore che è venuta in mente:

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

(Opzionale)

Spiegazione:

AFAIK, ci sono 6 casi diversi in cui un'app ASP.NET MVC3 può generare 404 secondi.

(Generato automaticamente da ASP.NET Framework :)

(1) Un URL non trova una corrispondenza nella tabella del percorso.

(Generato automaticamente da ASP.NET MVC Framework :)

(2) Un URL trova una corrispondenza nella tabella di instradamento, ma specifica un controller inesistente.

(3) Un URL trova una corrispondenza nella tabella di instradamento, ma specifica un'azione inesistente.

(Generato manualmente :)

(4) Un'azione restituisce un HttpNotFoundResult utilizzando il metodo HttpNotFound ().

(5) Un'azione genera un'eccezione HttpException con il codice di stato 404.

(6) Un'azione modifica manualmente la proprietà Response.StatusCode su 404.

Normalmente, vuoi raggiungere 3 obiettivi:

(1) Mostra una pagina di errore 404 personalizzata per l'utente.

(2) Mantenere il codice di stato 404 sulla risposta del cliente (particolarmente importante per la SEO).

(3) Invia la risposta direttamente, senza comportare un reindirizzamento 302.

Esistono vari modi per tentare di ottenere questo risultato:

(1)

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

Problemi con questa soluzione:

  1. Non è conforme all'obiettivo (1) nei casi (1), (4), (6).
  2. Non si conforma automaticamente all'obiettivo (2). Deve essere programmato manualmente.
  3. Non conforme all'obiettivo (3).

(2)

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

Problemi con questa soluzione:

  1. Funziona solo su IIS 7+.
  2. Non è conforme all'obiettivo (1) nei casi (2), (3), (5).
  3. Non si conforma automaticamente all'obiettivo (2). Deve essere programmato manualmente.

(3)

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

Problemi con questa soluzione:

  1. Funziona solo su IIS 7+.
  2. Non si conforma automaticamente all'obiettivo (2). Deve essere programmato manualmente.
  3. Oscura le eccezioni http a livello di applicazione. Ad esempio, non è possibile utilizzare la sezione customErrors, System.Web.Mvc.HandleErrorAttribute, ecc. Non può solo mostrare pagine di errore generiche.

(4)

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

e

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

Problemi con questa soluzione:

  1. Funziona solo su IIS 7+.
  2. Non si conforma automaticamente all'obiettivo (2). Deve essere programmato manualmente.
  3. Non è conforme all'obiettivo (3) nei casi (2), (3), (5).

Le persone che hanno avuto problemi con questo prima ancora hanno provato a creare le proprie librerie (vedi http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Ma la soluzione precedente sembra coprire tutti i casi senza la complessità dell'uso di una libreria esterna.


Bella risposta. Degno di molti più voti. Perché il tuo codice global.asax non funziona / appartiene a Application_Error.
NinjaNye,

7
Grazie! Non può essere eseguito in Application_Error, poiché i 404 espliciti generati da un controller non sono considerati errori su ASP.NET. Se si restituisce un HttpNotFound () da un controller, l'evento Application_Error non si attiverà mai.
Marco,

1
Penso che tu abbia dimenticato public ActionResult NotFound() {}nel tuo ErrorController. Inoltre, puoi spiegare come _NotFoundsarebbe il tuo parziale per le richieste AJAX?
d4n3,

2
Con MVC 4 rimango sempre MissingMethodException: Cannot create an abstract classin linea c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Qualche idea?
µBio

1
Se l'URL "non trovato" include un punto nel percorso (ad esempio esempio.com/hi.bob ), Application_EndRequest non si attiva affatto e ottengo la pagina 404 generica di IE.
Bob.at.Indigo.Salute

13

Mi piace molto la soluzione cottsaks e penso che sia spiegata molto chiaramente. la mia unica aggiunta è stata quella di modificare il passaggio 2 come segue

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
}

Fondamentalmente questo impedisce agli URL contenenti azioni e controller non validi di attivare due volte la routine di eccezione. ad es. per URL come asdfsdf / dfgdfgd


4
Questo è eccellente Quei casi "due volte" stavano iniziando a infastidirmi. aggiornata la mia risposta
Matt Kocaj

la soluzione sopra funziona se l'utente inserisce un controller e un nome azione errati?
Monojit Sarkar,

6

L'unico modo in cui sono riuscito a far funzionare il metodo di @ cottsak per controller non validi è stato modificare la richiesta di percorso esistente in CustomControllerFactory, in questo modo:

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

Dovrei menzionare che sto usando MVC 2.0.


Sai perché? (Specifico per MVC2?)
Matt Kocaj,

Penso che la chiave sia stata modificare la richiesta esistente anziché crearne una nuova, ma l'ho fatto un po 'di tempo fa, quindi non sono sicuro che fosse così. "InvokeHttp404" non funzionava dalla fabbrica del controller.
Dave K,

Ho aggiornato oggi la mia risposta con alcune specifiche MVC2. Potete per favore dirmi se la mia soluzione come sopra descritta non funziona ancora per voi?
Matt Kocaj,

4

Ecco un altro metodo che utilizza gli strumenti MVC che è possibile gestire le richieste a nomi di controller errati, nomi di route errati e qualsiasi altro criterio che ritieni adeguato all'interno di un metodo di azione. Personalmente, preferisco evitare quante più impostazioni web.config possibile, perché eseguono il reindirizzamento 302/200 e non supportano ResponseRewrite (Server.Transfer ) usando le viste Razor. Preferirei restituire un 404 con una pagina di errore personalizzata per motivi SEO.

Parte di questo è una nuova interpretazione della tecnica di cottsak sopra.

Questa soluzione utilizza anche impostazioni web.config minime che favoriscono invece i filtri di errore MVC 3.

uso

Basta lanciare una HttpException da un'azione o ActionFilterAttribute personalizzato.

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

Passo 1

Aggiungi la seguente impostazione al tuo web.config. Ciò è necessario per utilizzare HandleErrorAttribute di MVC.

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

Passo 2

Aggiungere un HandleHttpErrorAttribute personalizzato simile a HandleErrorAttribute del framework MVC, ad eccezione degli errori 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

Passaggio 3

Aggiungi filtri a GlobalFilterCollection ( GlobalFilters.Filters) in Global.asax. Questo esempio indirizzerà tutti gli errori InternalServerError (500) alla vista condivisa Error ( Views/Shared/Error.vbhtml). Gli errori NotFound (404) verranno inviati a ErrorHttp404.vbhtml anche nelle viste condivise. Ho aggiunto un errore 401 qui per mostrarti come può essere esteso per ulteriori codici di errore HTTP. Si noti che queste devono essere viste condivise e usano tutti l' System.Web.Mvc.HandleErrorInfooggetto come modello.

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

Passaggio 4

Crea una classe controller di base ed eredita da essa nei controller. Questo passaggio ci consente di gestire nomi di azioni sconosciute e aumentare l'errore HTTP 404 sul nostro 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

Passaggio 5

Creare un override ControllerFactory e sovrascriverlo nel file Global.asax in Application_Start. Questo passaggio ci consente di sollevare l'eccezione HTTP 404 quando è stato specificato un nome controller non valido.

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)

Passaggio 6

Includi un percorso speciale in RoutTable.Routes per l'azione BaseController Unknown. Questo ci aiuterà a aumentare un 404 nel caso in cui un utente acceda a un controller sconosciuto o ad un'azione sconosciuta.

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

Sommario

Questo esempio ha dimostrato come è possibile utilizzare il framework MVC per restituire 404 codici di errore HTTP al browser senza reindirizzamento utilizzando gli attributi di filtro e le visualizzazioni di errore condivise. Dimostra anche di mostrare la stessa pagina di errore personalizzata quando vengono specificati nomi di controller e nomi di azioni non validi.

Aggiungerò uno screenshot di un nome controller, nome azione non valido e un 404 personalizzato generato dall'azione Home / TriggerNotFound se ottengo abbastanza voti per postarne uno =). Fiddler restituisce un messaggio 404 quando accedo ai seguenti URL usando questa soluzione:

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

Il post di cottsak sopra e questi articoli erano buoni riferimenti.


Hmm, non sono riuscito a far funzionare tutto questo: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- qualche idea sul perché l'avrei ottenuto?
enashnash,

redirectMode = "ResponseRedirect". Questo restituirà un 302 trovato + un 200 OK che non è buono per il SEO!
PussInBoots il

4

La mia soluzione abbreviata che funziona con aree, controller e azioni non gestite:

  1. Crea una vista 404.cshtml.

  2. Crea una classe base per i tuoi 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. Creare una fabbrica di controller personalizzati restituendo il controller di base come fallback:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
  4. Aggiungi alla Application_Start()seguente riga:

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

3

In MVC4 WebAPI 404 può essere gestito nel modo seguente,

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

REGOLATORE DOMESTICO

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

VISUALIZZA

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

GLOBALE

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

RISULTATI

inserisci qui la descrizione dell'immagine


2

Prova NotFoundMVC su nuget. Funziona, nessuna configurazione.


http://localhost/Views/Shared/NotFound.cshtmlnon risulta in una pagina 404 personalizzata.
Dan Friedman,

È molto facile da personalizzare. Hai accesso all'URL richiesto e al referrer, quindi puoi fare quello che ti piace. Uso questo pacchetto e funziona davvero bene.
Avrohom Yisroel,

Questo è un ottimo pacchetto, a condizione che non utilizzerai azioni <ActionResult> dell'operazione asincrona (o altre azioni simili asincrone). Su MVC 5 questo è uno scenario rotto. C'è un fork su GitHub per aggirare questo, ma per me è un no, no.
Stargazer,

2

La mia soluzione, nel caso qualcuno la trovi utile.

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

Aggiungi a PageNotFound.cshtmlnella Sharedcartella e il gioco è fatto.


2
Questo problema non comporta un reindirizzamento 302 e quindi uno stato 200 (OK) al client? Non dovrebbero ancora ottenere uno stato 404?
Sam,

@Konamiman Sei sicuro che la riga del tuo codice debba essere letta model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;e non model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(& invece di &&)?
Jean-François Beauchamp,

2

Mi sembra che la CustomErrorsconfigurazione standard dovrebbe funzionare , tuttavia, a causa della dipendenza da Server.Transferessa sembra che l'implementazione interna diResponseRewrite non sia compatibile con MVC.

Mi sembra un buco evidentemente funzionale, quindi ho deciso di implementare nuovamente questa funzione usando un modulo HTTP. La soluzione seguente consente di gestire qualsiasi codice di stato HTTP (incluso 404) reindirizzando a qualsiasi route MVC valida esattamente come si farebbe normalmente.

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

Questo è stato testato sulle seguenti piattaforme;

  • MVC4 in modalità pipeline integrata (IIS Express 8)
  • MVC4 in modalità classica (VS Development Server, Cassini)
  • MVC4 in modalità classica (IIS6)

Benefici

  • Soluzione generica che può essere rilasciata in qualsiasi progetto MVC
  • Abilita il supporto per la tradizionale configurazione degli errori personalizzati
  • Funziona in modalità pipeline integrata e classica

La soluzione

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

        }


    }

}

uso

Includilo come modulo HTTP finale nel tuo 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>

Per quelli di voi che prestano attenzione, noterete che in modalità Pipeline integrata questo risponderà sempre con HTTP 200 a causa del modo in cui Server.TransferRequestfunziona. Per restituire il codice di errore corretto, utilizzo il seguente controller di errore.

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

Affrontare gli errori in ASP.NET MVC è solo una seccatura. Ho provato molti suggerimenti su questa pagina e su altre domande e siti e niente funziona bene. Un suggerimento era quello di gestire gli errori su web.config all'interno di system.webserver ma che restituisce solo pagine vuote .

Il mio obiettivo quando ho trovato questa soluzione era;

  • NON REDIRETTO
  • Restituisce CODICI DI STATO CORRETTI non 200 / Ok come la gestione degli errori predefinita

Ecco la mia soluzione

1. Aggiungi quanto segue alla sezione 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>

Quanto sopra gestisce tutti gli URL non gestiti da route.config e le eccezioni non gestite, in particolare quelle riscontrate nelle viste. Notate che ho usato aspx non html . Questo è così posso aggiungere un codice di risposta sul codice dietro.

2 . Crea una cartella chiamata Error (o come preferisci) nella radice del tuo progetto e aggiungi i due moduli web. Di seguito è la mia pagina 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>

E sul codice dietro ho impostato il codice di risposta

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

Fai lo stesso per la pagina 500

3. Per gestire gli errori all'interno dei controller. Ci sono molti modi per farlo. Questo è ciò che ha funzionato per me. Tutti i miei controller ereditano da un controller di base. Nel controller di base, ho i seguenti metodi

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. Aggiungi CustomError.cshtml alla cartella Viste condivise . Di seguito è il mio;

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

Ora nel controller dell'applicazione puoi fare qualcosa del genere;

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

Ora per l' avvertimento . Non gestirà errori di file statici. Quindi se hai un percorso come example.com/widgets e l'utente lo cambia in example.com/widgets.html , otterrà la pagina di errore predefinita IIS, quindi dovrai gestire gli errori a livello IIS in altro modo.


1

Pubblicare una risposta poiché il mio commento era troppo lungo ...

È sia un commento che domande al post / risposta dell'unicorno:

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

Preferisco questa risposta agli altri per la sua semplicità e per il fatto che apparentemente alcune persone di Microsoft sono state consultate. Tuttavia, ho tre domande e se è possibile rispondere, chiamerò questa risposta il Santo Graal di tutte le risposte di errore 404/500 sugli interwebs per un'app ASP.NET MVC (x).

@ Pure.Krome

  1. Puoi aggiornare la tua risposta con le cose SEO dai commenti evidenziati da GWB (non hai mai menzionato questo nella tua risposta) - <customErrors mode="On" redirectMode="ResponseRewrite">e <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Puoi chiedere ai tuoi amici del team ASP.NET se va bene farlo in quel modo - sarebbe bello avere qualche conferma - forse è un grande no-no cambiare redirectModee existingResponsein questo modo essere in grado di giocare bene con il SEO ?!

  3. Si può aggiungere qualche chiarimento che circonda tutta quella roba ( customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", rimuovere customErrorscompletamente, come qualcuno ha suggerito) dopo aver parlato con i tuoi amici a Microsoft?

Come stavo dicendo; sarebbe supernice se potessimo rendere la tua risposta più completa in quanto questa sembra essere una domanda abbastanza popolare con oltre 54000 visualizzazioni.

Aggiornamento : la risposta Unicorn fa un 302 trovato e un 200 OK e non può essere modificata per restituire solo 404 usando un percorso. Deve essere un file fisico che non è molto MVC: ish. Passiamo quindi a un'altra soluzione. Peccato perché questo sembrava essere l'ultimo MVC: ish risposta fino a qui.


1

Aggiungendo la mia soluzione, che è quasi identica a quella di Herman Kan, con una piccola ruga per consentirgli di funzionare per il mio progetto.

Crea un controller errori personalizzato:

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

Quindi creare una fabbrica di controller personalizzati:

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

Infine, aggiungi una sostituzione al controller degli errori personalizzato:

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

E questo è tutto. Non sono necessarie modifiche a Web.config.


1

1) Crea una classe Controller astratta.

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) Crea l'eredità da questa classe astratta in tutti i tuoi controller

public class HomeController : MyController
{}  

3) E aggiungi una vista denominata "NotFound" nella cartella View-Shared.


0

Ho esaminato la maggior parte delle soluzioni pubblicate su questo thread. Anche se questa domanda potrebbe essere vecchia, è ancora molto applicabile ai nuovi progetti anche adesso, quindi ho trascorso parecchio tempo a leggere le risposte presentate qui e altrove.

Come @Marco ha sottolineato i diversi casi in cui può accadere un 404, ho verificato la soluzione che ho compilato insieme contro tale elenco. Oltre al suo elenco di requisiti, ne ho aggiunto anche un altro.

  • La soluzione dovrebbe essere in grado di gestire le chiamate MVC e AJAX / WebAPI nel modo più appropriato. (vale a dire se 404 si verifica in MVC, dovrebbe mostrare la pagina Non trovato e se 404 si verifica in WebAPI, non dovrebbe dirottare la risposta XML / JSON in modo che il Javascript utilizzato possa analizzarlo facilmente).

Questa soluzione è 2 volte:

La prima parte proviene da @Guillaume all'indirizzo https://stackoverflow.com/a/27354140/2310818 . La loro soluzione si occupa di qualsiasi 404 causato a causa di percorso non valido, controller non valido e azione non valida.

L'idea è quella di creare un WebForm e quindi farlo chiamare l'azione NotFound del controller errori MVC. Fa tutto questo senza alcun reindirizzamento, quindi non vedrai un singolo 302 in Fiddler. Anche l'URL originale viene preservato, il che rende questa soluzione fantastica!


La seconda parte proviene da @ Germán all'indirizzo https://stackoverflow.com/a/5536676/2310818 . La loro soluzione si occupa di qualsiasi 404 restituito dalle tue azioni sotto forma di HttpNotFoundResult () o lancia un nuovo HttpException ()!

L'idea è quella di avere un filtro per esaminare la risposta e l'eccezione generata dai controller MVC e chiamare l'azione appropriata nel controller degli errori. Ancora una volta questa soluzione funziona senza reindirizzamento e l'URL originale viene conservato!


Come puoi vedere, entrambe queste soluzioni offrono insieme un meccanismo di gestione degli errori molto robusto e soddisfano tutti i requisiti elencati da @Marco, nonché i miei requisiti. Se desideri vedere un campione funzionante o una demo di questa soluzione, ti preghiamo di lasciare nei commenti e sarei felice di metterlo insieme.


0

Ho esaminato tutti gli articoli, ma nulla funziona per me: il mio requisito utente digitare qualsiasi cosa nella tua pagina 404 personalizzata dell'URL dovrebbe mostrare. Ho pensato che fosse molto semplice, ma dovresti capire la gestione corretta di 404:

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

Ho trovato questo articolo molto utile. Dovrebbe essere letto subito. Custome error page-Ben Foster

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.