我目前正在使用 WCF 后端开发 WPF 应用程序。我们已经为异常处理实现了一个客户端日志解决方案和一个服务器日志解决方案,它们工作得很好,但通常很难通过网络将信息联系在一起。如果服务器上发生异常,我想要一种通过网络将异常令牌传回的方法,以便我可以将异常记录到客户端上。这样,当我解决客户端错误时,我可以很容易地将它与服务器异常关联起来。
我想提供更多关于我们的架构的信息,然后我会陈述我的问题。
我们的 WCF 实现比用于设置服务引用的开箱即用方法更健壮一些。我们在客户端实现了动态代理生成。我们通过创建客户端和服务器共享的 Web 服务接口来实现这一点,并使用 Castle.DynamicProxy.ProxyGenerator 类和 CreateInterfaceProxyWithoutTarget 方法来创建代理。此外,当我们调用 CreateInterfaceProxyWithoutTarget 方法时,我们指定了一个 IInterceptor 实现。在服务器上,有三个主要类用于我们的跟踪和故障行为:
FaultOperationInvoker(实现 IOperationInvoker):尝试使用 IOperationInvoker.Invoke 调用服务方法。如果是错误异常类型,则重新抛出,如果是异常,则尝试确定是否存在具有特定详细信息类型的错误合同,如果是,则包装然后使用详细信息引发新的错误异常。
internal class FaultOperationInvoker : IOperationInvoker
{
IOperationInvoker innerOperationInvoker;
FaultDescription[] faults;
public FaultOperationInvoker(IOperationInvoker invoker, FaultDescription[] faults)
{
this.innerOperationInvoker = invoker;
this.faults = faults;
}
#region IOperationInvoker Members
object[] IOperationInvoker.AllocateInputs()
{
return this.innerOperationInvoker.AllocateInputs();
}
object IOperationInvoker.Invoke(object instance, object[] inputs, out object[] outputs)
{
try
{
return this.innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
catch (FaultException e)
{
ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation.");
//allow fault exceptions to bubble out
throw;
}
catch (Exception e)
{
Type exceptionType = e.GetType();
ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation.");
//if the excpetion is serializable and there operation is tagged to support fault contracts of type WcfSerivceFaultDetail
if (faults != null && (faults.OfType<WcfServiceFaultDetail>().Any() || faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) > 0))
{
throw new FaultException<WcfServiceFaultDetail>(new WcfServiceFaultDetail(true, e), "Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType));
}
else
{
throw new FaultException("Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType));
}
}
}
FaultOperationBehavior (Implements IOperationBehavior) 将调度操作调用程序指向上述故障操作调用程序。
[AttributeUsage(AttributeTargets.Method)]
public class FaultOperationBehavior : System.Attribute, IOperationBehavior
{
#region IOperationBehavior Members
void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }
void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription,
System.ServiceModel.Dispatcher.ClientOperation clientOperation) { }
void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription,
System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new FaultOperationInvoker(dispatchOperation.Invoker, operationDescription.Faults.ToArray());
}
void IOperationBehavior.Validate(OperationDescription operationDescription) { }
#endregion
}
ExceptionTraceBehavior(继承属性,实现IServiceBehavior)用于处理实现IServiceBehavior的异常。我们还有一个类(FaultOperationBehavior)
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, Inherited = true)]
public class ExceptionTraceBehavior : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var ep in serviceDescription.Endpoints)
{
foreach (var operation in ep.Contract.Operations)
{
if (operation.Behaviors.Contains(typeof(FaultOperationBehavior)))
continue;
operation.Behaviors.Add(new FaultOperationBehavior());
//Check to see if this operation description contains a wcf service fault detail operation. If it doesn't, add one.
if (operation.Faults != null && (operation.Faults.Count == 0 || operation.Faults.Count > 0 && operation.Faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) == 0))
{
FaultDescription faultDescription = new FaultDescription(operation.Name);
//faultDescription.Name = "WcfServiceFaultDetail";
//faultDescription.Namespace = "";
faultDescription.DetailType = typeof(WcfServiceFaultDetail);
operation.Faults.Add(faultDescription);
}
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
我们的每一个服务接口都有一个具体的实现。我们所有的服务也继承了我们的基础服务类,该类用 ExceptionTrace 属性装饰。
所以,现在有了背景信息,问题就来了。我希望每个服务操作都有一个详细类型为 WCFServiceFaultDetail 的故障合同,但我不想在每个服务操作上都添加一个 FaultContract 属性。正如您在 ExceptionTraceBehavior 中看到的那样,我想出了如何以编程方式添加故障协定,并且它非常适合将故障添加到操作中。当在操作调用程序中捕获到一个常规的旧异常时,它会发现存在正确的故障契约并抛出一个新的 FaultExcption。但是,当异常被客户端捕获回来时,它会落入 catch (FaultExcection fe) 代码,而不是 catch (FaultException fe) 代码。
但是,如果删除以编程方式添加到故障合同中的代码,我会用 [FaultContract(typeof(WcfServiceFaultDetail))] 装饰每个服务操作,客户端会按预期捕获异常。
我唯一能想到的是,由于代理是从接口而不是从 WSDL 或其他元数据动态生成的,并且接口上没有错误契约修饰,所以我的编程错误契约没有得到兑现。
考虑到这一点,我试图弄清楚如何在 IInterceptor 实现中添加故障契约,但没有成功。
所以我希望有人已经这样做了并且可以提供一些细节。任何帮助表示赞赏。