現在、WCF バックエンドを使用して WPF アプリケーションに取り組んでいます。例外処理用にクライアント ロギング ソリューションとサーバー ロギング ソリューションを実装しましたが、それらはうまく機能しますが、多くの場合、ネットワーク上で情報を結び付けるのは困難です。サーバーで例外が発生した場合、例外トークンをネットワーク経由で返す方法が必要でした。これにより、例外をクライアントに記録できるようになります。そうすれば、クライアント エラーのトラブルシューティングを行っているときに、それをサーバーの例外と簡単に関連付けることができます。
私たちのアーキテクチャについてもう少し情報を提供したいと思います。それから私の問題を述べます。
私たちの WCF 実装は、サービス参照を設定するためのすぐに使える方法よりも少し堅牢です。クライアントに動的プロキシ生成を実装しました。クライアントとサーバーで共有される Web サービス インターフェイスを作成し、Castle.DynamicProxy.ProxyGenerator クラスと CreateInterfaceProxyWithoutTarget メソッドを使用してプロキシを作成することで、これを実現しました。さらに、CreateInterfaceProxyWithoutTarget メソッドを呼び出すときに、IInterceptor 実装を指定します。サーバーには、トレースと障害の動作に使用される 3 つの主要なクラスがあります。
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
}
IServiceBehavior を実装する例外を処理するための ExceptionTraceBehavior (属性を継承、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 (FaultException fe) コードではなく、catch (FaultExcection fe) コードに分類されます。
ただし、プログラムでフォールト コントラクトに追加するコードを削除すると、すべてのサービス操作を [FaultContract(typeof(WcfServiceFaultDetail))] で装飾すると、クライアントは期待どおりに例外をキャッチします。
私が把握できる唯一のことは、プロキシが WSDL やその他のメタデータからではなくインターフェイスから動的に生成されており、インターフェイスにフォルト コントラクトの装飾がないため、プログラムによるフォルト コントラクトが尊重されていないことです。
その考えを念頭に置いて、IInterceptor 実装にフォールト コントラクトを追加する方法を見つけようとしましたが、成功しませんでした。
したがって、誰かがすでにこれを行っており、詳細を提供できることを願っています。どんな助けでも大歓迎です。