Каким должен быть в формате JSON возврат в случае сбоя / ошибки
Я пишу службу JSON в C# (.ashx file). При успешном запросе к службе я возвращаю некоторые данные JSON. Если запрос завершается неудачно, либо потому, что было вызвано исключение (например, тайм-аут базы данных), либо потому, что запрос был каким-то образом неверен (например, в качестве аргумента был указан идентификатор, который не существует в базе данных), как должна реагировать служба? Какие коды состояния HTTP являются разумными, и должен ли я возвращать какие-либо данные, если таковые имеются?
Я ожидаю, что служба будет в основном вызывается из jQuery с помощью jQuery.form plugin, имеет ли jQuery или этот плагин какой-либо способ обработки ответа на ошибку по умолчанию?
EDIT: я решил, что буду использовать jQuery + .ashx + HTTP [коды состояния] при успешном выполнении я верну JSON, но при ошибке я верну строку, так как кажется, что это то, что опция ошибки для jQuery.Аякс ожидает.
11 ответов:
возвращаемый код состояния HTTP должен зависеть от типа возникшей ошибки. Если идентификатор не существует в базе данных, верните 404; если у пользователя недостаточно прав для выполнения этого вызова Ajax, верните 403; если время ожидания базы данных до того, как найти запись, верните 500 (ошибка сервера).
jQuery автоматически обнаруживает такие коды ошибок и запускает функцию обратного вызова, определенную в вызове Ajax. Документация: http://api.jquery.com/jQuery.ajax/
короткий пример a
$.ajaxошибка обратного вызова:$.ajax({ type: 'POST', url: '/some/resource', success: function(data, textStatus) { // Handle success }, error: function(xhr, textStatus, errorThrown) { // Handle error } });
посмотреть этот вопрос для некоторого понимания лучших практик для вашей ситуации.
предложение topline (из указанной ссылки) заключается в стандартизации структуры ответа (как для успеха, так и для неудачи), которую ищет ваш обработчик, улавливая все исключения на уровне сервера и преобразуя их в одну и ту же структуру. Например (от ответ):
{ 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.Ч указал, вы должны быть последовательны во всем. Это легче сказать, чем сделать (как и все в развитии!).
например, если вы слишком быстро отправляете комментарии на SO, вместо того, чтобы быть последовательным и возвращать
{ Success: false, Message: "Can only comment once every blah..." }так будет бросать сервер исключение (
HTTP 500) и поймать его в своиerrorобратный.насколько это "чувствует себя правильно", чтобы использовать jQuery +
.ashx+ HTTP [status codes] IMO это добавит больше сложности в вашу клиентскую базу кода, чем это стоит. Поймите, что jQuery не "обнаруживает" коды ошибок, а скорее отсутствие кода успеха. Это важное различие при попытке создать клиент вокруг кодов ответа http с помощью jQuery. Вы получаете только два варианта (был ли это "успех"или " ошибка"?), который вы придется ветвиться дальше самостоятельно. Если у вас есть небольшое количество веб-сервисов, ведущих небольшое количество страниц, то это может быть хорошо, но что-нибудь более крупномасштабное может стать грязным.это гораздо более естественно в
.asmxWebService (или WCF, если на то пошло), чтобы вернуть пользовательский объект, чем настроить код состояния HTTP. Кроме того, вы получаете сериализацию JSON бесплатно.
использование кодов состояния HTTP было бы спокойным способом сделать это, но это предложило бы вам сделать остальную часть интерфейса спокойной, используя URI ресурсов и так далее.
на самом деле, определите интерфейс, как вам нравится (верните объект ошибки, например, детализируя свойство с ошибкой, и кусок HTML, который объясняет его и т. д.), Но как только вы решили что-то, что работает в прототипе, будьте безжалостно последовательны.
Я думаю, что если вы просто пузырь исключение, оно должно быть обработано в обратный вызов jQuery, который передается для опции 'error'. (Мы также регистрируем это исключение на стороне сервера в Центральном журнале). Никакого специального кода ошибки HTTP не требуется, но мне любопытно посмотреть, что делают другие люди.
Это то, что я делаю, но это просто мои $.02
Если вы собираетесь быть спокойным и возвращать коды ошибок, попробуйте придерживаться стандартных кодов, установленных W3C: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Я потратил несколько часов на решение этой проблемы. Мое решение основано на следующих пожеланиях / требованиях:
- не имеют повторяющийся шаблонный код обработки ошибок во всех действиях контроллера JSON.
- сохранить коды состояния HTTP (ошибка). Зачем? Потому что проблемы более высокого уровня не должны влиять на реализацию более низкого уровня.
- быть в состоянии получить данные JSON, когда ошибка/исключение происходят на сервере. Зачем? Потому что мне может понадобиться богатая информация об ошибках. Например. сообщение об ошибке, код состояния ошибки домена, трассировка стека (в среде отладки/разработки).
- простота использования на стороне клиента-предпочтительнее использовать jQuery.
Я создаю атрибут HandleErrorAttribute (см. комментарии к коду для объяснения деталей). Некоторые детали, включая "использование", были опущены, поэтому код может не компилироваться. Я добавляю фильтр к глобальным фильтрам во время инициализации приложения в Global.асакс.в 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);что я получу от всего этого? Конечным результатом является то, что
- ни одно из моих действий контроллера не имеет требований к атрибутам HandleErrorAttributes.
- ни одно из моих действий контроллера не содержит повторяющегося кода обработки ошибок котельной плиты.
- у меня есть одна точка кода обработки ошибок, позволяющая мне легко изменить ведение журнала и другие связанные с обработкой ошибок вещи.
- простое требование: действия контроллера, возвращающие JsonResult, должны иметь тип возврата JsonResult, а не какой-либо базовый тип, например ActionResult. Причина: см. комментарий код в 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);комментарии приветствуются! Я, вероятно, блог об этом решении когда-нибудь...
Я определенно вернул бы ошибку 500 с объектом JSON, описывающим условие ошибки, подобное как Ан ASP.NET AJAX "ScriptService" возвращает ошибку. Я считаю, что это довольно стандартно. Это определенно приятно иметь такую согласованность при обработке потенциально неожиданных условий ошибки.
в сторону, почему бы просто не использовать встроенные функции в .NET, если вы пишете его на C#? Службы WCF и ASMX позволяют легко сериализовать данные в формате JSON, без изобретение колеса.
рельсы леса использовать
422 Unprocessable Entityдля таких ошибок. Смотрите RFC 4918 для получения дополнительной информации.
Да, вы должны использовать коды состояния HTTP. А также желательно возвращать описания ошибок в несколько стандартизированном формате JSON, например Ноттингема см. сообщение об ошибке apigility:
полезная нагрузка проблемы API имеет следующую структуру:
- тип: URL-адрес документа, описывающего условие ошибки (необязательно, и "about:blank" является предполагается, если ничего не предусмотрено; должен решитесь на удобочитаемое документ; Apigility всегда обеспечивает это).
- title: краткое название для условия ошибки (обязательно; и должно быть одинаковым для каждого проблема та же тип; Apigility всегда обеспечивает это).
- статус: код состояния HTTP для текущего запроса (необязательно; Apigility всегда предоставляет это).
- деталь: ошибка подробности, относящиеся к этому запросу (необязательно; Apigility требует его для каждого проблема.)
- экземпляр: URI, определяющий конкретный экземпляр этой проблемы (необязательно; Apigility в настоящее время не обеспечивает этого).
если пользователь предоставляет неверные данные, это обязательно должно быть
400 Bad Request(запрос содержит неверный синтаксис или не может быть исполнено.)
Я не думаю, что вы должны возвращать какие-либо коды ошибок http, а пользовательские исключения, которые полезны для клиентской части приложения, чтобы интерфейс знал, что на самом деле произошло. Я бы не стал пытаться замаскировать реальные проблемы с кодами ошибок 404 или что-то в этом роде.
для ошибок сервера / протокола я бы попытался быть как можно более REST / HTTP (сравните это с вводом URL-адреса в вашем браузере):
- вызывается несуществующий элемент (/persons / {non-existing-id-here}). Возвращать 404.
- произошла непредвиденная ошибка на сервере (ошибка кода). Возвращает 500.
- пользователь клиента не имеет права на получение ресурса. Возвращает 401.
для домена/бизнес-логики конкретных ошибок я бы сказал протокол используется правильно, и нет внутренней ошибки сервера, поэтому ответьте с помощью объекта ошибки JSON / XML или того, что вы предпочитаете описывать свои данные (сравните это с заполнением форм на веб-сайте):
- пользователь хочет изменить имя своей учетной записи, но пользователь еще не проверил свою учетную запись, щелкнув ссылку в электронном письме, которое было отправлено пользователю. Возврат {"ошибка": "учетная запись не проверена"} или что-то еще.
- пользователь хочет заказать книгу, но книга был продан (состояние изменено в БД) и больше не может быть заказан. Возврат {"ошибка": "книга уже продана"}.
Comments