2009-03-23 16 views
71

Estoy escribiendo un servicio JSON en C# (archivo .ashx). En una solicitud exitosa al servicio devuelvo algunos datos JSON. Si la solicitud falla, ya sea porque se lanzó una excepción (por ejemplo, tiempo de espera de la base de datos) o porque la solicitud fue incorrecta de alguna manera (por ejemplo, una ID que no existe en la base de datos se presentó como argumento) ¿cómo debería responder el servicio? ¿Qué códigos de estado de HTTP son razonables y debo devolver cualquier información, si corresponde?¿Qué debe devolver un servicio JSON por error/error?

Anticipo que el servicio se invocará principalmente desde jQuery utilizando el complemento jQuery.form, ¿jQuery o este complemento tiene alguna forma predeterminada de manejar una respuesta de error?

EDIT: He decidido que voy a usar jQuery + + .ashx HTTP [códigos de estado] en el éxito que voy a volver JSON, pero en caso de error que voy a volver una cadena, ya que parece que eso es lo la opción de error para jQuery.ajax espera.

Respuesta

31

El código de estado HTTP que devuelva debe depender del tipo de error que se haya producido. Si una ID no existe en la base de datos, devuelva un 404; si un usuario no tiene suficientes privilegios para realizar esa llamada Ajax, devuelva un 403; si la base de datos agota el tiempo de espera antes de poder encontrar el registro, devuelva un 500 (error del servidor).

jQuery detecta automáticamente tales códigos de error y ejecuta la función de devolución de llamada que usted define en su llamada Ajax. Documentación: http://api.jquery.com/jQuery.ajax/

ejemplo corto de una devolución de llamada $.ajax error:

$.ajax({ 
    type: 'POST', 
    url: '/some/resource', 
    success: function(data, textStatus) { 
    // Handle success 
    }, 
    error: function(xhr, textStatus, errorThrown) { 
    // Handle error 
    } 
}); 
+3

¿Qué código de error crees que debo devolver si alguien proporciona datos no válidos, como una cadena donde se requiere un número entero? o una dirección de correo electrónico no válida? – thatismatt

+0

algo en el rango de 500, igual que cualquier error de código del lado del servidor similar – annakata

+7

El rango de 500 es un error del servidor, pero nada ha ido mal en el servidor. Hicieron una mala solicitud, ¿no debería estar en el rango de 400? – thatismatt

15

El uso de códigos de estado HTTP sería una forma RESTANTE de hacerlo, pero eso sugeriría que haga que el resto de la interfaz RESTful use los URI de recursos, y así sucesivamente.

En verdad, defina la interfaz como desee (devuelva un objeto de error, por ejemplo, detallando la propiedad con el error, y un fragmento de HTML que lo explique, etc.), pero una vez que haya decidido algo que trabaja en un prototipo, sea implacablemente consistente.

+0

me gusta lo que está sugiriendo, estoy asumiendo que usted piensa que debería volver JSON entonces? Algo así como: {error: {mensaje: "Ocurrió un error", detalles: "Ocurrió porque es lunes".}} – thatismatt

+0

@thatismatt - Eso es bastante razonable, si los errores son siempre fatales. Para obtener más granularidad, hacer una matriz 'error' a (posiblemente vacía) y agregar un parámetro' fatal_error: bool' le dará bastante flexibilidad. –

+1

Ah, y +1 para las respuestas RESTful de cuándo usar y cuándo no utilizar. :-) –

2

Creo que si solo hace una excepción, debería manejarse en el jQuery callback that is passed in for the 'error' option. (También registramos esta excepción en el lado del servidor en un registro central). No se requiere un código de error HTTP especial, pero tengo curiosidad por ver lo que hacen otras personas también.

Esto es lo que hago, pero eso es sólo mi $ .02

Si va a estar tranquilo y volver códigos de error, trate de cumplir con los códigos estándar establecidos por el W3C: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

0

No creo que deba devolver ningún código de error HTTP, sino excepciones personalizadas que son útiles para el extremo del cliente de la aplicación para que la interfaz sepa qué ocurrió realmente. No trataría de ocultar problemas reales con códigos de error 404 o algo por el estilo.

