jQuery 失败/错误时 JSON 服务应该返回什么
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/674027/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What should a JSON service return on failure / error
提问by thatismatt
I'm writing a JSON service in C# (.ashx file). On a successful request to the service I return some JSON data. If the request fails, either because an exception was thrown (e.g. database timeout) or because the request was wrong in some way (e.g. an ID that doesn't exist in the database was given as an argument) how should the service respond? What HTTP status codes are sensible, and should I return any data, if any?
我正在用 C#(.ashx 文件)编写一个 JSON 服务。在成功请求服务时,我返回一些 JSON 数据。如果请求失败,要么是因为抛出了异常(例如数据库超时),要么是因为请求在某些方面是错误的(例如,数据库中不存在的 ID 作为参数被提供)服务应该如何响应?哪些 HTTP 状态代码是合理的,我是否应该返回任何数据(如果有)?
I'm anticipating that service will mainly be called from jQuery using the jQuery.form plugin, does jQuery or this plugin have any default way of handling an error response?
我预计该服务将主要使用 jQuery.form 插件从 jQuery 调用,jQuery 或此插件是否有任何处理错误响应的默认方式?
EDIT:I've decided I'll use jQuery + .ashx + HTTP [status codes] on success I'll return JSON but on error I'll return a string, as it appears that that is what the error option for jQuery.ajax expects.
编辑:我决定成功时我将使用 jQuery + .ashx + HTTP [状态代码] 我将返回 JSON 但错误时我将返回一个字符串,因为这似乎是 jQuery 的错误选项。阿贾克斯期待。
采纳答案by Ron DeVera
The HTTP status code you return should depend on the type of error that has occurred. If an ID doesn't exist in the database, return a 404; if a user doesn't have enough privileges to make that Ajax call, return a 403; if the database times out before being able to find the record, return a 500 (server error).
您返回的 HTTP 状态代码应取决于发生的错误类型。如果数据库中不存在 ID,则返回 404;如果用户没有足够的权限进行 Ajax 调用,则返回 403;如果数据库在能够找到记录之前超时,则返回 500(服务器错误)。
jQuery automatically detects such error codes, and runs the callback function that you define in your Ajax call. Documentation: http://api.jquery.com/jQuery.ajax/
jQuery 会自动检测此类错误代码,并运行您在 Ajax 调用中定义的回调函数。文档:http: //api.jquery.com/jQuery.ajax/
Short example of a $.ajax
error callback:
$.ajax
错误回调的简短示例:
$.ajax({
type: 'POST',
url: '/some/resource',
success: function(data, textStatus) {
// Handle success
},
error: function(xhr, textStatus, errorThrown) {
// Handle error
}
});
回答by Crescent Fresh
See this questionfor some insight into best-practices for your situation.
请参阅此问题以深入了解适合您情况的最佳实践。
The topline suggestion (from said link) is to standardize a response structure (for both success and failure) that your handler looks for, catching all Exceptions at the server layer and converting them to the same structure. For example (from this answer):
顶线建议(来自上述链接)是标准化处理程序查找的响应结构(成功和失败),在服务器层捕获所有异常并将它们转换为相同的结构。例如(来自这个答案):
{
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"
}
}
This is the approach stackoverflow uses (in case you were wondering how others do this kind of thing); write operations like voting have "Success"
and "Message"
fields, regardless of if the vote was allowed or not:
这是 stackoverflow 使用的方法(以防你想知道其他人是如何做这种事情的);无论是否允许投票,都可以写操作,例如投票具有"Success"
和"Message"
字段:
{ Success:true, NewScore:1, Message:"", LastVoteTypeId:3 }
As @Phil.H pointed out, you should be consistent in whatever you choose. This is easier said than done (as is everything in development!).
正如@Phil.H 指出的那样,无论您选择什么,您都应该保持一致。这说起来容易做起来难(就像开发中的一切一样!)。
For example, if you submit comments too quickly on SO, instead of being consistent and returning
例如,如果您在 SO 上提交评论太快,而不是保持一致并返回
{ Success: false, Message: "Can only comment once every blah..." }
SO will throw a server exception (HTTP 500
) and catch it in their error
callback.
SO 将抛出服务器异常 ( HTTP 500
) 并在其error
回调中捕获它。
As much as it "feels right" to use jQuery + .ashx
+ HTTP [status codes] IMO it will add more complexity to your client-side code base than it's worth. Realize that jQuery does not "detect" error codes but rather the lack of a success code. This is an important distinction when trying to design a client around http response codes with jQuery. You only get two choices (was it a "success" or "error"?), which you have to branch further on your own. If you have a small number of WebServices driving a small number of pages then it might be okay, but anything larger scale may get messy.
尽管使用 jQuery + .ashx
+ HTTP [状态代码] IMO “感觉正确”,它会给您的客户端代码库增加比其价值更多的复杂性。意识到 jQuery 不会“检测”错误代码,而是缺少成功代码。当尝试使用 jQuery 围绕 http 响应代码设计客户端时,这是一个重要的区别。你只有两个选择(是“成功”还是“错误”?),你必须自己进一步分支。如果您有少量 Web 服务驱动少量页面,那么它可能没问题,但任何更大的规模都可能变得混乱。
It's much more natural in a .asmx
WebService (or WCF for that matter) to return a custom object than to customize the HTTP status code. Plus you get the JSON serialization for free.
在.asmx
WebService(或 WCF)中返回自定义对象比自定义 HTTP 状态代码要自然得多。此外,您还可以免费获得 JSON 序列化。
回答by Phil H
Using HTTP status codes would be a RESTful way to do it, but that would suggest you make the rest of the interface RESTful using resource URIs and so on.
使用 HTTP 状态代码将是一种 RESTful 方式来做到这一点,但这会建议您使用资源 URI 等使接口的其余部分成为 RESTful。
In truth, define the interface as you like (return an error object, for example, detailing the property with the error, and a chunk of HTML that explains it, etc), but once you've decided on something that works in a prototype, be ruthlessly consistent.
事实上,根据自己的喜好定义接口(返回一个错误对象,例如,详细说明带有错误的属性,以及解释它的一段 HTML 等),但是一旦你决定了在原型中工作的东西,无情地保持一致。
回答by Dan Esparza
I think if you just bubble an exception, it should be handled in the jQuery callback that is passed in for the 'error' option. (We also log this exception on the server side to a central log). No special HTTP error code required, but I'm curious to see what other folks do, too.
我认为如果你只是冒泡一个异常,它应该在为 'error' option 传入的jQuery 回调中处理。(我们还在服务器端将此异常记录到中央日志中)。不需要特殊的 HTTP 错误代码,但我也很想知道其他人是怎么做的。
This is what I do, but that's just my $.02
这就是我所做的,但这只是我的 $.02
If you are going to be RESTful and return error codes, try to stick to the standard codes set forth by the W3C: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
如果您打算使用 RESTful 并返回错误代码,请尝试遵循 W3C 规定的标准代码:http: //www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
回答by Bjarke
I've spend some hours solving this problem. My solution is based on the following wishes/requirements:
我花了几个小时来解决这个问题。我的解决方案基于以下愿望/要求:
- Don't have repetitive boilerplate error handling code in all JSON controller actions.
- Preserve HTTP (error) status codes. Why? Because higher level concerns should not affect lower level implementation.
- Be able to get JSON data when an error/exception occur on the server. Why? Because I might want rich error information. E.g. error message, domain specific error status code, stack trace (in debug/development environment).
- Ease of use client side - preferable using jQuery.
- 在所有 JSON 控制器操作中不要有重复的样板错误处理代码。
- 保留 HTTP(错误)状态代码。为什么?因为更高级别的关注不应该影响更低级别的实现。
- 能够在服务器发生错误/异常时获取 JSON 数据。为什么?因为我可能想要丰富的错误信息。例如错误消息、特定于域的错误状态代码、堆栈跟踪(在调试/开发环境中)。
- 易于使用的客户端 - 最好使用 jQuery。
I create a HandleErrorAttribute (see code comments for explanation of the details). A few details including "usings" has been left out, so the code might not compile. I add the filter to the global filters during application initialization in Global.asax.cs like this:
我创建了一个 HandleErrorAttribute(有关详细信息的解释,请参阅代码注释)。包括“使用”在内的一些细节已被省略,因此代码可能无法编译。我在 Global.asax.cs 中的应用程序初始化期间将过滤器添加到全局过滤器,如下所示:
GlobalFilters.Filters.Add(new UnikHandleErrorAttribute());
Attribute:
属性:
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;
}
}
}
I've developed the following jQuery plugin for client side ease of use:
为了客户端的易用性,我开发了以下 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 (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);
What do I get from all this? The final result is that
我从这一切中得到什么?最后的结果是
- None of my controller actions has requirements on HandleErrorAttributes.
- None of my controller actions contains any repetitive boiler plate error handling code.
- I have a single point of error handling code allowing me to easily change logging and other error handling related stuff.
- A simple requirement: Controller actions returning JsonResult's must have return type JsonResult and not some base type like ActionResult. Reason: See code comment in FooHandleErrorAttribute.
- 我的控制器操作都没有对 HandleErrorAttributes 的要求。
- 我的控制器操作都不包含任何重复的样板错误处理代码。
- 我有一个错误处理代码点,允许我轻松更改日志记录和其他错误处理相关的内容。
- 一个简单的要求:返回 JsonResult 的控制器操作必须具有返回类型 JsonResult 而不是像 ActionResult 这样的基类型。原因:参见 FooHandleErrorAttribute 中的代码注释。
Client side example:
客户端示例:
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);
Comments are most welcome! I'll probably blog about this solution some day...
评论是最受欢迎的!有一天我可能会在博客上介绍这个解决方案......
回答by mb21
Yes, you should use HTTP status codes. And also preferably return error descriptions in a somewhat standardized JSON format, like Nottingham's proposal, see apigility Error Reporting:
是的,您应该使用 HTTP 状态代码。并且最好以某种标准化的 JSON 格式返回错误描述,例如Nottingham 的提案,请参阅apigility 错误报告:
The payload of an API Problem has the following structure:
- type: a URL to a document describing the error condition (optional, and "about:blank" is assumed if none is provided; should resolve to a human-readabledocument; Apigility always provides this).
- title: a brief title for the error condition (required; and should be the same for every problem of the same type; Apigility always provides this).
- status: the HTTP status code for the current request (optional; Apigility always provides this).
- detail: error details specific to this request (optional; Apigility requires it for each problem).
- instance: URI identifying the specific instance of this problem (optional; Apigility currently does not provide this).
API 问题的有效负载具有以下结构:
- type: 描述错误条件的文档的 URL(可选,如果没有提供,则假定为“about:blank”;应解析为人类可读的文档;Apigility 始终提供此内容)。
- title:错误条件的简短标题(必需;对于同一类型的每个问题都应该是相同的;Apigility 总是提供这个)。
- status:当前请求的 HTTP 状态代码(可选;Apigility 始终提供此代码)。
- detail:特定于此请求的错误详细信息(可选;Apigility 要求每个问题都有它)。
- instance:标识此问题的特定实例的 URI(可选;Apigility 目前不提供此功能)。
回答by ZiggyTheHamster
回答by Dave Ward
I would definitely return a 500 error with a JSON object describing the error condition, similar to how an ASP.NET AJAX "ScriptService" error returns. I believe this is fairly standard. It's definitely nice to have that consistency when handling potentially unexpected error conditions.
我肯定会返回一个带有 JSON 对象的 500 错误描述错误条件,类似于ASP.NET AJAX“ScriptService”错误返回的方式。我相信这是相当标准的。在处理潜在的意外错误情况时,拥有这种一致性绝对是件好事。
Aside, why not just use the built in functionality in .NET, if you're writing it in C#? WCF and ASMX services make it easy to serialize data as JSON, without reinventing the wheel.
另外,如果您是用 C# 编写的,为什么不直接使用 .NET 中的内置功能呢?WCF 和 ASMX 服务可以轻松地将数据序列化为 JSON,而无需重新发明轮子。
回答by Daniel Serodio
If the user supplies invalid data, it should definitely be a 400 Bad Request
(The request contains bad syntax or cannot be fulfilled.)
如果用户提供了无效的数据,它肯定是一个400 Bad Request
(请求包含错误的语法或无法完成。)
回答by Quintin Robinson
I don't think you should be returning any http error codes, rather custom exceptions that are useful to the client end of the application so the interface knows what had actually occurred. I wouldn't try and mask real issues with 404 error codes or something to that nature.
我认为您不应该返回任何 http 错误代码,而是返回对应用程序客户端有用的自定义异常,以便接口知道实际发生了什么。我不会试图用 404 错误代码或类似的东西来掩盖真正的问题。