78 votes

Que doit faire un JSON service de retour sur la panne / erreur

Je suis en train d'écrire un JSON service en C# (.ashx fichier). Sur le succès de la demande pour le service, j'ai renvoyer des données JSON. Si la requête échoue, soit parce qu'une exception a été levée (par exemple, la base de données de délai d'attente) ou parce que la demande était mal, d'une certaine façon (par exemple, un ID qui n'existe pas dans la base de données a été donné en argument) comment le service de réagir? Ce que les codes d'état HTTP sont sensibles, et dois-je le renvoyer toutes les données, le cas échéant?

Je m'attends à ce service va principalement être appelée à partir de jQuery en utilisant jQuery.forme de plugin, n'jQuery ou ce plugin ont tout défaut de traitement d'une réponse d'erreur?

EDIT: j'ai décidé que je vais utiliser jQuery + .ashx + HTTP [codes d'état] sur le succès que je vais retourner JSON mais sur une erreur que je vais retourner une chaîne de caractères, comme il semble que c'est ce que l'erreur d'option pour jQuery.ajax attend.

55voto

Crescent Fresh Points 54070

Voir cette question pour un aperçu des meilleures pratiques pour votre situation.

La ligne du dessus suggestion (à partir de ce lien) est de normaliser une structure de réponse (succès et échec) que votre gestionnaire de recherche, attraper toutes les Exceptions à la couche de serveur et de les convertir à la même structure. Par exemple (à partir de cette réponse):

{
    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"
    }
} 

C'est l'approche stackoverflow utilise (dans le cas où vous vous demandez comment les autres font ce genre de chose); les opérations d'écriture comme de vote ont "Success" et "Message" champs, indépendamment de si le vote était autorisé ou pas:

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

Comme @Phil.H a souligné, vous devez être cohérent dans ce que vous choisissez. C'est plus facile à dire qu'à faire (tout est dans le de développement!).

Par exemple, si vous soumettez des commentaires trop rapidement, au lieu d'être cohérent et de retour

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

Il en sera de jeter un serveur exception (HTTP 500) et de l'attraper dans leur error de rappel.

D'autant qu'il "se sent le droit" d'utiliser jQuery + .ashx + HTTP [codes d'état] de l'OMI, il va ajouter plus de complexité à votre code côté client de la base que ce qu'il vaut. Réaliser que jQuery n'est pas "détecter" les codes d'erreur, mais plutôt l'absence d'un code de réussite. C'est une distinction importante à faire lorsque vous essayez de concevoir un client autour des codes de réponse http avec jQuery. Vous n'avez que deux choix (c'était un "succès" ou "erreur"?), qui vous devez branche sur votre propre. Si vous avez un petit nombre de WebServices de la conduite d'un petit nombre de pages, alors qu'il pourrait être bien, mais rien de plus grande échelle peut être très salissant.

C'est beaucoup plus naturel en .asmx WebService (ou WCF) pour retourner un objet personnalisé que de personnaliser le code d'état HTTP. De Plus, vous avez la sérialisation JSON pour gratuit.

33voto

Ron DeVera Points 9085

Le code de statut HTTP de vous renvoyer dépend du type d'erreur qui s'est produite. Si l'ID n'existe pas dans la base de données, renvoyer une 404; si un utilisateur ne dispose pas de privilèges suffisants pour faire que l'appel Ajax, de retour d'une 403.

jQuery détecte automatiquement ces codes d'erreur, et exécute la fonction de rappel que vous définissez dans votre appel Ajax. Documentation: http://api.jquery.com/jQuery.ajax/

Petit exemple d'un $.ajax d'erreur de rappel:

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

17voto

Phil H Points 10133

À l'aide de codes d'état HTTP serait une bonne façon de le faire, mais que vous suggère de faire le reste de l'interface RESTful à l'aide de ressources d'Uri et ainsi de suite.

En vérité, définir l'interface que vous voulez retourner un objet d'erreur, par exemple, en détaillant la propriété avec l'erreur, et un morceau de code HTML qui explique, etc), mais une fois que vous avez décidé sur quelque chose qui fonctionne dans un prototype, être impitoyablement cohérente.

3voto

Bjarke Points 21

J'ai passé quelques heures à résoudre ce problème. Ma solution est basée sur la suite des souhaits et exigences:

  • N'ont pas répétitif standard code de gestion d'erreur dans tous JSON actions du contrôleur.
  • Préserver HTTP (erreur) des codes d'état. Pourquoi? Parce que niveau supérieur préoccupations ne devrait pas affecter le niveau inférieur de la mise en œuvre.
  • Être en mesure d'obtenir des données JSON lorsqu'une erreur/exception se produire sur le serveur. Pourquoi? Parce que je veuille l'erreur riche d'informations. E. g. message d'erreur spécifique au domaine d'erreur code d'état, trace de la pile (en debug/environnement de développement).
  • La facilité d'utilisation côté client préférable à l'aide de jQuery.

J'ai créer un HandleErrorAttribute (voir les commentaires dans le code pour l'explication des détails). Un peu de détails, y compris "l'usage" a été laissé de côté, de sorte que le code peut ne pas compiler. J'ai ajouter le filtre les filtres globaux au cours de l'initialisation de l'application Mondiale.asax.cs comme ceci:

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

Attribut:

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

J'ai mis au point la suite de plugin jQuery pour le côté client, la facilité d'utilisation:

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

Que dois-je obtenir de tout cela? Le résultat final est que

  • Aucun de mes actions de contrôleur a des exigences sur HandleErrorAttributes.
  • Aucun de mes actions de contrôleur contient tout répétitif de la chaudière de la plaque de code de gestion d'erreur.
  • J'ai un seul point de code de gestion d'erreur en me permettant de changer facilement de l'exploitation forestière et de la gestion d'erreur liées à des trucs.
  • Une simple exigence: que les actions du Contrôleur de retour JsonResult doivent avoir le type de retour JsonResult et non pas un type de base comme ActionResult. Raison: Voir le commentaire de code dans FooHandleErrorAttribute.

Côté Client exemple:

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

Les commentaires sont la plupart de bienvenue! Je vais probablement sur le blog, cette solution un jour...

3voto

Dan Esparza Points 11826

Je pense que si vous venez de bulle une exception, elle doit être traitée dans le jQuery de callback qui est passé dans le "erreur" option. (Nous enregistrons également cette exception sur le côté serveur à un centre de journal). Aucun code d'erreur HTTP requis, mais je suis curieux de voir ce que les autres gens le font, trop.

C'est ce que je fais, mais c'est juste mon $.02

Si vous allez être paisible et retourner les codes d'erreur, essayez de respecter les codes établis par le W3C: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X