2009-05-01 21 views
63

Come posso gestire correttamente le eccezioni generate dai controller in ASP.NET MVC? L'attributo HandleError sembra elaborare solo le eccezioni generate dall'infrastruttura MVC e non le eccezioni generate dal mio codice.Gestione degli errori in ASP.NET MVC

Usando questo web.config

<customErrors mode="On"> 
    <error statusCode="401" redirect="/Errors/Http401" /> 
</customErrors> 

con il seguente codice

namespace MvcApplication1.Controllers 
{ 
    [HandleError] 
    public class HomeController : Controller 
    { 
     public ActionResult Index() 
     { 
      // Force a 401 exception for testing 
      throw new HttpException(401, "Unauthorized"); 
     } 
    } 
} 

non risulta in quello che speravo. Invece ottengo la pagina di errore ASP.NET generica che mi dice di modificare il mio web.config per vedere le informazioni di errore reali. Tuttavia, se invece di lanciare un'eccezione torno un invalido vista, ho la pagina di /Shared/Views/Error.aspx:

return View("DoesNotExist"); 

Lanciare eccezioni all'interno di un controller come ho fatto sopra sembra di bypassare tutti la funzionalità HandleError, quindi qual è il modo giusto per creare pagine di errore e come posso giocare con l'infrastruttura MVC?

risposta

20

Grazie alla kazimanzurrashaid, ecco quello che ho finito per fare in Global.asax.cs:

protected void Application_Error() 
{ 
    Exception unhandledException = Server.GetLastError(); 
    HttpException httpException = unhandledException as HttpException; 
    if (httpException == null) 
    { 
     Exception innerException = unhandledException.InnerException; 
     httpException = innerException as HttpException; 
    } 

    if (httpException != null) 
    { 
     int httpCode = httpException.GetHttpCode(); 
     switch (httpCode) 
     { 
      case (int) HttpStatusCode.Unauthorized: 
       Response.Redirect("/Http/Error401"); 
       break; 
     } 
    } 
} 

Sarò in grado di aggiungere più pagine a HttpContoller sulla base di eventuali codici di errore HTTP aggiuntivi che devo supportare.

+1

Ti suggerisco di controllare la fonte di http://kigg.codeplex.com dove troverai il codice sorgente completo di questo httpModule, voglio solo ingombrare global.asax ecco perché HttpModule. – kazimanzurrashid

13

L'attributo HandleError sembra elaborare solo le eccezioni generate dall'infrastruttura MVC e non le eccezioni generate dal mio codice.

Questo è semplicemente sbagliato. In effetti, HandleError elaborerà solo eccezioni generate nel proprio codice o in codice chiamato dal proprio codice. In altre parole, solo le eccezioni in cui la tua azione si trova nello stack di chiamate.

La vera spiegazione del comportamento che stai vedendo è l'eccezione specifica che stai lanciando. HandleError si comporta in modo diverso con una HttpException. Dal codice sorgente:

 // If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method), 
     // ignore it. 
     if (new HttpException(null, exception).GetHttpCode() != 500) { 
      return; 
     } 
+0

Hai sottolineato che una delle mie affermazioni è corretta senza affrontare il vero problema di non essere in grado di fornire la gestione degli errori per HTTP codici di errore –

+4

Posso spiegare cosa sta succedendo, ma avrei bisogno di più informazioni per dirvi la cosa giusta da fare per la vostra applicazione. HandleError funziona su un'azione e generalmente non si gettano le HttpException all'interno di un'azione. Piuttosto che indovinare cosa stai effettivamente cercando di fare e fornirti un consiglio potenzialmente scorretto, posso solo spiegare i fatti dietro il comportamento che stai vedendo. Sentiti libero di aggiornare la domanda se desideri spiegare di più su cosa vuoi veramente fare. –

63

Controller.OnException(ExceptionContext context). Sovrascrivi.

protected override void OnException(ExceptionContext filterContext) 
{ 
    // Bail if we can't do anything; app will crash. 
    if (filterContext == null) 
     return; 
     // since we're handling this, log to elmah 

    var ex = filterContext.Exception ?? new Exception("No further information exists."); 
    LogException(ex); 

    filterContext.ExceptionHandled = true; 
    var data = new ErrorPresentation 
     { 
      ErrorMessage = HttpUtility.HtmlEncode(ex.Message), 
      TheException = ex, 
      ShowMessage = !(filterContext.Exception == null), 
      ShowLink = false 
     }; 
    filterContext.Result = View("ErrorPage", data); 
} 
+0

Sì, questo è il modo per farlo. Utilizzare uno dei filtri forniti o creare il proprio filtro delle eccezioni è la strada da percorrere. Puoi cambiare la vista in base al tipo di eccezione o approfondire approfondendo ispezionando maggiori informazioni nel tuo filtro personalizzato. – a7drew

+0

Sei sicuro che questo non era qualcosa che era nelle versioni di anteprima di MVC ma è stato modificato per 1.0? Non vedo questo override, invece sembra che tu debba ignorare OnActionExecuted e ispezionare il valore filterContext.Exception –

+0

http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onexception (VS.100) .aspx – Will

6