+0

¿Sugiere que devuelva un 200 incluso si algo sale mal? ¿Qué quiere decir "excepción personalizada"? ¿Te refieres a una pieza de JSON que describe el error? – thatismatt

+4

Blah, devolver el código http no significa que tampoco pueda devolver el mensaje de descripción de error. Regresar 200 sería bastante tonto sin mencionar el error. – StaxMan

+0

Estoy de acuerdo con @StaxMan - siempre devuelvo el mejor código de estado pero incluyo la descripción en la información de devolución – schmoopy

52

Ver this question para una cierta penetración en las mejores prácticas para su situación.

La sugerencia principal (desde dicho enlace) es estandarizar una estructura de respuesta (tanto para el éxito como para el fracaso) que busca su manejador, capturando todas las excepciones en la capa del servidor y convirtiéndolas a la misma estructura.Por ejemplo (de this answer):

{ 
    success:false, 
    general_message:"You have reached your max number of Foos for the day", 
    errors: { 
     last_name:"This field is required", 
     mrn:"Either SSN or MRN must be entered", 
     zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible" 
    } 
} 

Este es el enfoque stackoverflow usos (en caso de que se preguntan cómo otros lo hacen este tipo de cosas); las operaciones de escritura como de votación tienen "Success" y "Message" campos, independientemente de si se permite o no el voto:

{ Success:true, NewScore:1, Message:"", LastVoteTypeId:3 } 

Como @Phil.H pointed out, debe ser coherente en lo que usted elige. Esto es más fácil decirlo que hacerlo (¡como lo está todo en desarrollo!).

Por ejemplo, si envía los comentarios demasiado rápido en eso, en lugar de ser constante y regresar

{ Success: false, Message: "Can only comment once every blah..." } 

también lo hará una excepción de servidor (HTTP 500) y atraparlo en su error de devolución de llamada.

Tanto como "se siente bien" usar jQuery + .ashx + HTTP [códigos de estado] IMO añadirá más complejidad a su base de código del lado del cliente de lo que vale. Tenga en cuenta que jQuery no "detecta" códigos de error, sino más bien la falta de un código de éxito. Esta es una distinción importante al tratar de diseñar un cliente en torno a los códigos de respuesta http con jQuery. Solo tiene dos opciones (¿fue un "éxito" o un "error"?), Que debe ramificar aún más por su cuenta. Si tiene una pequeña cantidad de WebServices manejando un número pequeño de páginas, entonces podría estar bien, pero cualquier escala más grande puede resultar desordenada.

Es mucho más natural en un servicio web .asmx (o WCF para ese asunto) devolver un objeto personalizado que personalizar el código de estado HTTP. Además, obtienes la serialización JSON de forma gratuita.

+1

Enfoque válido, solo un pequeño detalle: los ejemplos no son válidos JSON (faltan comillas dobles para los nombres de las teclas) – StaxMan

+1

esto es lo que solía hacer, pero realmente debería usar códigos de estado http, eso es lo que están ahí (especialmente si estás haciendo cosas RESTful) – Eva

+0

Creo que este enfoque es definitivamente válido: los códigos de estado http son útiles para hacer cosas relajantes, pero no tan útiles cuando, por ejemplo, estás haciendo llamadas de API a un script que contiene una consulta de base de datos. Incluso cuando la consulta de la base de datos devuelve un error, el código de estado http seguirá siendo 200. En este caso, normalmente uso la tecla 'éxito' para indicar si la consulta MySQL ha sido exitosa o no :) – Terry

2

Definitivamente devolveré un error 500 con un objeto JSON que describa la condición de error, similar a how an ASP.NET AJAX "ScriptService" error returns. Creo que esto es bastante estándar. Definitivamente es bueno tener esa coherencia cuando se manejan condiciones de error potencialmente inesperadas.

Aparte, ¿por qué no simplemente utiliza la funcionalidad incorporada en .NET, si la está escribiendo en C#? Los servicios WCF y ASMX facilitan la serialización de datos como JSON, sin reinventar la rueda.

