.net 如何使自定义 WCF 错误处理程序返回带有非 OK http 代码的 JSON 响应?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1149037/
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
How to make custom WCF error handler return JSON response with non-OK http code?
提问by
I'm implementing a RESTful web service using WCF and the WebHttpBinding. Currently I'm working on the error handling logic, implementing a custom error handler (IErrorHandler); the aim is to have it catch any uncaught exceptions thrown by operations and then return a JSON error object (including say an error code and error message - e.g. { "errorCode": 123, "errorMessage": "bla" }) back to the browser user along with an an HTTP code such as BadRequest, InteralServerError or whatever (anything other than 'OK' really). Here is the code I am using inside the ProvideFault method of my error handler:
我正在使用 WCF 和 WebHttpBinding 实现 RESTful Web 服务。目前我正在研究错误处理逻辑,实现自定义错误处理程序(IErrorHandler);目的是让它捕获操作抛出的任何未捕获的异常,然后返回一个 JSON 错误对象(包括错误代码和错误消息 - 例如 { "errorCode": 123, "errorMessage": "bla" })返回给浏览器用户以及一个 HTTP 代码,例如 BadRequest、InteralServerError 或其他任何东西(实际上除了“OK”之外的任何东西)。这是我在错误处理程序的 ProvideFault 方法中使用的代码:
fault = Message.CreateMessage(version, "", errorObject, new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var rmp = new HttpResponseMessageProperty();
rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
rmp.Headers.Add(HttpRequestHeader.ContentType, "application/json");
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
--> This returns with Content-Type: application/json, however the status code is 'OK' instead of 'InternalServerError'.
--> 这会返回 Content-Type: application/json,但是状态代码是“OK”而不是“InternalServerError”。
fault = Message.CreateMessage(version, "", errorObject, new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var rmp = new HttpResponseMessageProperty();
rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
//rmp.Headers.Add(HttpRequestHeader.ContentType, "application/json");
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
--> This returns with the correct status code, however the content-type is now XML.
--> 这会返回正确的状态代码,但是内容类型现在是 XML。
fault = Message.CreateMessage(version, "", errorObject, new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var response = WebOperationContext.Current.OutgoingResponse;
response.ContentType = "application/json";
response.StatusCode = HttpStatusCode.InternalServerError;
--> This returns with the correct status code and the correct content-type! The problem is that the http body now has the text 'Failed to load source for: http://localhost:7000/bla..' instead of the actual JSON data..
--> 这将返回正确的状态代码和正确的内容类型!问题是 http 正文现在有文本“无法加载源:http://localhost:7000/bla..”而不是实际的 JSON 数据。
Any ideas? I'm considering using the last approach and just sticking the JSON in the HTTP StatusMessage header field instead of in the body, but this doesn't seem quite as nice?
有任何想法吗?我正在考虑使用最后一种方法并将 JSON 粘贴在 HTTP StatusMessage 标头字段而不是正文中,但这似乎不太好?
回答by Inferis
Actually, this works for me.
实际上,这对我有用。
Here's my ErrorMessage class:
这是我的 ErrorMessage 类:
[DataContract]
public class ErrorMessage
{
public ErrorMessage(Exception error)
{
Message = error.Message;
StackTrace = error.StackTrace;
Exception = error.GetType().Name;
}
[DataMember(Name="stacktrace")]
public string StackTrace { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
[DataMember(Name = "exception-name")]
public string Exception { get; set; }
}
Combined with the last snippet above:
结合上面的最后一个片段:
fault = Message.CreateMessage(version, "", new ErrorMessage(error), new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var response = WebOperationContext.Current.OutgoingResponse;
response.ContentType = "application/json";
response.StatusCode = HttpStatusCode.InternalServerError;
This gives me proper errors as json. Thanks. :)
这给了我正确的 json 错误。谢谢。:)
回答by nadavy
Here's a complete solution based on some info from above:
这是基于上面的一些信息的完整解决方案:
Yes you have. You can create custom error handler and do what you feel like.
是的,你有。您可以创建自定义错误处理程序并做您想做的事情。
See the attached code.
请参阅随附的代码。
That's the custom error handler:
那是自定义错误处理程序:
public class JsonErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
// Yes, we handled this exception...
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// Create message
var jsonError = new JsonErrorDetails { Message = error.Message, ExceptionType = error.GetType().FullName };
fault = Message.CreateMessage(version, "", jsonError,
new DataContractJsonSerializer(typeof(JsonErrorDetails)));
// Tell WCF to use JSON encoding rather than default XML
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
// Modify response
var rmp = new HttpResponseMessageProperty
{
StatusCode = HttpStatusCode.BadRequest,
StatusDescription = "Bad Request",
};
rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
}
That's an extended service behavior to inject the error handler:
这是注入错误处理程序的扩展服务行为:
/// <summary>
/// This class is a custom implementation of the WebHttpBehavior.
/// The main of this class is to handle exception and to serialize those as requests that will be understood by the web application.
/// </summary>
public class ExtendedWebHttpBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// clear default erro handlers.
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add our own error handler.
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler());
//BehaviorExtensionElement
}
}
That's a custom binding so you'll be able to configure it in the web.config
这是一个自定义绑定,因此您可以在 web.config 中对其进行配置
/// <summary>
/// Enables the ExtendedWebHttpBehavior for an endpoint through configuration.
/// Note: Since the ExtendedWebHttpBehavior is derived of the WebHttpBehavior we wanted to have the exact same configuration.
/// However during the coding we've relized that the WebHttpElement is sealed so we've grabbed its code using reflector and
/// modified it to our needs.
/// </summary>
public sealed class ExtendedWebHttpElement : BehaviorExtensionElement
{
private ConfigurationPropertyCollection properties;
/// <summary>Gets or sets a value that indicates whether help is enabled.</summary>
/// <returns>true if help is enabled; otherwise, false. </returns>
[ConfigurationProperty("helpEnabled")]
public bool HelpEnabled
{
get
{
return (bool)base["helpEnabled"];
}
set
{
base["helpEnabled"] = value;
}
}
/// <summary>Gets and sets the default message body style.</summary>
/// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageBodyStyle" /> enumeration.</returns>
[ConfigurationProperty("defaultBodyStyle")]
public WebMessageBodyStyle DefaultBodyStyle
{
get
{
return (WebMessageBodyStyle)base["defaultBodyStyle"];
}
set
{
base["defaultBodyStyle"] = value;
}
}
/// <summary>Gets and sets the default outgoing response format.</summary>
/// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageFormat" /> enumeration.</returns>
[ConfigurationProperty("defaultOutgoingResponseFormat")]
public WebMessageFormat DefaultOutgoingResponseFormat
{
get
{
return (WebMessageFormat)base["defaultOutgoingResponseFormat"];
}
set
{
base["defaultOutgoingResponseFormat"] = value;
}
}
/// <summary>Gets or sets a value that indicates whether the message format can be automatically selected.</summary>
/// <returns>true if the message format can be automatically selected; otherwise, false. </returns>
[ConfigurationProperty("automaticFormatSelectionEnabled")]
public bool AutomaticFormatSelectionEnabled
{
get
{
return (bool)base["automaticFormatSelectionEnabled"];
}
set
{
base["automaticFormatSelectionEnabled"] = value;
}
}
/// <summary>Gets or sets the flag that specifies whether a FaultException is generated when an internal server error (HTTP status code: 500) occurs.</summary>
/// <returns>Returns true if the flag is enabled; otherwise returns false.</returns>
[ConfigurationProperty("faultExceptionEnabled")]
public bool FaultExceptionEnabled
{
get
{
return (bool)base["faultExceptionEnabled"];
}
set
{
base["faultExceptionEnabled"] = value;
}
}
protected override ConfigurationPropertyCollection Properties
{
get
{
if (this.properties == null)
{
this.properties = new ConfigurationPropertyCollection
{
new ConfigurationProperty("helpEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("defaultBodyStyle", typeof(WebMessageBodyStyle), WebMessageBodyStyle.Bare, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("defaultOutgoingResponseFormat", typeof(WebMessageFormat), WebMessageFormat.Xml, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("automaticFormatSelectionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("faultExceptionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None)
};
}
return this.properties;
}
}
/// <summary>Gets the type of the behavior enabled by this configuration element.</summary>
/// <returns>The <see cref="T:System.Type" /> for the behavior enabled with the configuration element: <see cref="T:System.ServiceModel.Description.WebHttpBehavior" />.</returns>
public override Type BehaviorType
{
get
{
return typeof(ExtendedWebHttpBehavior);
}
}
protected override object CreateBehavior()
{
return new ExtendedWebHttpBehavior
{
HelpEnabled = this.HelpEnabled,
DefaultBodyStyle = this.DefaultBodyStyle,
DefaultOutgoingResponseFormat = this.DefaultOutgoingResponseFormat,
AutomaticFormatSelectionEnabled = this.AutomaticFormatSelectionEnabled,
FaultExceptionEnabled = this.FaultExceptionEnabled
};
}
}
That's the web.config
那是 web.config
<system.serviceModel>
<diagnostics>
<messageLogging logMalformedMessages="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<bindings>
<webHttpBinding>
<binding name="regularService" />
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="AjaxBehavior">
<extendedWebHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="extendedWebHttp" type="MyNamespace.ExtendedWebHttpElement, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="MyWebService">
<endpoint address="" behaviorConfiguration="AjaxBehavior"
binding="webHttpBinding" bindingConfiguration="regularService"
contract="IMyWebService" />
</service>
</services>
Note:The behavior extension should be in one line EXACTLY as is (there's a bug in WCF).
注意:行为扩展应该按原样在一行中(WCF 中存在一个错误)。
That's my client side (part of our custom proxy)
那是我的客户端(我们自定义代理的一部分)
public void Invoke<T>(string action, object prms, JsAction<T> successCallback, JsAction<WebServiceException> errorCallback = null, JsBoolean webGet = null)
{
Execute(new WebServiceRequest { Action = action, Parameters = prms, UseGetMethod = webGet },
t =>
{
successCallback(t.As<T>());
},
(req, message, err)=>
{
if (req.status == 400) //Bad request - that's what we've specified in the WCF error handler.
{
var details = JSON.parse(req.responseText).As<JsonErrorDetails>();
var ex = new WebServiceException()
{
Message = details.Message,
StackTrace = details.StackTrace,
Type = details.ExceptionType
};
errorCallback(ex);
}
});
}
回答by Jaime Botero
In the latest version of WCF (As of 11/2011) there's a better way of doing this using WebFaultException. You can use it as follows in your service catch blocks:
在最新版本的 WCF(截至 11/2011)中,使用 WebFaultException 有更好的方法来做到这一点。您可以在服务 catch 块中按如下方式使用它:
throw new WebFaultException<ServiceErrorDetail>(new ServiceErrorDetail(ex), HttpStatusCode.SeeOther);
[DataContract]
public class ServiceErrorDetail
{
public ServiceErrorDetail(Exception ex)
{
Error = ex.Message;
Detail = ex.Source;
}
[DataMember]
public String Error { get; set; }
[DataMember]
public String Detail { get; set; }
}
回答by ChrisCa
I had the exact same problem. This was useful for me:
我有同样的问题。这对我很有用:
http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/
http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/
回答by steamer25
Double-check that your errorObject can be serialized by DataContractJsonSerializer. I ran into a problem where my contract implementation didn't provide a setter for one of the properties and was silently failing to serialize--resulting in similar symptoms: 'server did not send a response'.
仔细检查您的 errorObject 是否可以被 DataContractJsonSerializer 序列化。我遇到了一个问题,我的合约实现没有为其中一个属性提供设置器,并且默默地无法序列化 - 导致类似的症状:“服务器没有发送响应”。
Here's the code I used to get more details about the serialization error (makes a good unit test with an assertion and without the try/catch for breakpoint purposes):
这是我用来获取有关序列化错误的更多详细信息的代码(使用断言进行良好的单元测试,并且没有用于断点目的的 try/catch):
Stream s = new MemoryStream();
try
{
new DataContractJsonSerializer(typeof(ErrorObjectDataContractClass)).WriteObject(s, errorObject);
} catch(Exception e)
{
e.ToString();
}
s.Seek(0, SeekOrigin.Begin);
var json = new StreamReader(s, Encoding.UTF8).ReadToEnd();
回答by Mike Gledhill
Here's the solution I came up with:
这是我想出的解决方案:
Catching exceptions from WCF Web Services
Basically, you get your web service to set a OutgoingWebResponseContextvariable, and return nullas the result (yes, really !)
基本上,您让您的 Web 服务设置一个OutgoingWebResponseContext变量,并null作为结果返回(是的,真的!)
public List<string> GetAllCustomerNames()
{
// Get a list of unique Customer names.
//
try
{
// As an example, let's throw an exception, for our Angular to display..
throw new Exception("Oh heck, something went wrong !");
NorthwindDataContext dc = new NorthwindDataContext();
var results = (from cust in dc.Customers select cust.CompanyName).Distinct().OrderBy(s => s).ToList();
return results;
}
catch (Exception ex)
{
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.Forbidden;
response.StatusDescription = ex.Message;
return null;
}
}
Then, you get your caller to look out for errors, then check if a "statusText" value was returned.
然后,您让您的调用者注意错误,然后检查是否返回了“statusText”值。
Here's how I did it in Angular:
这是我在 Angular 中的做法:
$http.get('http://localhost:15021/Service1.svc/getAllCustomerNames')
.then(function (data) {
// We successfully loaded the list of Customer names.
$scope.ListOfCustomerNames = data.GetAllCustomerNamesResult;
}, function (errorResponse) {
// The WCF Web Service returned an error
var HTTPErrorNumber = errorResponse.status;
var HTTPErrorStatusText = errorResponse.statusText;
alert("An error occurred whilst fetching Customer Names\r\nHTTP status code: " + HTTPErrorNumber + "\r\nError: " + HTTPErrorStatusText);
});
And here's what my Angular code displayed in IE:
这是我在 IE 中显示的 Angular 代码:
Cool, hey ?
酷,嘿?
Completely generic, and no need to add Successor ErrorMessagefields to the [DataContract]data which your services are returning.
完全通用,无需向服务返回的数据添加Success或ErrorMessage字段[DataContract]。
回答by Roy Oliver
For those using web apps to call WFC, always return your JSON as a Stream. For errors, no need for a bunch of fancy/ugly code. Just change the http status code with:
对于使用 Web 应用程序调用 WFC 的用户,请始终将您的 JSON 作为流返回。对于错误,不需要一堆花哨/丑陋的代码。只需更改 http 状态代码:
System.ServiceModel.Web.WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.InternalServerError
Then instead of throwing the exception, format that exception or a custom error object into JSON and return it as a System.IO.Stream.
然后不是抛出异常,而是将该异常或自定义错误对象格式化为 JSON 并将其作为 System.IO.Stream 返回。
回答by JLamb
What does the ErrorMessage class look like?
ErrorMessage 类是什么样的?
Don't use the StatusMessage field for machine-readable data -- see http://tools.ietf.org/html/rfc2616#section-6.1.1.
不要将 StatusMessage 字段用于机器可读数据——请参阅http://tools.ietf.org/html/rfc2616#section-6.1.1。
Also, it may be okay that "the http body now has the text 'Failed to load source for: http://localhost:7000/bla..' instead of the actual JSON data.." -- a literal string is JSON data if I remember correctly.
此外,“http 正文现在具有文本 'Failed to load source for: http://localhost:7000/bla..' 而不是实际的 JSON 数据 ..”可能是可以的 - 文字字符串是 JSON如果我没记错的话,数据。


