12

私の WCF クライアント ライブラリには、次のアーキテクチャが計画されています。

  • より多くの制御が必要であり、クライアントを別のアセンブリに保持し、WCF サービスが変更されたときに再生成を回避したいため、svcutil で生成されたプロキシの代わりに ChannelFactory を使用する
  • メッセージ インスペクターを使用して動作を WCF エンドポイントに適用する必要があるため、各チャネルは独自の認証トークンを送信できます。
  • 私のクライアント ライブラリは MVC フロントエンドから使用されるため、考えられるスレッドの問題について考える必要があります。
  • 私は.NET 4.5を使用しています(WCFクライアントをより良い方法で実装するためのヘルパーまたは新しいアプローチがあるかもしれませんか?)

さまざまな個別のビットに関する多くの記事を読みましたが、すべてを正しい方法でまとめる方法についてはまだ混乱しています。次の質問があります。

  1. 私が理解しているように、 ChannelFactory を静的変数にキャッシュしてから、そこからチャネルを取得することをお勧めしますよね?
  2. エンドポイントの動作は ChannelFactory 全体に固有のものですか、それとも各チャネルに個別に認証動作を適用できますか? 動作がファクトリ全体に固有のものである場合、これはエンドポイントの動作オブジェクトに状態情報を保持できないことを意味します。これは、同じ認証トークンがすべてのチャネルで再利用されるためですが、明らかに各チャネルには独自の認証トークンが必要です。現在の使用者。つまり、エンドポイントの動作内でトークンを計算する必要があります (HttpContext にトークンを保持でき、メッセージ インスペクターの動作はそれを発信メッセージに追加するだけです)。
  3. 私のクライアント クラスは使い捨てです (IDispose を実装します)。可能な状態 (開かれていない、開かれていない、失敗した...) にある可能性があることを知って、チャネルを正しく破棄するにはどうすればよいですか? 処分するだけですか?中絶してから処分しますか?閉じて (ただし、まだまったく開いていない可能性があります)、破棄しますか?
  4. チャンネルで作業中にエラーが発生した場合はどうすればよいですか? 壊れているのはチャネルだけですか、それとも ChannelFactory 全体が壊れていますか?

1 行のコードは 1,000 語以上を話すので、ここに私の考えをコード形式で示します。上記のすべての質問に「???」を付けました。コードで。

public class MyServiceClient : IDisposable
{
    // channel factory cache
    private static ChannelFactory<IMyService> _factory;
    private static object _lock = new object();

    private IMyService _client = null;
    private bool _isDisposed = false;

     /// <summary>
    /// Creates a channel for the service
    /// </summary>
    public MyServiceClient()
    {
        lock (_lock)
        {
            if (_factory == null)
            {
                // ... set up custom bindings here and get some config values

                var endpoint = new EndpointAddress(myServiceUrl);
                _factory = new ChannelFactory<IMyService>(binding, endpoint);

                // ???? do I add my auth behavior for entire ChannelFactory 
                // or I can apply it for individual channels when I create them?
            }
        }

        _client = _factory.CreateChannel();
    }

    public string MyMethod()
    {
        RequireClientInWorkingState();
        try
        {
            return _client.MyMethod();
        }
        catch
        {
            RecoverFromChannelFailure();
            throw;
        }
    }

    private void RequireClientInWorkingState()
    {
        if (_isDisposed)
            throw new InvalidOperationException("This client was disposed. Create a new one.");

        // ??? is it enough to check for CommunicationState.Opened && Created?
        if (state != CommunicationState.Created && state != CommunicationState.Opened)
            throw new InvalidOperationException("The client channel is not ready to work. Create a new one.");
    }

    private void RecoverFromChannelFailure()
    {
        // ??? is it the best way to check if there was a problem with the channel?
        if (((IChannel)_client).State != CommunicationState.Opened)
        {
            // ??? is it safe to call Abort? won't it throw?
            ((IChannel)_client).Abort();
        }

        // ??? and what about ChannelFactory? 
        // will it still be able to create channels or it also might be broken and must be thrown away? 
        // In that case, how do I clean up ChannelFactory correctly before creating a new one?
    }

    #region IDisposable

    public void Dispose()
    {    
        // ??? is it how to free the channel correctly?
        // I've heard, broken channels might throw when closing 
        // ??? what if it is not opened yet?
        // ??? what if it is in fault state?
        try
        {
            ((IChannel)_client).Close();
        }
        catch
        {
           ((IChannel)_client).Abort();              
        }

        ((IDisposable)_client).Dispose();

        _client = null;
        _isDisposed = true;
    }