+0

No lo hago piense que 500 código de error se debe utilizar en este contexto. Según la especificación: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html, la mejor alternativa es enviar un 400 (solicitud incorrecta). 500 error es más adecuado para una excepción no controlada. –

1

Si los datos no válidos usuario suministra, que sin duda debe ser un 400 Bad Request (La solicitud contiene sintaxis incorrecta o no se puede cumplir.)

+0

CUALQUIERA del rango de 400 es aceptable y 422 es la mejor opción para datos que no se pueden procesar – jamesc

2

Rieles andamios utilizan 422 Unprocessable Entity para este tipo de errores. Vea RFC 4918 para más información.

3

Pasé algunas horas resolviendo este problema. Mi solución se basa en los siguientes deseos/requisitos:

  • No tiene un código de manejo repetitivo de código repetitivo en todas las acciones del controlador JSON.
  • Conservar los códigos de estado de HTTP (error). ¿Por qué? Porque las preocupaciones de mayor nivel no deberían afectar la implementación de menor nivel.
  • Poder obtener datos JSON cuando ocurre un error/excepción en el servidor. ¿Por qué? Porque podría querer información rica sobre errores. P.ej. mensaje de error, código de estado de error específico de dominio, seguimiento de pila (en entorno de depuración/desarrollo).
  • Facilidad de uso del lado del cliente: es preferible utilizar jQuery.

Creo un HandleErrorAttribute (vea los comentarios del código para la explicación de los detalles). Se han omitido algunos detalles, incluidos los "usos", por lo que el código podría no compilarse. Agrego el filtro para los filtros globales durante la inicialización de la aplicación en Global.asax.cs así:

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

Atributo:

namespace Foo 
{ 
    using System; 
    using System.Diagnostics; 
    using System.Linq; 
    using System.Net; 
    using System.Reflection; 
    using System.Web; 
    using System.Web.Mvc; 

    /// <summary> 
    /// Generel error handler attribute for Foo MVC solutions. 
    /// It handles uncaught exceptions from controller actions. 
    /// It outputs trace information. 
    /// If custom errors are enabled then the following is performed: 
    /// <ul> 
    /// <li>If the controller action return type is <see cref="JsonResult"/> then a <see cref="JsonResult"/> object with a <c>message</c> property is returned. 
    ///  If the exception is of type <see cref="MySpecialExceptionWithUserMessage"/> it's message will be used as the <see cref="JsonResult"/> <c>message</c> property value. 
    ///  Otherwise a localized resource text will be used.</li> 
    /// </ul> 
    /// Otherwise the exception will pass through unhandled. 
    /// </summary> 
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
    public sealed class FooHandleErrorAttribute : HandleErrorAttribute 
    { 
    private readonly TraceSource _TraceSource; 

    /// <summary> 
    /// <paramref name="traceSource"/> must not be null. 
    /// </summary> 
    /// <param name="traceSource"></param> 
    public FooHandleErrorAttribute(TraceSource traceSource) 
    { 
     if (traceSource == null) 
     throw new ArgumentNullException(@"traceSource"); 
     _TraceSource = traceSource; 
    } 

    public TraceSource TraceSource 
    { 
     get 
     { 
     return _TraceSource; 
     } 
    } 

    /// <summary> 
    /// Ctor. 
    /// </summary> 
    public FooHandleErrorAttribute() 
    { 
     var className = typeof(FooHandleErrorAttribute).FullName ?? typeof(FooHandleErrorAttribute).Name; 
     _TraceSource = new TraceSource(className); 
    } 

