我目前正在使用WCF后端处理WPF应用程序。我们已经实现了客户端日志记录解决方案和用于异常处理的服务器日志记录解决方案,并且它们运行良好,但通常很难通过网络将信息绑定在一起。如果在服务器上发生异常,我想要一种方法将异常令牌传回到线路,以便我可以在客户端上将其记录,但异常。这样,当我在解决客户端错误时遇到问题时,我可以很容易地将其与服务器异常相关联。
我想提供一些关于我们架构的更多信息,然后我会陈述我的问题。
我们的WCF实现比设置服务引用的开箱即用方法更强大。我们在客户端上实现了动态代理生成。我们通过创建客户端和服务器共享的Web服务接口来完成此任务,并使用Castle.DynamicProxy.ProxyGenerator类和CreateInterfaceProxyWithoutTarget方法来创建代理。另外,当我们调用CreateInterfaceProxyWithoutTarget方法时,我们指定一个IInterceptor实现。 在服务器上有三个主要类用于跟踪和故障行为:
FaultOperationInvoker(Implements IOperationInvoker):尝试使用IOperationInvoker.Invoke调用服务方法。如果是故障异常类型,则重新抛出,如果是异常,则尝试确定是否存在具有特定详细信息类型的故障合同,如果是,则进行换行,然后使用详细信息引发新的故障异常。 / p>
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(Inherits Attribute,Implements 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实现中添加错误契约,但没有成功。
所以我希望有人已经这样做了,并且可以提供一些细节。任何帮助表示赞赏。
答案 0 :(得分:0)
我不是WCF专家,但我的手有点脏。
我认为你是对的。在创建某种类型的ChannelFactory时,会从程序集元数据中解析故障合同。由于您的接口未使用相应的FaultContract属性进行修饰,因此您的客户端使用默认的错误合同而没有详细信息。
在运行时向接口方法添加FaultContract属性也不会起作用。
一种解决方案可能是在运行时动态生成和使用类型,以生成渠道工厂。
我从来没有这样做,但我认为这可以从AppDomain的DefineDynamicAssembly开始。