我已经实现了IErrorHandler来处理在我的宁静WCF服务的构造函数中引发的授权异常。当捕获到一般异常时,将按预期返回我的自定义类型,但是ContentType标头不正确。
HTTP/1.1 500 Internal Server Error Content-Type: application/xml; ... {"ErrorMessage":"Error!"}
但是,当错误处理程序尝试返回401未经授权的http状态代码时,消息正文将覆盖为默认类型,但ContentType标头应为原样。
HTTP/1.1 401 Unauthorized Content-Type: application/json; ... {"Message":"Authentication failed.","StackTrace":null,"ExceptionType":"System.InvalidOperationException"}
显然这里有问题,但我不确定是什么。
如何实现IErrorHandler,使其以正确的标头返回json中的自定义类型?
BaseDataResponseContract对象:
[Serializable] [DataContract( Name = "BaseDataResponseContract" )] public class BaseDataResponseContract { [DataMember] public string ErrorMessage { get; set; } } // end
这是我要返回的对象。应用程序中的所有其他对象都从该对象继承。当引发异常时,我们真正关心的就是http状态代码和错误消息。
IErrorHandler实现(为简便起见,未显示日志):
namespace WebServices.BehaviorsAndInspectors { public class ErrorHandler : IErrorHandler { public bool HandleError(Exception error) { return true; } // end public void ProvideFault(Exception ex, MessageVersion version, ref Message fault) { // Create a new instance of the object I would like to return with a default message var baseDataResponseContract = new BaseDataResponseContract { ErrorMessage = "Error!" }; // Get the outgoing response portion of the current context var response = WebOperationContext.Current.OutgoingResponse; // Set the http status code response.StatusCode = HttpStatusCode.InternalServerError; // If the exception is a specific type change the default settings if (ex.GetType() == typeof(UserNotFoundException)) { baseDataResponseContract.ErrorMessage = "Invalid Username!"; response.StatusCode = HttpStatusCode.Unauthorized; } // Create the fault message that is returned (note the ref parameter) fault = Message.CreateMessage(version, "", baseDataResponseContract, new DataContractJsonSerializer(typeof(BaseDataResponseContract))); // Tell WCF to use JSON encoding rather than default XML var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json); fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty); // Add ContentType header that specifies we are using json var httpResponseMessageProperty = new HttpResponseMessageProperty(); httpResponseMessageProperty.Headers[HttpResponseHeader.ContentType] = "application/json"; fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponseMessageProperty); } // end } // end class } // end namespace
IServiceBehavior实现:
namespace WebServices.BehaviorsAndInspectors { public class ErrorHandlerExtensionBehavior : BehaviorExtensionElement, IServiceBehavior { public override Type BehaviorType { get { return GetType(); } } protected override object CreateBehavior() { return this; } private IErrorHandler GetInstance() { return new ErrorHandler(); } void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { var errorHandlerInstance = GetInstance(); foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) { dispatcher.ErrorHandlers.Add(errorHandlerInstance); } } void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end } // end class } // end namespace
Web.Config:
<system.serviceModel> <services> <service name="WebServices.MyService"> <endpoint binding="webHttpBinding" contract="WebServices.IMyService" /> </service> </services> <extensions> <behaviorExtensions> <!-- This extension if for the WCF Error Handling--> <add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> <ErrorHandlerBehavior /> </behavior> </serviceBehaviors> </behaviors> .... </system.serviceModel>
最后,使用WebFaultException时我看到类似的行为。我的想法是,这是一些深埋的.Net恶作剧的结果。我选择实现IErrorHandler,以便可以捕获可能无法处理的任何其他异常。
参考:
https://msdn.microsoft.com/zh- CN/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx
http://www.brainthud.com/cards/5218/25441/which-four-behavior-interfaces- exist-for-interacting-with-a-service-or-client-description-what-methods-do- they-实施和
经过一整天的努力,我发现这是由IIS设置引起的。
在IIS中的API项目下,在“身份验证”菜单下,将“表单身份验证”设置为“启用”。我关闭了此“功能”,上面的代码按预期开始工作。我发现这是由于团队中的另一个开发人员将代码放入web.config文件中,从而更改了IIS中的设置。特别:
<?xml version="1.0" encoding="utf-8"?> <configuration> ... <system.web> <authentication mode="Forms" /> </system.web> ... </configuration>
此外,通过使用WebOperationContext OutgoingResponse对象上的ContentType属性,我能够使Content- Type标头正确显示。
// Get the outgoing response portion of the current context var response = WebOperationContext.Current.OutgoingResponse; // Add ContentType header that specifies we are using JSON response.ContentType = new MediaTypeHeaderValue("application/json").ToString();