当HTTP状态代码为401 Unauthorized时,IErrorHandler返回错误的消息体

我已经实现了IErrorHandler来处理在我的restful WCF服务的构造函数中抛出的授权exception。 捕获常规exception时,将按预期返回自定义类型,但ContentType标头不正确。

HTTP/1.1 500 Internal Server Error Content-Type: application/xml; ... {"ErrorMessage":"Error!"} 

但是,当error handling程序尝试返回401 Unauthorized 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 

这是我想要返回的对象。 我的应用程序中的所有其他对象都inheritance自此对象。 当抛出exception时,我们真正关心的是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 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中:

                      ....  

最后,我在使用WebFaultException时看到了类似的行为。 我的想法是,这是一些深埋的.Net恶作剧的结果。 我选择实现IErrorHandler,以便我可以捕获任何其他可能无法处理的exception。

参考:

https://msdn.microsoft.com/en-us/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-落实和

其他例子:

IErrorHandler似乎没有处理我在WCF中的错误..任何想法?

如何使用非OK http代码使自定义WCFerror handling程序返回JSON响应?

如何为HttpClient请求设置Content-Type标头?

我不太确定你的应用程序是如何实现的。 根据您的描述,我建议使用visual studio调试您的ErrorHandler,以查看exception是否到达您的回调。

如果是,请按照您希望的方式手动构建肥皂故障或响应。

如果没有,则表示exception发生在到达您的服务操作之前,它可能已经在Channel堆栈中失败,在这种情况下,一个简单的方法是添加额外的HttpModule来自定义或映射响应。 或者您可以尝试在通道堆栈中自定义编码器。

根据您编写的内容,您将在服务实现的构造函数中引发exception。 因为WCF使用reflection来创建服务实现,除非您的服务是Singleton,否则您将获得TargetInvocationException。

示例(使用LINQPad):

 void Main() { try { Activator.CreateInstance(typeof(Foo)); } catch(Exception e) { e.Message.Dump(); e.GetType().Name.Dump(); } } public class Foo { public Foo() { throw new AuthorizationFailedException(); } } public class AuthorizationFailedException : Exception { } 

基本上,避免在构造函数中基于业务逻辑抛出exception。 只有这样才能处理编程错误。

经过几乎整整一天的努力,我发现这是由IIS设置引起的。

在IIS中的我的API项目下,在“身份validation”菜单下,我将“表单身份validation”设置为“已启用”。 我关闭了这个’function’,上面的代码开始按预期工作。 我发现这是由于我团队中的另一位开发人员将代码置于web.config文件中,该文件改变了IIS中的设置。 特别:

   ...    ...  

此外,通过使用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();