Non credo che si sarà in grado di mostrare ErrorPage specifico basato sulla HttpCode con l'attributo HandleError ed io preferirei usare un HttpModule per questo scopo. Supponendo di avere una cartella "ErrorPages" in cui esiste una pagina differente per ogni errore specifico e la mappatura è specificata nel web.config come l'applicazione normale del modulo web. E il seguente è il codice che viene utilizzato per visualizzare la pagina di errore:

public class ErrorHandler : BaseHttpModule{ 

public override void OnError(HttpContextBase context) 
{ 
    Exception e = context.Server.GetLastError().GetBaseException(); 
    HttpException httpException = e as HttpException; 
    int statusCode = (int) HttpStatusCode.InternalServerError; 

    // Skip Page Not Found and Service not unavailable from logging 
    if (httpException != null) 
    { 
     statusCode = httpException.GetHttpCode(); 

     if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable)) 
     { 
      Log.Exception(e); 
     } 
    } 

    string redirectUrl = null; 

    if (context.IsCustomErrorEnabled) 
    { 
     CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors"); 

     if (section != null) 
     { 
      redirectUrl = section.DefaultRedirect; 

      if (httpException != null) 
      { 
       if (section.Errors.Count > 0) 
       { 
        CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)]; 

        if (item != null) 
        { 
         redirectUrl = item.Redirect; 
        } 
       } 
      } 
     } 
    } 

    context.Response.Clear(); 
    context.Response.StatusCode = statusCode; 
    context.Response.TrySkipIisCustomErrors = true; 

    context.ClearError(); 

    if (!string.IsNullOrEmpty(redirectUrl)) 
    { 
     context.Server.Transfer(redirectUrl); 
    } 
} 

}

+0

Questo è quello che stavo cercando.Ho adottato un approccio leggermente più semplice e ho inserito il mio codice in Application_Error per gestire lo stesso insieme di errori tra i controller. Pubblicherò la soluzione in risposta a questa domanda. –

3

un'altra possibilità (non è vero nel tuo caso) che gli altri la lettura di questo potrebbe trattarsi di è che la vostra pagina di errore sta gettando un errore in sé, o non attua:

System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo> 

Se questo è il caso, allora otterrete la pagina di errore predefinita (altrimenti otterreste un ciclo infinito perché continuerebbe a tentare di inviarlo alla vostra pagina di errore personalizzata).Questo non era immediatamente ovvio per me.

Questo modello è il modello inviato alla pagina di errore. Se la pagina di errore utilizza la stessa pagina principale del resto del sito e richiede altre informazioni sul modello, sarà necessario creare il proprio tipo di attributo [HandleError] o sostituire OnException o qualcosa del genere.

+0

È anche possibile dichiarare l'attributo [HandleError] sui controller o sui metodi del controller, ma non aiuta per gli errori HTTP. –

2

Ho scelto l'approccio Controller.OnException(), che per me è la scelta logica - poiché ho scelto ASP.NET MVC, preferisco rimanere a livello di framework ed evitare di fare scherzi con la meccanica sottostante, se possibile.

mi sono imbattuto nel seguente problema:

Se l'eccezione si verifica all'interno della vista, apparirà l'uscita parziale da questo punto di vista sullo schermo, insieme con l'errore-messaggio.

Ho fissato questo cancellando la risposta, prima di impostare filterContext.Result - in questo modo:

 filterContext.HttpContext.Response.Clear(); // gets rid of any garbage 
     filterContext.Result = View("ErrorPage", data); 

Spero che questo consente di risparmiare qualcun altro po 'di tempo :-)

+0

Sembra che si stia replicando il comportamento già previsto dall'attributo HandleError. –

+2

Forse. Imparare tutte queste cose si sta rivelando uno sforzo doloroso. Così tanti cattivi esempi e documentazione obsoleta da filtrare. Immagino di aver appena contribuito con un altro cattivo esempio ;-) –

2
 protected override void OnException (ExceptionContext filterContext) 
    { 
     if (filterContext != null && filterContext.Exception != null) 
     { 
      filterContext.ExceptionHandled = true; 
      this.View("Error").ViewData["Exception"] = filterContext.Exception.Message; 
      this.View("Error").ExecuteResult(this.ControllerContext); 
     } 
    } 
+1

Si potrebbe voler memorizzare il risultato di 'this.View (" Errore ")' in una variabile locale invece di richiamare il metodo 2 volte. – SandRock

1

Jeff Atwood User Friendly Exception Handling module grandi opere per MVC. Puoi configurarlo interamente nel tuo web.config, senza modifiche del codice sorgente del progetto MVC. Tuttavia, è necessaria una piccola modifica per restituire lo stato HTTP originale piuttosto che uno stato 200. Vedi questo relativo forum post.

In sostanza, in Handler.vb, è possibile aggiungere qualcosa di simile:

' In the header... 
Private _exHttpEx As HttpException = Nothing 

' At the top of Public Sub HandleException(ByVal ex As Exception)... 
HttpContext.Current.Response.StatusCode = 500 
If TypeOf ex Is HttpException Then 
    _exHttpEx = CType(ex, HttpException) 
    HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode() 
End If