IErrorHandler 在 HTTP 状态代码为 401 未授权时返回错误的消息正文
本文关键字:返回 错误 正文 消息 授权 HTTP 状态 代码 IErrorHandler | 更新日期: 2023-09-27 17:55:36
我已经实现了IErrorHandler来处理在我的restful 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 Object:
[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
服务行为实现:
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
网络配置:
<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/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-implement-and
其他示例:
IErrorHandler似乎没有处理我在WCF中的错误..有什么想法吗?
如何使自定义 WCF 错误处理程序返回带有非正常 http 代码的 JSON 响应?
如何为 HttpClient 请求设置内容类型标头?
我不太确定您的应用程序是如何实现的。根据您的描述,我建议使用Visual Studio来调试错误处理程序,以查看异常是否到达您的回调。
如果是,请以所需的方式手动构造 SOAP 故障或响应。
如果没有,则意味着异常发生在到达您的服务操作之前,它可能已经在通道堆栈中失败,在这种情况下,一个简单的方法是添加额外的 HttpModule 来自定义或映射响应。或者,您可以尝试在通道堆栈中自定义编码器。
根据您编写的内容,您将在服务实现的构造函数中引发异常。由于 WCF 使用反射来创建服务实现,因此除非您的服务是单一实例,否则您将获得 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
{
}
基本上,避免在构造函数中基于业务逻辑引发异常。仅在处理编程错误时才这样做。
在为此苦苦挣扎了将近一整天之后,我发现这是由 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();