verbos tutorial servicios servicio restful practicas peticiones para identificar español ejemplo dummies como buenas jquery web-services json

jquery - tutorial - ¿Qué debería devolver un servicio JSON por falla/error?



servicios rest buenas practicas (11)

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, el 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?

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

EDITAR: he decidido que usaré jQuery + .ashx + HTTP [códigos de estado] en caso de éxito Devolveré JSON pero en caso de error devolveré una cadena, ya que parece que esa es la opción de error para jQuery. ajax espera.


Consulte esta pregunta para conocer 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 su controlador busca, capturando todas las excepciones en la capa del servidor y convirtiéndolas a la misma estructura. Por ejemplo (de esta respuesta ):

{ 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 que usa (en caso de que se esté preguntando cómo otros hacen este tipo de cosas); las operaciones de escritura como votar tienen campos "Success" y "Message" , independientemente de si el voto fue permitido o no:

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

Como lo señaló @ Phil.H , debes ser coherente en lo que elijas. Esto es más fácil decirlo que hacerlo (¡como lo está todo en desarrollo!).

Por ejemplo, si envía comentarios demasiado rápido en SO, en lugar de ser coherente y regresar

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

SO lanzará una excepción de servidor ( HTTP 500 ) y la capturará en su devolución de llamada de error .

Por mucho que "se sienta bien" usar jQuery + .ashx + HTTP [códigos de estado] IMO agregará más complejidad de la que vale su base de código del lado del cliente. 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.

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


Creo que si solo haces una excepción, se debe manejar en la devolución de llamada jQuery que se transfiere para la opción ''error'' . (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 solo mi $ .02

Si va a ser RESTful y devuelve códigos de error, intente seguir los códigos estándar establecidos por el W3C: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html


Definitivamente devolvería un error 500 con un objeto JSON que describa la condición de error, similar a cómo regresa un error ASP.NET AJAX "ScriptService" . 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 usar la funcionalidad incorporada en .NET, si lo estás escribiendo en C #? Los servicios WCF y ASMX facilitan la serialización de datos como JSON, sin reinventar la rueda.


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 dichos 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 por error de $.ajax :

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


El uso de códigos de estado HTTP sería una forma RESTANTE de hacerlo, pero eso sugeriría que restaure el resto de la interfaz utilizando 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 funcione en un prototipo , sé implacablemente implacable.


Los andamios de rieles usan 422 Unprocessable Entity para este tipo de errores. Ver RFC 4918 para más información.


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.


Para los errores de servidor / protocolo, trataría de ser lo más REST / HTTP posible (Compare esto con usted ingresando las URL en su navegador):

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

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

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

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

  • No tenga un código de manejo de error repetitivo repetitivo en todas las acciones del controlador JSON.
  • Preserve 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.
  • Ser capaz de obtener datos JSON cuando ocurre un error / excepción en el servidor. ¿Por qué? Porque podría querer información rica sobre errores. Ej. Mensaje de error, código de estado de error específico del dominio, seguimiento de pila (en entorno de depuración / desarrollo).
  • Facilidad de uso del lado del cliente: preferiblemente usando 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 a los filtros globales durante la inicialización de la aplicación en Global.asax.cs de esta manera:

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://.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; } } }

Desarrollé el siguiente complemento jQuery para facilitar el uso del lado 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é obtengo 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 único 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 del lado del 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);

Los comentarios son bienvenidos! Probablemente bloguee sobre esta solución algún día ...


Sí, debes usar códigos de estado HTTP. Y también preferiblemente devuelve las descripciones de error en un formato JSON algo estandarizado, como la propuesta de Nottingham , consulte apigility Error Reporting :

La carga útil de un problema API tiene la siguiente estructura:

  • tipo : una URL a un documento que describe la condición de error (opcional, y "sobre: ​​en blanco" se asume si no se proporciona, debe resolverse en un documento legible por humanos , Apigility siempre proporciona esto).
  • title : un título breve para la condición de error (obligatorio, y debe ser el mismo para cada problema del mismo tipo ; Apigility siempre proporciona esto).
  • estado : el código de estado HTTP para la solicitud actual (opcional; Apigility siempre proporciona esto).
  • detalle : detalles de error específicos de esta solicitud (opcional; Apigility lo requiere para cada problema).
  • instancia : URI que identifica la instancia específica de este problema (opcional; actualmente, Apigility no proporciona esto).

Si el usuario proporciona datos no válidos, definitivamente debe ser una 400 Bad Request ( la solicitud contiene mala sintaxis o no se puede cumplir ) .