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 (p. Ej., Tiempo de espera de la base de datos) o porque la solicitud fue incorrecta de alguna manera (p. Ej., Se proporcionó como argumento una ID que no existe en la base de datos), ¿cómo debería responder el servicio? ¿Qué códigos de estado HTTP son sensibles y debo devolver algún dato, si corresponde?
Anticipo que el servicio se llamará principalmente desde jQuery usando el complemento jQuery.form, ¿jQuery o este complemento tienen alguna forma predeterminada de manejar una respuesta de error?
EDITAR: Decidí 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. espera ajax.
fuente
Consulte esta pregunta para obtener información sobre las mejores prácticas para su situación.
La sugerencia principal (de 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 stackoverflow (en caso de que se pregunte cómo hacen los demás este tipo de cosas); escribir operaciones como votar have
"Success"
y"Message"
fields, independientemente de si se permitió el voto o no:{ Success:true, NewScore:1, Message:"", LastVoteTypeId:3 }
Como señaló @ Phil.H , debes ser consistente en lo que elijas. Es más fácil decirlo que hacerlo (¡como todo lo que está en desarrollo!).
Por ejemplo, si envía comentarios demasiado rápido sobre 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 detectará en suerror
devolución de llamada.Por mucho que "se sienta bien" usar jQuery +
.ashx
+ HTTP [códigos de estado] IMO, agregará más complejidad a la 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 cuando se intenta diseñar un cliente en torno a códigos de respuesta http con jQuery. Solo tiene dos opciones (¿fue un "éxito" o un "error"?), Que debe ampliar por su cuenta. Si tiene una pequeña cantidad de WebServices que manejan una pequeña cantidad de páginas, entonces podría estar bien, pero cualquier escala mayor puede complicarse.Es mucho más natural en un
.asmx
WebService (o WCF para el caso) devolver un objeto personalizado que personalizar el código de estado HTTP. Además, obtiene la serialización JSON de forma gratuita.fuente
Usar códigos de estado HTTP sería una forma RESTful de hacerlo, pero eso sugeriría que haga que el resto de la interfaz sea RESTful usando URI de recursos y así sucesivamente.
En realidad, 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 , sea implacablemente coherente.
fuente
error
una matriz (posiblemente vacía) y agregar unfatal_error: bool
parámetro le dará bastante flexibilidad.Creo que si solo publica una excepción, debe manejarse en la devolución de llamada de jQuery que se pasa 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 qué hacen otras personas también.
Esto es lo que hago, pero eso es solo mi $ .02
Si va a estar RESTful y devolver códigos de error, intente ceñirse a los códigos estándar establecidos por el W3C: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
fuente
He pasado algunas horas resolviendo este problema. Mi solución se basa en los siguientes deseos / requisitos:
Creo un HandleErrorAttribute (consulte los comentarios del código para obtener una explicación de los detalles). Se han omitido algunos detalles, incluidos los "usos", por lo que es posible que el código no se compile. Agrego el filtro a 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; } } }
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
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 algún día escribiré en un blog sobre esta solución ...
fuente
Definitivamente devolvería un error 500 con un objeto JSON que describe 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 consistencia al manejar condiciones de error potencialmente inesperadas.
Aparte, ¿por qué no usar 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.
fuente
Los andamios de rieles se utilizan
422 Unprocessable Entity
para este tipo de errores. Consulte RFC 4918 para obtener más información.fuente
Sí, debe utilizar códigos de estado HTTP. Y también, preferiblemente, devolver descripciones de errores en un formato JSON algo estandarizado, como la propuesta de Nottingham , consulte un Informe de errores de habilidad :
fuente
Si el usuario proporciona datos no válidos, definitivamente debería ser un
400 Bad Request
( La solicitud contiene una sintaxis incorrecta o no se puede cumplir ) .fuente
No creo que deba devolver ningún código de error http, sino excepciones personalizadas que son útiles para el cliente final de la aplicación para que la interfaz sepa lo que realmente ocurrió. No trataría de ocultar problemas reales con códigos de error 404 o algo por el estilo.
fuente
Para errores de servidor / protocolo, trataría de ser lo más REST / HTTP posible (Compare esto con usted escribiendo URL en su navegador):
Para errores específicos de dominio / lógica comercial, diría que el protocolo se usa de la manera correcta y no hay un 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):
fuente