我正在用C#(.ashx文件)编写JSON服务。在成功请求服务后,我返回一些JSON数据。如果请求失败,要么是因为抛出异常(例如数据库超时),要么是因为请求在某种程度上是错误的(例如,数据库中不存在的ID作为参数),服务应该如何响应?什么HTTP状态代码是明智的,我应该返回任何数据,如果有的话?
我预计服务将主要使用jQuery.form插件从jQuery调用,jQuery或者这个插件是否有任何默认的方式来处理错误响应?
编辑:我已经决定成功使用jQuery + .ashx + HTTP [状态代码]我会返回JSON但是出错时我会返回一个字符串,因为它出现了这就是jQuery.ajax的错误选项所期望的。
答案 0 :(得分:55)
请参阅this question,了解您所处情况的最佳做法。
topline建议(来自所述链接)是标准化处理程序查找的响应结构(成功和失败),捕获服务器层的所有异常并将它们转换为相同的结构。例如(来自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"
}
}
这是stackoverflow使用的方法(如果你想知道别人怎么做这种事情);无论投票是否被允许,像投票这样的写操作都有"Success"
和"Message"
字段:
{ Success:true, NewScore:1, Message:"", LastVoteTypeId:3 }
作为@Phil.H pointed out,无论你选择什么,都应该保持一致。这说起来容易做起来就像开发中的一切一样!
例如,如果您在SO上过快地提交评论,而不是保持一致并返回
{ Success: false, Message: "Can only comment once every blah..." }
SO将抛出服务器异常(HTTP 500
)并在error
回调中捕获它。
尽管使用jQuery + .ashx
+ HTTP [状态代码] IMO“感觉正确”,但它会增加客户端代码库的复杂性,而不是它的价值。意识到jQuery不会“检测”错误代码,而是缺少成功代码。当尝试使用jQuery围绕http响应代码设计客户端时,这是一个重要的区别。你只有两个选择(是“成功”还是“错误”?),你必须自己进一步分支。如果您有少量WebServices驱动少量页面,那么它可能没问题,但任何规模较大的可能都会变得混乱。
在.asmx
WebService(或WCF)中返回自定义对象比自定义HTTP状态代码要自然得多。另外,您可以免费获得JSON序列化。
答案 1 :(得分:32)
您返回的HTTP状态代码应该取决于发生的错误类型。如果数据库中不存在ID,则返回404;如果用户没有足够的权限进行Ajax调用,则返回403;如果数据库在能够找到记录之前超时,则返回500(服务器错误)。
jQuery会自动检测此类错误代码,并运行您在Ajax调用中定义的回调函数。文档:http://api.jquery.com/jQuery.ajax/
$.ajax
错误回调的简短示例:
$.ajax({
type: 'POST',
url: '/some/resource',
success: function(data, textStatus) {
// Handle success
},
error: function(xhr, textStatus, errorThrown) {
// Handle error
}
});
答案 2 :(得分:16)
使用HTTP状态代码将是一种RESTful方式,但这建议您使用资源URI等使接口的其余部分RESTful。
实际上,您可以根据需要定义界面(例如,返回错误对象,详细说明带有错误的属性,以及解释它的一大块HTML等),但是一旦确定了可行的内容在原型中,要无情地保持一致。
答案 3 :(得分:3)
我认为如果您只是冒出异常,应该在jQuery callback that is passed in for the 'error' option处理。 (我们还将服务器端的此异常记录到中央日志中)。不需要特殊的HTTP错误代码,但我很想知道其他人也会这样做。
这就是我所做的,但这只是我的$ .02
如果您要成为RESTful并返回错误代码,请尝试遵守W3C规定的标准代码:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
答案 4 :(得分:3)
我花了几个小时来解决这个问题。我的解决方案基于以下愿望/要求:
我创建了一个HandleErrorAttribute(有关详细信息,请参阅代码注释)。包括“使用”在内的一些细节已被遗漏,因此代码可能无法编译。我在Global.asax.cs中的应用程序初始化期间将过滤器添加到全局过滤器,如下所示:
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 (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);
我从这一切中得到了什么?最终结果是
客户端示例:
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);
非常欢迎评论!我有一天可能会在博客上发表这个解决方案...
答案 5 :(得分:2)
我肯定会使用描述错误条件的JSON对象返回500错误,类似于how an ASP.NET AJAX "ScriptService" error returns。我相信这是相当标准的。在处理潜在的意外错误情况时,保持这种一致性非常好。
除此之外,为什么不在.NET中使用内置功能,如果你用C#编写它? WCF和ASMX服务可以轻松地将数据序列化为JSON,而无需重新发明轮子。
答案 6 :(得分:2)
Rails脚手架使用422 Unprocessable Entity
来处理这类错误。有关详细信息,请参阅RFC 4918。
答案 7 :(得分:2)
是的,您应该使用HTTP状态代码。并且最好以某种标准化的JSON格式返回错误描述,例如Nottingham’s proposal,请参阅apigility Error Reporting:
API问题的有效负载具有以下结构:
- 类型:描述错误条件的文档的URL(可选,&#34; about:blank&#34;是 假设没有提供;应解析为人类可读的文档; Apigility总是 提供这个)。
- 标题:错误条件的简要标题(必填;并且每个都应该相同) 同一类型的问题; Apigility总是提供这个)。
- 状态:当前请求的HTTP状态代码(可选; Apigility始终提供此功能)。
- 详细信息:此请求特定的错误详细信息(可选; Apigility要求每个请求 问题)。
- 实例:标识此问题的特定实例的URI(可选;当前Apigility 不提供这个)。
答案 8 :(得分:1)
如果用户提供无效数据,则肯定应为400 Bad Request
(请求包含错误的语法或无法完成。)
答案 9 :(得分:0)
我认为您不应该返回任何http错误代码,而是返回对应用程序的客户端有用的自定义异常,以便接口知道实际发生了什么。我不会尝试用404错误代码掩盖真正的问题或类似的东西。
答案 10 :(得分:0)
对于服务器/协议错误,我会尽量使用REST / HTTP(与您在浏览器中输入URL进行比较):
对于域/业务逻辑特定的错误,我会说协议以正确的方式使用,并且没有服务器内部错误,因此使用错误JSON / XML对象或您喜欢的任何内容进行响应来描述您的数据(比较此你填写网站上的表格):