2011-08-17 27 views
7

Me preguntaba cuál sería la mejor implementación para un error global (no tiene que ser un error, sino también un mensaje de éxito). Permítanme descomponerlo para usted con un ejemplo:ASP.NET MVC: ¿Enfoque para el manejo de errores globales?

  1. usuario intenta eliminar un registro falla
  2. Supresión y se registra un error
  3. usuario redirige a otra página de un mensaje de error
  4. de visualización para el usuario (utilizando un HtmlHelper o algo así, no quiero que sea una página de error específica)

Sólo tengo curiosidad por lo que ustedes piensan. He estado considerando TempData, ViewData y Session, pero todos tienen sus pros y sus contras.

TIA!

ACTUALIZACIÓN:

voy a mostrar un ejemplo de lo que significa exactamente, tal vez no era lo suficientemente claro. Este es un ejemplo de un método que agrega un mensaje cuando el usuario elimina un registro. Si el usuario tiene éxito, el usuario vuelve a dirigir a otra página

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

Y en la vista "RecordsList", sería un poco frío para mostrar todos los mensajes (tanto los mensajes de error y éxito) en un HtmlHelper o algo así.

<%= Html.RenderAllMessages %> 

Esto se puede lograr de muchas maneras, solo tengo curiosidad por lo que ustedes harían.

ACTUALIZACIÓN 2:

He creado un controlador de errores personalizado (mensaje). Puede ver el código si se desplaza hacia abajo.

+0

Eso es un poco raro. En la mayoría de los casos, la falta de un mensaje de error indica éxito. Los mensajes de éxito son redundantes en la mayoría de los casos. Si hay un error, permanecerías en la misma página, así que supongo que todavía no entiendo la lógica de lo que estás tratando de hacer. –

+1

No estoy de acuerdo, creo que es muy importante informar al usuario que una acción ha tenido éxito, de lo contrario, el usuario puede confundirse. Pero también entiendo lo que quiere decir, es más importante informar al usuario cuando falla. –

+1

Todos los estudios de usabilidad que se han realizado están en desacuerdo con usted. Es algo muy estresado. Los usuarios no quieren ser molestados si no hay un problema. –

Respuesta

2

Estoy confundido por estos pasos:

  • Supresión falla y se registra un error
  • usuario redirige a otra página

¿por qué se redirigir al usuario cuando se produce un error? Eso no tiene ningún sentido, a menos que malinterprete algo.

En general, i siga estas pautas:

  • Error con formulario de presentación (por ejemplo, HTTP POST): comprobar ModelState.IsValid y devolver el mismo punto de vista y hacer que el error fuera con @Html.ValidationSummary()
  • error con AJAX llame al: devuelva JsonResult (como dice @Tomas), y use scripts básicos del lado del cliente para inspeccionar el JSON y muestre el resultado
  • Error con dom ain/negocio: lanzar excepciones personalizados y dejar que el controlador de ellas captura y añadir a ModelState que el anterior
+0

Sé que esto suena extraño, pero digamos los detalles de su vista para un registro. Digamos que elimina el registro y desea redirigir al usuario a una vista de lista para todos los registros y mostrar un mensaje al usuario. Por supuesto, puede usar TempData, pero sería bueno usar algún tipo de manejo genérico de errores, que registrará todos sus mensajes, incluso si se trata de una solicitud. –

+0

He actualizado mi publicación con un ejemplo más detallado. –

+0

@Kristoffer - veo, realmente no habla de mensajes de error per se, sino de mensajes personalizados. TempData es tu única opción. Hago este tipo de cosas cuando un usuario que no está conectado intenta guardar algunos datos. Guardo los datos en TempData, redirijo al usuario al inicio de sesión, luego de vuelta y prellevo el formulario. Lo hago a través de un filtro de acción personalizado. – RPM1984

1

Prefiero escribir mi capa de servidor como una API que emite JSON - en ASP.NET MVC que es realmente simple - usted acaba de crear un grupo de objetos anónimos anidados, y return Json(data);. El objeto JSON es consumido por la capa del cliente, que consiste en html, css y javascript (yo uso mucho jQuery, pero es posible que prefiera otras herramientas).

Como javascript es dinámico, es muy fácil tener una propiedad status en el objeto de datos, y la secuencia de comandos del lado del cliente puede interpretar eso y mostrar el estado o los mensajes de error según sea necesario.

Por ejemplo, consideremos el siguiente método de acción:

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

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

Esto devolverá JSON en el siguiente formato:

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

donde ... es un marcador de posición para las propiedades de stuff. Ahora, si quiero el control de errores, sólo puede hacer

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

Desde Javascript es dinámica, no importa que los dos objetos anónimos no tienen las mismas propiedades.Según el valor de status, solo buscaré las propiedades que realmente estén allí.

Esto se puede implementar incluso mejor si crea sus propias clases de resultados de acción, que se extienden a JsonResult y agrega la propiedad de estado automáticamente. Por ejemplo, puede crear una para las solicitudes fallidas que toma una excepción en el constructor y una para las exitosas que tomar un objeto anónimo.

+3

¿Has oído hablar alguna vez de los códigos de estado HTTP? –

+0

Sí, esa es una forma de hacerlo. A menudo tiendo a utilizar esta técnica cuando estoy trabajando con javascript, pero con mi escenario anterior, este ejemplo no funcionaría (?) Porque se trata de una solicitud HTTP. Es por eso que tengo curiosidad de las implementaciones de cómo mantener los errores sobre las solicitudes HTTP. –

+0

@Max: prefiero usar los códigos de estado HTTP para cosas que * no he visto *. El estado en mi objeto JSON no es tanto "la solicitud falló debido a un error del servidor" como "Lo que intentó hacer no funcionó, pero no es realmente debido a un error en la aplicación". –

0

Si todo lo que vamos a hacer es redirigir al usuario a otra página, a continuación, se puede utilizar cualquier ActionMethod hacer así que solo redirija a eso.

Si desea un error global, como un 500 o 403 o algún otro error, la plantilla predeterminada de MVC 3 crea una página _Error.cshtml y registra el controlador de errores en el global.asax.

Si desea detectar errores específicos, puede registrar manejadores adicionales en el mismo lugar y decirle al sistema qué página de Error usar para ese error.

+0

¡Hola! Realmente no sé si eso es lo que estoy buscando. He actualizado mi publicación con un ejemplo más detallado. ¡Gracias! –

4

Solo por diversión, creé mi propio controlador de error (mensaje) personalizado que funciona prácticamente como TempData, pero con la pequeña diferencia de que este manejador está accesible en toda la aplicación.

No voy a explicar cada paso del código, pero para resumir todo, utilicé IHttpModule para activar un método para cada solicitud y Session para guardar datos. A continuación se muestra el código, siéntase libre de editar o dar sugerencias de mejoras.

Web.config (Definir módulo)

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

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

ErrorManager.cs (código del gestor de controlador de errores)

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 método de extensión para la prestación de los errores)

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 para la creación de errores

// 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 de errores de renderizado

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

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