    public override void OnException(ExceptionContext filterContext) 
    { 
     var actionMethodInfo = GetControllerAction(filterContext.Exception); 
     // It's probably an error if we cannot find a controller action. But, hey, what should we do about it here? 
     if(actionMethodInfo == null) return; 

     var controllerName = filterContext.Controller.GetType().FullName; // filterContext.RouteData.Values[@"controller"]; 
     var actionName = actionMethodInfo.Name; // filterContext.RouteData.Values[@"action"]; 

     // Log the exception to the trace source 
     var traceMessage = string.Format(@"Unhandled exception from {0}.{1} handled in {2}. Exception: {3}", controllerName, actionName, typeof(FooHandleErrorAttribute).FullName, filterContext.Exception); 
     _TraceSource.TraceEvent(TraceEventType.Error, TraceEventId.UnhandledException, traceMessage); 

     // Don't modify result if custom errors not enabled 
     //if (!filterContext.HttpContext.IsCustomErrorEnabled) 
     // return; 

     // We only handle actions with return type of JsonResult - I don't use AjaxRequestExtensions.IsAjaxRequest() because ajax requests does NOT imply JSON result. 
     // (The downside is that you cannot just specify the return type as ActionResult - however I don't consider this a bad thing) 
     if (actionMethodInfo.ReturnType != typeof(JsonResult)) return; 

     // Handle JsonResult action exception by creating a useful JSON object which can be used client side 
     // Only provide error message if we have an MySpecialExceptionWithUserMessage. 
     var jsonMessage = FooHandleErrorAttributeResources.Error_Occured; 
     if (filterContext.Exception is MySpecialExceptionWithUserMessage) jsonMessage = filterContext.Exception.Message; 
     filterContext.Result = new JsonResult 
     { 
      Data = new 
      { 
       message = jsonMessage, 
       // Only include stacktrace information in development environment 
       stacktrace = MyEnvironmentHelper.IsDebugging ? filterContext.Exception.StackTrace : null 
      }, 
      // Allow JSON get requests because we are already using this approach. However, we should consider avoiding this habit. 
      JsonRequestBehavior = JsonRequestBehavior.AllowGet 
     }; 

     // Exception is now (being) handled - set the HTTP error status code and prevent caching! Otherwise you'll get an HTTP 200 status code and running the risc of the browser caching the result. 
     filterContext.ExceptionHandled = true; 
     filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; // Consider using more error status codes depending on the type of exception 
     filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); 

     // Call the overrided method 
     base.OnException(filterContext); 
    } 

    /// <summary> 
    /// Does anybody know a better way to obtain the controller action method info? 
    /// See http://stackoverflow.com/questions/2770303/how-to-find-in-which-controller-action-an-error-occurred. 
    /// </summary> 
    /// <param name="exception"></param> 
    /// <returns></returns> 
    private static MethodInfo GetControllerAction(Exception exception) 
    { 
     var stackTrace = new StackTrace(exception); 
     var frames = stackTrace.GetFrames(); 
     if(frames == null) return null; 
     var frame = frames.FirstOrDefault(f => typeof(IController).IsAssignableFrom(f.GetMethod().DeclaringType)); 
     if (frame == null) return null; 
     var actionMethod = frame.GetMethod(); 
     return actionMethod as MethodInfo; 
    } 
    } 
} 

he desarrollado el siguiente plugin de jQuery para la facilidad de uso del cliente :

(function ($, undefined) { 
    "using strict"; 

    $.FooGetJSON = function (url, data, success, error) { 
    /// <summary> 
    /// ********************************************************** 
    /// * UNIK GET JSON JQUERY PLUGIN.       * 
    /// ********************************************************** 
    /// This plugin is a wrapper for jQuery.getJSON. 
    /// The reason is that jQuery.getJSON success handler doesn't provides access to the JSON object returned from the url 
    /// when a HTTP status code different from 200 is encountered. However, please note that whether there is JSON 
    /// data or not depends on the requested service. if there is no JSON data (i.e. response.responseText cannot be 
    /// parsed as JSON) then the data parameter will be undefined. 
    /// 
    /// This plugin solves this problem by providing a new error handler signature which includes a data parameter. 
    /// Usage of the plugin is much equal to using the jQuery.getJSON method. Handlers can be added etc. However, 
    /// the only way to obtain an error handler with the signature specified below with a JSON data parameter is 
    /// to call the plugin with the error handler parameter directly specified in the call to the plugin. 
    /// 
    /// success: function(data, textStatus, jqXHR) 
    /// error: function(data, jqXHR, textStatus, errorThrown) 
    /// 
    /// Example usage: 
    /// 
    /// $.FooGetJSON('/foo', { id: 42 }, function(data) { alert('Name :' + data.name); }, function(data) { alert('Error: ' + data.message); }); 
    /// </summary> 

    // Call the ordinary jQuery method 
    var jqxhr = $.getJSON(url, data, success); 

    // Do the error handler wrapping stuff to provide an error handler with a JSON object - if the response contains JSON object data 
    if (typeof error !== "undefined") { 
     jqxhr.error(function(response, textStatus, errorThrown) { 
     try { 
      var json = $.parseJSON(response.responseText); 
      error(json, response, textStatus, errorThrown); 
     } catch(e) { 
      error(undefined, response, textStatus, errorThrown); 
     } 
     }); 
    } 

    // Return the jQueryXmlHttpResponse object 
    return jqxhr; 
    }; 
})(jQuery); 

