2011-08-17 19 views
7

Mi chiedevo quale sarebbe stata la migliore implementazione per un errore globale (non deve essere un errore, può essere anche un messaggio di successo)? Mi permetta di scomposizione per voi con un esempio:ASP.NET MVC - Approccio per la gestione globale degli errori?

  1. utente tenta di eliminare un record
  2. eliminazione non riesce e viene registrato un errore
  3. utente reindirizza a un'altra pagina messaggio di errore
  4. di visualizzazione per l'utente (usando un HtmlHelper o qualcosa del genere, non voglio che sia una pagina di errore specifica)

Sono solo curioso di sapere cosa ne pensate voi ragazzi. Ho preso in considerazione TempData, ViewData e Session ma tutti hanno i loro pro e contro.

TIA!

UPDATE:

ti faccio vedere un esempio quello che esattamente dire, forse non sono stato abbastanza chiaro. Questo è un esempio di un metodo che aggiunge un messaggio quando l'utente cancella un record. Se l'utente ha successo, l'utente reindirizza a un'altra pagina

public ActionResult DeleteRecord(Record recordToDelete) 
{ 
    // If user succeeds deleting the record 
    if (_service.DeleteRecord(recordToDelete) 
    { 
     // Add success message 
     MessageHandler.AddMessage(Status.SUCCESS, "A message to user"); 

     // And redirect to list view 
     return RedirectToAction("RecordsList"); 
    } 
    else 
    { 
     // Else return records details view 
     return View("RecordDetails", recordToDelete); 
    } 
} 

E nella vista "RecordsList", sarebbe un pò freddo per mostrare tutti i messaggi (sia i messaggi di errore e di successo) in un HtmlHelper o qualcosa del genere.

<%= Html.RenderAllMessages %> 

Questo può essere ottenuto in molti modi, sono solo curioso di sapere cosa fareste voi ragazzi.

UPDATE 2:

ho creato un gestore di errore personalizzato (messaggio). Puoi vedere il codice se scorri verso il basso.

+0

Questo è un po 'strano. Nella maggior parte dei casi, la mancanza di un messaggio di errore indica il successo. I messaggi di successo sono ridondanti nella maggior parte dei casi. Se c'è un errore rimarrai sulla stessa pagina, quindi credo di non avere ancora la logica di quello che stai cercando di fare. –

+1

Non sono d'accordo, penso sia molto importante informare l'utente che un'azione è riuscita, altrimenti l'utente può essere confuso. Ma capisco anche cosa intendi, è più importante informare l'utente quando fallisce. –

+1

Tutti gli studi di usabilità che sono stati fatti non sono d'accordo con te. È una cosa molto stressata. Gli utenti non vogliono essere disturbati se non c'è un problema. –

risposta

2

Sono confuso da questi passaggi:

  • eliminazione non riesce e viene registrato un errore
  • utente reindirizza a un'altra pagina

Perché si reindirizzare l'utente in caso di errore? Questo non ha alcun senso, a meno che non fraintendiate qualcosa.

In generale, seguo queste linee guida:

  • errore con modulo di presentazione (ad esempio HTTP POST): controllare ModelState.IsValid e restituire la stessa vista e rendere l'errore con @Html.ValidationSummary()
  • Errore con AJAX chiama: restituisce JsonResult (come dice @Tomas) e usa script di base sul lato client per ispezionare JSON e mostra il risultato
  • Errore dom ain/incassi: gettare eccezioni personalizzate e lasciare che il controllore li cattura e aggiungere al ModelState come sopra
+0

So che sembra strano, ma diciamo i dettagli di visualizzazione per un record. Diciamo che si cancella il record e si desidera reindirizzare l'utente a un listview per tutti i record e visualizzare un messaggio all'utente. Naturalmente, è possibile utilizzare TempData, ma sarebbe bello utilizzare una sorta di gestione degli errori generica, che registrerà tutti i messaggi, anche se è coinvolta una richiesta. –

+0

Ho aggiornato il mio post con un esempio più dettagliato. –

+0

@Kristoffer - Vedo, il tuo non parla davvero di messaggi di errore di per sé, ma messaggi personalizzati. TempData è la tua unica opzione. Faccio questo genere di cose quando un utente senza accesso tenta di salvare alcuni dati. Inserisco i dati in TempData, reindirizzo l'utente al login, quindi faccio di nuovo e pre-compilare il modulo. Lo faccio tramite un filtro azione personalizzato. – RPM1984

1

Preferisco scrivere il mio livello server come API che emette JSON - in ASP.NET MVC è molto semplice - basta creare un gruppo di oggetti anonimi nidificati e return Json(data);. L'oggetto JSON viene quindi utilizzato dal livello client, che comprende html, css e javascript (io uso molto jQuery, ma potresti preferire altri strumenti).

Dal javascript è dinamica, è quindi facile reale di avere solo una proprietà status sull'oggetto dei dati, e lo script lato client in grado di interpretare e che i messaggi di stato di visualizzazione o di errore, se necessario.

Ad esempio, si consideri il seguente metodo di azione:

public ActionResult ListStuff() 
{ 
    var stuff = Repo.GetStuff(); 

    return Json(new { status = "OK", thestuff = stuff }); 
} 

Ciò ritorno JSON nel seguente formato:

{ "status": "OK", "thestuf": [{ ... }, { ... }] } 

dove ... è un segnaposto per le proprietà di stuff. Ora, se voglio la gestione degli errori, posso solo fare

try 
{ 
    var stuff = Repo.GetStuff(); 
    return Json(new { status = "OK", thestuff = stuff}); 
} 
catch (Exception ex) 
{ 
    Log.Error(ex); 
    return Json(new { status = "Fail", reason = ex.Message }); 
} 

Dal javascript è dinamico, non importa che i due oggetti anonimi non hanno le stesse proprietà.In base al valore di status, cercherò solo le proprietà effettivamente presenti.

Questo può essere implementato ancora meglio se si creano le proprie classi risultato azione, che estendono JsonResult e aggiungono automaticamente la proprietà di stato. Ad esempio, è possibile crearne uno per le richieste non riuscite che accetta un'eccezione nel costruttore e una per quelle riuscite rispetto a prendere un oggetto anonimo.

+3

Hai mai sentito parlare dei codici di stato HTTP? –

+0

Sì, questo è un modo per farlo. Spesso tendo ad usare questa tecnica quando lavoro con javascript, ma con il mio scenario sopra questo esempio non funzionerebbe (?) Perché è coinvolta una richiesta HTTP. Ecco perché sono curioso di implementazioni su come mantenere gli errori sulle richieste HTTP. –

+0

@Max: preferisco usare i codici di stato HTTP per cose che * non ho previsto *. Lo stato nel mio oggetto JSON non è tanto "la richiesta non è riuscita a causa di un errore del server", quanto "ciò che si è tentato di fare non ha funzionato, ma non è proprio a causa di un errore nell'applicazione". –

0

Se tutto quello che stai andando a fare è reindirizzare l'utente ad un'altra pagina, quindi è possibile utilizzare qualsiasi ActionMethod fare così e basta reindirizzare ad esso.

Se si desidera un errore globale, ad esempio un errore 500 o 403 o un altro errore, il modello predefinito MVC 3 crea una pagina _Error.cshtml e registra il gestore degli errori in global.asax.

Se si desidera rilevare errori specifici, è possibile registrare altri gestori nella stessa posizione e indicare al sistema la pagina di errore da utilizzare per quell'errore.

+0

Ciao! Non so se questo è quello che sto cercando. Ho aggiornato il mio post con un esempio più dettagliato. Grazie! –

4

Solo per divertimento, ho creato il mio gestore di errore personalizzato (messaggio) che funziona come TempData, ma con la piccola differenza che questo gestore è accessibile in tutta l'applicazione.

Non ho intenzione di spiegare ogni singolo passaggio del codice, ma per sommare tutto, ho usato IHttpModule per sparare un metodo per ogni richiesta e Session per salvare i dati. Di seguito è riportato il codice, sentiti libero di modificare o dare suggerimenti per miglioramenti.

web.config (Definizione di modulo)

<httpModules> 
    <add name="ErrorManagerModule" type="ErrorManagerNamespace.ErrorManager"/> 
</httpModules> 

<system.webServer> 
    <modules runAllManagedModulesForAllRequests="true"> 
    <add name="ErrorManagerModule" type="ErrorManagerNamespace.ErrorManager"/> 
    </modules> 
</system.webServer> 

ErrorManager.cs (codice direttore di gestore di errore)

public class ErrorManager : IRequiresSessionState, IHttpModule 
{ 
    private const string SessionKey = "ERROR_MANAGER_SESSION_KEY"; 

    public enum Type 
    { 
     None, 
     Warning, 
     Success, 
     Error 
    } 

    /* 
    * 
    * Public methods 
    * 
    */ 

    public void Dispose() 
    { 
    } 

    public void Init(HttpApplication context) 
    { 
     context.AcquireRequestState += new EventHandler(Initiliaze); 
    } 

    public static IList<ErrorModel> GetErrors(ErrorManager.Type type = Type.None) 
    { 
     // Get all errors from session 
     var errors = GetErrorData(); 

     // Destroy Keep alive 
     // Decrease all errors request count 
     foreach (var error in errors.Where(o => type == ErrorManager.Type.None || o.ErrorType == type).ToList()) 
     { 
      error.KeepAlive = false; 
      error.IsRead = true; 
     } 

     // Save errors to session 
     SaveErrorData(errors); 

     //return errors; 
     return errors.Where(o => type == ErrorManager.Type.None || o.ErrorType == type).ToList(); 
    } 

    public static void Add(ErrorModel error) 
    { 
     // Get all errors from session 
     var errors = GetErrorData(); 
     var result = errors.Where(o => o.Key.Equals(error.Key, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); 

     // Add error to collection 
     error.IsRead = false; 

     // Error with key is already associated 
     // Remove old error from collection 
     if (result != null) 
      errors.Remove(result); 

     // Add new to collection 
     // Save errors to session 
     errors.Add(error); 
     SaveErrorData(errors); 
    } 

    public static void Add(string key, object value, ErrorManager.Type type = Type.None, bool keepAlive = false) 
    { 
     // Create new error 
     Add(new ErrorModel() 
     { 
      IsRead = false, 
      Key = key, 
      Value = value, 
      KeepAlive = keepAlive, 
      ErrorType = type 
     }); 
    } 

    public static void Remove(string key) 
    { 
     // Get all errors from session 
     var errors = GetErrorData(); 
     var result = errors.Where(o => o.Key.Equals(key, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); 

     // Error with key is in collection 
     // Remove old error 
     if (result != null) 
      errors.Remove(result); 

     // Save errors to session 
     SaveErrorData(errors); 
    } 

    public static void Clear() 
    { 
     // Clear all errors 
     HttpContext.Current.Session.Remove(SessionKey); 
    } 

    /* 
    * 
    * Private methods 
    * 
    */ 

    private void Initiliaze(object o, EventArgs e) 
    { 
     // Get context 
     var context = ((HttpApplication)o).Context; 

     // If session is ready 
     if (context.Handler is IRequiresSessionState || 
      context.Handler is IReadOnlySessionState) 
     { 
      // Load all errors from session 
      LoadErrorData(); 
     } 
    } 

    private static void LoadErrorData() 
    { 
     // Get all errors from session 
     var errors = GetErrorData().Where(o => !o.IsRead).ToList(); 

     // If KeepAlive is set to false 
     // Mark error as read 
     foreach (var error in errors) 
     { 
      if (error.KeepAlive == false) 
       error.IsRead = true; 
     } 

     // Save errors to session 
     SaveErrorData(errors); 
    } 

    private static void SaveErrorData(IList<ErrorModel> errors) 
    { 
     // Make sure to remove any old errors 
     HttpContext.Current.Session.Remove(SessionKey); 
     HttpContext.Current.Session.Add(SessionKey, errors); 
    } 

    private static IList<ErrorModel> GetErrorData() 
    { 
     // Get all errors from session 
     return HttpContext.Current.Session[SessionKey] 
      as IList<ErrorModel> ?? 
      new List<ErrorModel>(); 
    } 

    /* 
    * 
    * Model 
    * 
    */ 

    public class ErrorModel 
    { 
     public string Key { get; set; } 
     public object Value { get; set; } 
     public bool KeepAlive { get; set; } 
     internal bool IsRead { get; set; } 
     public Type ErrorType { get; set; } 
    } 

HtmlHelperExtension.cs (un metodo di estensione per il rendering degli errori)

public static class HtmlHelperExtension 
{ 
    public static string RenderMessages(this HtmlHelper obj, ErrorManager.Type type = ErrorManager.Type.None, object htmlAttributes = null) 
    { 
     var builder = new TagBuilder("ul"); 
     var errors = ErrorManager.GetErrors(type); 

     // If there are no errors 
     // Return empty string 
     if (errors.Count == 0) 
      return string.Empty; 

     // Merge html attributes 
     builder.MergeAttributes(new RouteValueDictionary(htmlAttributes), true); 

     // Loop all errors 
     foreach (var error in errors) 
     { 
      builder.InnerHtml += String.Format("<li class=\"{0}\"><span>{1}</span></li>", 
       error.ErrorType.ToString().ToLower(), 
       error.Value as string); 
     } 

     return builder.ToString(); 
    } 
} 

Uso per la creazione di errori

// This will only be available for one request 
ErrorManager.Add("Key", "An error message", ErrorManager.Type.Error); 

// This will be available for multiple requests 
// When error is read, it will be removed 
ErrorManager.Add("Key", "An error message", ErrorManager.Type.Error, true); 

// Remove an error 
ErrorManager.Remove("AnotherKey"); 

// Clear all error 
ErrorManager.Clear(); 

Uso per il rendering errori

// This will render all errors 
<%= Html.RenderMessages() %> 

// This will just render all errors with type "Error" 
<%= Html.RenderMessages(ErrorManager.Type.Error) %>