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
)并将其捕获到error
callback中。
就像使用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 Entity
来422 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)。