JSON服务应该返回什么样的失败/错误

我在C#(.ashx文件)中编写JSON服务。 在对服务的成功请求中,我返回了一些JSON数据。 如果请求失败,或者是因为引发了exception(例如数据库超时),或者是因为请求在某种方式上是错误的(例如数据库中不存在的ID作为参数),服务应该如何响应? 什么HTTP状态代码是明智的,我应该返回任何数据,如果有的话?

我期待的服务将主要从jQuery使用jQuery.form插件调用,jQuery或这个插件有任何默认的方式来处理错误响应?

编辑:我决定我会使用jQuery + .ashx + HTTP [状态代码]成功我会返回JSON,但错误我会返回一个string,因为它似乎是jQuery的错误选项。阿贾克斯期待。

您返回的HTTP状态代码应该取决于发生的错误types。 如果数据库中不存在一个ID,则返回一个404; 如果用户没有足够的权限来进行Ajax调用,则返回403; 如果数据库在能够查找logging之前超时,则返回500(服务器错误)。

jQuery自动检测这样的错误代码,并运行您在Ajax调用中定义的callback函数。 文档: http : //api.jquery.com/jQuery.ajax/

$.ajax错误callback的简短示例:

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

看到这个问题的一些洞察你的情况的最佳做法。

顶线build议(从上述链接)是标准化你的处理程序查找的响应结构(成功和失败),捕获服务器层的所有exception并将它们转换为相同的结构。 例如(从这个答案 ):

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

这是stackoverflow使用的方法(如果你想知道别人怎么做这种事情); 无论投票是否被允许,投票等写作操作都有"Success""Message"字段:

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

正如@ Phil.H所指出的那样 ,无论你select什么,你都应该保持一致。 说起来容易做起来难(一切都在开发中!)。

例如,如果您在SO上提交意见的速度太快,而不是一致并返回

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

所以会抛出一个服务器exception( HTTP 500 )并将其捕获到errorcallback中。

就像使用jQuery + .ashx + HTTP [状态码] IMO“感觉不错”一样,它会给你的客户端代码库增加更多的复杂性。 意识到jQuery不会“检测”错误代码,而是缺less成功的代码。 尝试围绕http响应代码devise客户端时,这是一个重要的区别。 你只有两个select(是“成功”还是“错误”?),你必须自己进一步分支。 如果你有less量WebServices驱动less量的页面,那么它可能是好的,但是任何更大的规模可能会变得混乱。

.asmx WebService(或WCF)中,返回自定义对象比自定义HTTP状态代码更自然。 另外你可以免费获得JSON序列化。

使用HTTP状态代码将是一个RESTful的方式,但是这会build议您使用资源URI等来使RESTful接口的其余部分。

事实上,按照你喜欢的方式来定义接口(例如,返回一个错误对象,详细说明错误的属性,以及解释它的HTML块等),但是一旦你决定了一个原型,无情地一致。

我花了几个小时解决这个问题。 我的解决scheme基于以下愿望/要求:

  • 在所有JSON控制器操作中不要有重复的样板error handling代码。
  • 保留HTTP(错误)状态代码。 为什么? 因为更高层次的关注不应该影响更低层次的实施。
  • 当服务器发生错误/exception时,能够获取JSON数据。 为什么? 因为我可能想要丰富的错误信息。 例如错误消息,特定于域的错误状态码,堆栈跟踪(在debugging/开发环境中)。
  • 易于使用的客户端 – 最好使用jQuery。

我创build一个HandleErrorAttribute(请参阅代码注释了解详细信息)。 包括“使用”在内的一些细节已被排除,因此代码可能无法编译。 我在Global.asax.cs的应用程序初始化过程中将filter添加到全局filter中,如下所示:

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

属性:

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

我已经开发了以下jQuery插件,方便客户端的使用:

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

我从这一切中得到了什么? 最后的结果是

  • 我的控制器操作都没有对HandleErrorAttributes的要求。
  • 我的控制器操作都不包含任何重复的锅炉板error handling代码。
  • 我有一个error handling代码点,使我可以轻松地更改日志logging和其他error handling相关的东西。
  • 一个简单的要求:返回JsonResult的Controller行为必须具有返回typesJsonResult,而不是像ActionResult那样的基types。 原因:请参阅FooHandleErrorAttribute中的代码注释。

客户端示例:

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

非常欢迎评论! 我可能会有一天在这个解决scheme的博客…

我认为,如果你只是冒泡一个exception,它应该在传入的'错误'选项的jQuerycallback中处理。 (我们也在服务器端将这个exceptionlogging到一个中央日志中)。 没有特殊的HTTP错误代码需要,但我很好奇,看看其他人也做了什么。

这是我做的,但那只是我的$ .02

如果您打算使用RESTful并返回错误代码,请尝试遵守W3C制定的标准代码: http : //www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

我肯定会返回一个500错误描述错误条件的JSON对象,类似于ASP.NET AJAX“ScriptService”错误返回的方式 。 我相信这是相当标准的。 在处理潜在的意外错误情况时,保持这种一致性是非常好的。

另外,为什么不使用.NET中的内置function呢,如果你用C#写的呢? WCF和ASMX服务可以轻松地将数据序列化为JSON,而无需重新发明轮子。

Rails脚手架使用422 Unprocessable Entity422 Unprocessable Entity这些types的错误。 有关更多信息,请参阅RFC 4918 。

如果用户提供了无效的数据,那肯定是一个400 Bad Request请求包含错误的语法或不能被满足。

我不认为你应该返回任何http错误代码,而是自定义的exception对应用程序的客户端有用,所以界面知道实际发生了什么。 我不会尝试用404错误代码掩盖真正的问题,或者这种性质的东西。

对于服务器/协议错误,我会尝试尽可能作为REST / HTTP(与您在浏览器中inputURL的方式进行比较):

  • (/ persons / {non-existing-id-here})。 返回一个404。
  • 在服务器(代码错误)发生意外的错误。 退货500。
  • 客户端用户无权获取资源。 返回一个401。

对于域/业务逻辑特定的错误,我会说这个协议是以正确的方式使用,并没有服务器内部错误,所以回应一个错误的JSON / XML对象或任何你喜欢描述你的数据(与你比较填写在网站上的表格):

  • 用户想要更改其帐户名称,但用户还没有通过点击发送给用户的电子邮件中的链接来validation其帐户。 返回{“错误”:“帐户未validation”}或其他。
  • 一个用户想订购一本书,但这本书已经卖出了(状态改变了),不能再订购了。 返回{“error”:“Book already sold”}。

是的,你应该使用HTTP状态码。 最好还是以标准化的JSON格式返回错误描述,如诺丁汉的build议 ,参见apigility错误报告 :

API问题的有效载荷具有以下结构:

  • 键入 :描述错误条件的文档的URL(可选,如果没有提供“about:blank”,则应假定为可读文档; Apigility始终提供)。
  • title :错误条件的简要标题(必需;对于同一types的每个问题应该是相同的; Apigility总是提供这个)。
  • 状态 :当前请求的HTTP状态码(可选; Apigility始终提供)。
  • 详细信息 :特定于此请求的错误详细信息(可选; Apigility要求用于每个问题)。
  • 实例 :标识此问题的特定实例的URI(可选; Apigility目前不提供此function)。