¿Qué recibo de todo esto? El resultado final es que

  • Ninguna de mis acciones de controlador tiene requisitos en HandleErrorAttributes.
  • Ninguna de las acciones de mi controlador contiene ningún código repetitivo de manejo de error de la placa de la caldera.
  • Tengo un solo punto de código de manejo de errores que me permite cambiar fácilmente el registro y otras cuestiones relacionadas con el manejo de errores.
  • Un requisito simple: las acciones del controlador que devuelven JsonResult deben tener el tipo de retorno JsonResult y no algún tipo base como ActionResult. Motivo: Ver el comentario del código en FooHandleErrorAttribute. ejemplo lado

Cliente:

var success = function(data) { 
    alert(data.myjsonobject.foo); 
}; 
var onError = function(data) { 
    var message = "Error"; 
    if(typeof data !== "undefined") 
    message += ": " + data.message; 
    alert(message); 
}; 
$.FooGetJSON(url, params, onSuccess, onError); 

comentarios son bienvenidos! Probablemente voy a un blog acerca de esta solución algún día ...

+0

boooo! es mejor tener una respuesta simple con solo una explicación necesaria que una gran respuesta por el bien de satisfacer una situación específica. busque una respuesta general la próxima vez, para que todos puedan usarla – pythonian29033

0

Para los errores de servidor/Protocolo I trataría de ser como REST/HTTP como sea posible (Compárese esto con usted a escribir en la dirección URL de su navegador):

  • se llama un elemento no existente (/ persons/{non-existing-id-here}). Devuelva un 404.
  • se produjo un error inesperado en el servidor (error de código). Devuelva un 500.
  • el usuario del cliente no está autorizado para obtener el recurso. Devuelva un 401.

Para errores específicos de lógica de dominio/negocios Yo diría que el protocolo se usa de la manera correcta y no hay error interno del servidor, responda con un error objeto JSON/XML o lo que prefiera describir sus datos con (compárese esto con el que rellenar formularios en un sitio web):

  • un usuario quiere cambiar su nombre de la cuenta, pero el usuario aún no ha verificado su cuenta haciendo clic en un enlace en un correo electrónico que fue enviado a el usuario. Devolución {"error": "Cuenta no verificada"} o lo que sea.
  • un usuario quiere pedir un libro, pero el libro se vendió (el estado cambió en DB) y ya no puede pedirse más. Retorno {"error": "Libro ya vendido"}.
0

Sí, debe usar códigos de estado HTTP.Y también volver preferentemente descripciones de error en un formato JSON más estandarizadas, como Nottingham’s proposal, ver apigility Error Reporting:

The payload of an API Problem has the following structure:

  • type: a URL to a document describing the error condition (optional, and "about:blank" is assumed if none is provided; should resolve to a human-readable document; Apigility always provides this).
  • title: a brief title for the error condition (required; and should be the same for every problem of the same type; Apigility always provides this).
  • status: the HTTP status code for the current request (optional; Apigility always provides this).
  • detail: error details specific to this request (optional; Apigility requires it for each problem).
  • instance: URI identifying the specific instance of this problem (optional; Apigility currently does not provide this).
Cuestiones relacionadas