    #endregion
}
4

1 に答える 1

15

私は遅くなったほうがいいと思います...そして作者はそれを機能させているようです。これは将来のWCFユーザ​​ーに役立つかもしれません。

1) ChannelFactory は、チャネルのすべての動作を含むチャネルを配置します。CreateChannel メソッドを使用してチャネルを作成すると、チャネルが「アクティブ化」されます。チャネル ファクトリはキャッシュできます。

2) バインディングと動作を使用してチャネル ファクトリを形成します。この形は、このチャンネルを作成するすべての人に共有されます。コメントで指摘したように、メッセージ インスペクタをアタッチできますが、より一般的なケースは、ヘッダーを使用してカスタム状態情報をサービスに送信することです。OperationContext.Current を介してヘッダーを添付できます。

using (var op = new OperationContextScope((IContextChannel)proxy))
{
    var header = new MessageHeader<string>("Some State");
    var hout = header.GetUntypedHeader("message", "urn:someNamespace");
    OperationContext.Current.OutgoingMessageHeaders.Add(hout);
}

3) これは、クライアント チャネルとファクトリを破棄する一般的な方法です (このメソッドは、私の ProxyBase クラスの一部です)。

public virtual void Dispose()
{
    CloseChannel();
    CloseFactory();
}

protected void CloseChannel()
{
    if (((IChannel)_client).State == CommunicationState.Opened)
    {
        try
        {
            ((IChannel)_client).Close();
        }
        catch (TimeoutException /* timeout */)
        {
            // Handle the timeout exception
            ((IChannel)innerChannel).Abort();
        }
        catch (CommunicationException /* communicationException */)
        {
            // Handle the communication exception
            ((IChannel)_client).Abort();
        }
    }
}

protected void CloseFactory()
{
    if (Factory.State == CommunicationState.Opened)
    {
        try
        {
            Factory.Close();
        }
        catch (TimeoutException /* timeout */)
        {
            // Handle the timeout exception
            Factory.Abort();
        }
        catch (CommunicationException /* communicationException */)
        {
            // Handle the communication exception
            Factory.Abort();
        }
    }
}

4) WCF は、ファクトリではなくチャネルに障害を起こします。再接続ロジックを実装できますが、それにはカスタム ProxyBase などからクライアントを作成して派生させる必要があります。

protected I Channel
{
    get
    {
        lock (_channelLock)
        {
            if (! object.Equals(innerChannel, default(I)))
            {
                ICommunicationObject channelObject = innerChannel as ICommunicationObject;
                if ((channelObject.State == CommunicationState.Faulted) || (channelObject.State == CommunicationState.Closed))
                {
                    // Channel is faulted or closing for some reason, attempt to recreate channel
                    innerChannel = default(I);
                }
            }

            if (object.Equals(innerChannel, default(I)))
            {
                Debug.Assert(Factory != null);
                innerChannel = Factory.CreateChannel();
                ((ICommunicationObject)innerChannel).Faulted += new EventHandler(Channel_Faulted);
            }
        }

        return innerChannel;
    }
}

5) チャネルを再利用しないでください。開いて、何かをして、閉じるというのが通常の使用パターンです。

6) 共通のプロキシ基本クラスを作成し、そこからすべてのクライアントを派生させます。これは、再接続、呼び出し前/呼び出し後のロジックの使用、ファクトリからのイベントの消費 (Faulted、Opening など) などに役立ちます。

7) 独自の CustomChannelFactory を作成します。これにより、ファクトリの動作をさらに制御できます。たとえば、デフォルトのタイムアウトを設定したり、さまざまなバインディング設定 (MaxMessageSizes) を適用したりできます。

public static void SetTimeouts(Binding binding, TimeSpan? timeout = null, TimeSpan? debugTimeout = null)
        {
            if (timeout == null)
            {
                timeout = new TimeSpan(0, 0, 1, 0);
            }
            if (debugTimeout == null)
            {
                debugTimeout = new TimeSpan(0, 0, 10, 0);
            }
            if (Debugger.IsAttached)
            {
                binding.ReceiveTimeout = debugTimeout.Value;
                binding.SendTimeout = debugTimeout.Value;
            }
            else
            {
                binding.ReceiveTimeout = timeout.Value;
                binding.SendTimeout = timeout.Value;
            }
        }
于 2014-03-02T05:32:53.523 に答える