WCFクライアントの「using」ブロックの問題の最善の回避策は何ですか?


404

usingブロック内でWCFサービスクライアントをインスタンス化するのが好きです。これは、実装するリソースを使用するための標準的な方法であるためですIDisposable

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

ただし、このMSDNの記事に記載されているように、WCFクライアントをusingブロックでラップすると、クライアントがエラー状態(タイムアウトや通信の問題など)になるエラーをマスクできます。簡単に言えば、Dispose()が呼び出されると、クライアントのClose()メソッドが起動しますが、障害のある状態であるためエラーがスローされます。その後、元の例外は2番目の例外によってマスクされます。良くない。

MSDN記事で推奨されている回避策は、usingブロックの使用を完全に回避し、代わりにクライアントをインスタンス化して次のようなものを使用することです。

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

usingブロックと比較して、それは醜いと思います。そして、クライアントが必要になるたびに書くコードがたくさん。

幸い、私はIServiceOrientedでこのような他のいくつかの回避策を見つけました。あなたは次から始めます:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

これにより、次のことが可能になります。

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

それは悪くありませんが、usingブロックほど表現力があり理解しやすいとは思いません。

私が現在使用しようとしている回避策は、まずblog.davidbarret.netで読みました。基本的に、クライアントのDispose()メソッドはどこで使用してもオーバーライドします。何かのようなもの:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

これusingにより、障害のある状態の例外をマスクする危険なしに、ブロックを再び許可できるように見えます。

それで、これらの回避策を使用するために注意しなければならない他の落とし穴はありますか?誰かがもっと良いものを思いついたことがありますか?


42
最後の(this.Stateを検査する)はレースです。ブール値をチェックするときはエラーにならないかもしれませんが、Close()を呼び出すときはエラーになるかもしれません。
ブライアン

15
あなたは状態を読みます。障害はありません。Close()を呼び出す前に、チャネルに障害が発生します。Close()がスローします。ゲームオーバー。
ブライアン

4
時間が経つ。非常に短い時間の場合もありますが、技術的には、チャネルの状態を確認してから閉じるように要求するまでの期間に、チャネルの状態が変化することがあります。
エリックキング

8
Action<T>代わりに使用しますUseServiceDelegate<T>。マイナー。
hIpPy 2013年

2
この静的ヘルパーはService<T>、単体テストが複雑になるため(ほとんどの静的なものと同様)、本当に好きではありません。それを使用しているクラスに注入できるように、非静的であることが望ましいです。
Fabio Marreco 2014年

回答:


137

実際、ブログ書いていましたがルークの回答を参照)、これは私のIDisposableラッパーよりも優れていると思います。典型的なコード:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(コメントごとに編集)

Usevoidを返すため、戻り値を処理する最も簡単な方法は、キャプチャされた変数を使用することです。

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

2
@MarcGravellそのクライアントをどこに注入できますか?ChannelFactoryがクライアントを作成し、ファクトリオブジェクトがServiceクラス内で新規作成されることを想定しています。つまり、カスタムファクトリを可能にするためにコードを少しリファクタリングする必要があります。これは正しいですか、またはここで明らかなものが欠けていますか?
Anttu 2012年

16
ラッパーを簡単に変更できるため、結果のキャプチャ変数は必要ありません。次のようなもの: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris

3
たぶん役に立つ https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón

この方法で資格情報を追加するにはどうすればよいですか?
ヒッパソス

2
私の意見では、最も正しい解決策は次のとおりです: 1)競合状態なしでClose / Abortパターンを実行する2)サービス操作が例外をスローする状況を処理する3)CloseおよびAbortメソッドの両方が例外をスローする状況を処理する4)処理するThreadAbortExceptionなどの非同期例外 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

IServiceOriented.comによって提唱されたソリューションとDavid Barretのブログによって提唱されたソリューションのどちらかを選択した場合、私はクライアントのDispose()メソッドをオーバーライドすることによって提供される単純さを好みます。これにより、使い捨てオブジェクトの場合と同様に、using()ステートメントを引き続き使用できます。ただし、@ Brianが指摘したように、このソリューションには競合状態が含まれています。これは、チェック時にStateに障害が発生することはないが、Close()が呼び出されるまでに発生する可能性があるため、CommunicationExceptionが引き続き発生する場合があります。

そこで、これを回避するために、私は両方の長所を組み合わせたソリューションを採用しました。

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

2
アンマネージリソースで 'Try-Finally'(または構文糖-"using(){}")ステートメントを使用するのは危険ではないですか 適例として、「閉じる」オプションが失敗した場合、例外はキャッチされず、最終的に実行されない可能性があります。また、finallyステートメントに例外がある場合、他の例外をマスクできます。そのため、Try-Catchが推奨されます。
Zack Jannsen 2013年

ザック、オブジェクトがはっきりしない。何が欠けていますか?Closeメソッドが例外をスローした場合、finallyブロックは、例外がスローされる前に実行されます。正しい?
Patrick Szalapski、2014

1
@jmoreno、編集を取り消しました。気が付くと、メソッドにはcatchブロックがまったくありません。アイデアは、ということである任意の(でも、最終的に)発生する例外は黙って捕捉されない、スローする必要があります。
マットデイビス

5
@MattDavisなぜsuccessフラグが必要なのですか?なんでtry { Close(); } catch { Abort(); throw; }
Konstantin Spirin 2014

try / catchを配置するのはClose(); success = true;どうですか?finallyブロックで正常に中止できた場合、例外がスローされたくありません。その場合にAbort()が失敗した場合にのみ、例外がスローされます。このようにして、try / catchは潜在的な競合状態の例外を隠し、さらにfinallyブロックで接続をabort()できるようにします。
goku_da_master 2016年

32

私はそれを正しく機能させるために高次関数を書きました。これをいくつかのプロジェクトで使用しましたが、うまく機能しているようです。これは、「使用」パラダイムなどなしで、物事が最初から行われるべき方法でした。

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

次のような呼び出しを行うことができます。

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

これは、例の場合とほとんど同じです。一部のプロジェクトでは、強く型付けされたヘルパーメソッドを記述しているため、「Wcf.UseFooService(f => f ...)」のように記述します。

すべてが考慮されているので、とてもエレガントだと思います。発生した特定の問題はありますか?

これにより、他の気の利いた機能をプラグインできるようになります。たとえば、あるサイトでは、ログインしたユーザーに代わってサイトがサービスに対して認証を行います。(サイト自体には資格情報はありません。)独自の "UseService"メソッドヘルパーを作成することで、チャネルファクトリを希望どおりに構成できます。また、生成されたプロキシを使用する必要はありません。どのインターフェイスでも実行できます。 。


例外が発生します:ChannelFactory.EndpointのAddressプロパティがnullでした。ChannelFactoryのエンドポイントには、有効なアドレスを指定する必要がありますGetCachedFactory方法とは?
マーシャル

28

これは、WCFクライアント呼び出しを処理するためにマイクロソフトが推奨する方法です。

詳細については、「予期される例外」を参照してください。

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

追加情報 多くの人々がWCFについてこの質問をしているようで、Microsoftは例外の処理方法を示す専用のサンプルを作成しました。

c:\ WF_WCF_Samples \ WCF \ Basic \ Client \ ExpectedExceptions \ CS \ client

サンプルをダウンロード: C#または VB

usingステートメント関連する非常に多くの問題があることを考慮して(熱くなっていますか?)この問題に関する内部のディスカッションスレッドは、コードカウボーイになり、よりクリーンな方法を見つけるために私の時間を無駄にするつもりはありません。私はそれを吸い上げて、サーバーアプリケーションにWCFクライアントをこの冗長な(まだ信頼できる)方法で実装します。

キャッチするオプションの追加の失敗

多くの例外が派生してCommunicationExceptionおり、それらの例外のほとんどは再試行する必要があるとは思いません。私はMSDNで各例外をくまなく調べて、(TimeOutException上記に加えて)再試行可能な例外の短いリストを見つけました。再試行する必要のある例外を逃した場合はお知らせください。

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
// reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
catch (EndpointNotFoundException enfe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

確かに、これは書くのにちょっとありふれたコードです。私は現在この回答を好みますが、そのコードに将来の問題を引き起こす可能性のある「ハッキング」は表示されません。


1
サンプルのコードはまだ問題を引き起こしていますか?UsingUsingプロジェクト(VS2013)を実行してみましたが、次の行"Hope this code wasn't important, because it might not happen."はまだ実行されています...
janv8000

14

私はようやく、この問題のクリーンな解決に向けたいくつかの確実なステップを見つけました。

このカスタムツールは、WCFProxyGeneratorを拡張して、例外処理プロキシを提供します。それはExceptionHandlingProxy<T>継承するという追加のプロキシを生成しますExceptionHandlingProxyBase<T>-後者はプロキシの機能の本質を実装します。その結果、チャネルファクトリとチャネルのライフタイムの管理を継承ClientBase<T>またはExceptionHandlingProxy<T>カプセル化するデフォルトプロキシの使用を選択できます。ExceptionHandlingProxyは、非同期メソッドとコレクション型に関して、[サービス参照の追加]ダイアログでの選択を尊重します。

Codeplexには、例外処理WCFプロキシジェネレーターと呼ばれるプロジェクトがあります。基本的には、新しいカスタムツールをVisual Studio 2008にインストールし、このツールを使用して新しいサービスプロキシ(サービス参照の追加)を生成します。障害のあるチャネル、タイムアウト、安全な廃棄を処理するための優れた機能がいくつかあります。ExceptionHandlingProxyWrapperという優れたビデオがここにあります、これがどのように機能するかを正確に説明しています。

Usingステートメントを安全に再度使用できます。チャネルが何らかの要求(TimeoutExceptionまたはCommunicationException)でフォルトが発生した場合、Wrapperはフォルトされたチャネルを再初期化してクエリを再試行します。それが失敗した場合、Abort()コマンドを呼び出してプロキシを破棄し、例外を再スローします。サービスがFaultExceptionコードをスローする場合、サービスは実行を停止し、プロキシは安全に中止され、期待どおりに正しい例外がスローされます。


@Shimmyステータスベータ版。日付:土2009年7月11日によってミケーレブスタマンテ。死んだプロジェクト?
Kiquenet 2016年

11

Marc Gravell、MichaelGG、およびMatt Davisの回答に基づいて、開発者は次のことを考え出しました。

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

使用例:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

これは可能な限り「using」構文に近く、voidメソッドを呼び出すときにダミー値を返す必要がなく、タプルを使用せずにサービスを複数回呼び出す(および複数の値を返す)ことができます。

また、これは ClientBase<T>必要に応じて、 ChannelFactoryの代わりに子孫で。

開発者が代わりに手動でプロキシ/チャネルを破棄したい場合、拡張メソッドが公開されます。


これを使用しているかどうかは、PoolingDuplexを使用していて、呼び出し後に接続を閉じない場合に意味があります。これにより、クライアントサービスが数日でも存続し、サーバーのコールバックを処理できるようになります。私が理解している限り、ここで説明されている解決策は、セッションごとに1つの呼び出しに対して意味がありますか?
2012年

@sll-これは、呼び出しが戻った直後に接続を閉じるためのものです(セッションごとに1つの呼び出し)。
TrueWill、2013年

@cacho DisposeSafelyプライベートにすることは確かに選択肢であり、混乱を避けるでしょう。誰かが直接それを呼び出したいユースケースがあるかもしれませんが、私は片手で思いつくことができません。
TrueWillは、

@truewillはドキュメンテーションだけのために、このメソッドがスレッドセーフであることを言及することも重要ですか?
カチョサンタ2015

1
私の意見では、最も正しい解決策は次のとおりです: 1)競合状態なしでClose / Abortパターンを実行する2)サービス操作が例外をスローする状況を処理する3)CloseおよびAbortメソッドの両方が例外をスローする状況を処理する4)処理するThreadAbortExceptionなどの非同期例外 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@マーク・グラベル

これを使用しても問題ありませんか?

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

または、同じ(Func<T, TResult>)場合Service<IOrderService>.Use

これらは変数を返すのを容易にします。


2
+1 @MarcGravell私もあなたの答えは「上手くいくはずだ」と思います:P(そして、アクションはnullリターンのFuncに関して実装できます)。このページ全体はごちゃごちゃです
Ruben Bartelink 2013年

7

これは何ですか?

これは、受け入れられた回答のCWバージョンですが、(私が完全に考えるものとして)例外処理が含まれています。

受け入れられた回答は、もはや存在しないこのウェブサイトを参照しています。トラブルを避けるために、最も関連性の高い部分をここに含めます。さらに、これらの厄介なネットワークタイムアウトを処理するための例外再試行処理を含めるように少し変更しました。

単純なWCFクライアントの使用法

クライアント側のプロキシを生成したら、これを実装するために必要なのはこれだけです。

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
});

ServiceDelegate.cs

このファイルをソリューションに追加します。再試行の回数や処理したい例外を変更したくない場合を除いて、このファイルを変更する必要はありません。

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS:この投稿をコミュニティウィキにしました。この回答から「ポイント」を収集することはしませんが、実装に同意する場合は賛成するか、編集して改善することをお勧めします。


この回答の特徴付けに同意するかどうかはわかりません。これは、とCWバージョンです、あなたの例外処理の考え方を追加します。
John Saunders

@JohnSaunders-True(例外処理の私の概念)。私が見逃しているか、誤って処理している例外があれば教えてください。
goodguys_activate

成功変数についてはどうですか?ソースコードに追加する必要があります:if(成功)return; ??
Kiquenet 2013年

最初の呼び出しがスローされ、2番目の呼び出しが成功した場合、mostRecentExはnullにならないため、とにかく5回の再試行に失敗した例外をスローしています。または私は何かを逃していますか?2回目、3回目、4回目、または5回目の試行が成功した場合、mostRecentExをどこでクリアしたかわかりません。また、成功した場合の返品も表示されません。ここで何か不足しているはずですが、例外がスローされない場合、このコードは常に5回実行されませんか?
Bart Calixto 2014年

@Bart- success == false最後のifステートメントに追加しました
goodguys_activate '28年

7

以下は質問のソースの拡張バージョンです複数のチャネルファクトリをキャッシュし、契約名で構成ファイル内のエンドポイントを検索するように拡張されています。

.NET 4を使用します(具体的には、反変、LINQなどvar)。

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

1
UseServiceDelegate<T>代わりになぜ使用するのAction<T>ですか?
Mike Mayer

1
元の作成者がそうしたと私が思う唯一の理由は、開発者がサービスの呼び出しに属していることを知っている強く型付けされたデリゲートを持っていることでした。しかし、私が見る限りAction<T>、同様に機能します。
ジェシーC.スライサー2013年

5

このようなラッパーは機能します:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

これにより、次のようなコードを記述できるようになります。

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

もちろん、必要な場合、ラッパーはより多くの例外をキャッチできますが、原則は同じです。


Disposeが特定の条件下で呼び出されないことについての議論を覚えています。WCFによってメモリリークが発生します。
goodguys_activate

メモリリークが発生したかどうかはわかりませんが、問題はこれです。DisposeIChannel を呼び出すと、チャネルがフォルト状態の場合に例外がスローされる可能性があります。これは、MicrosoftがDisposeスローしないように指定しているため問題です。したがって、上記のコードCloseは、例外がスローされた場合のケースの処理です。Abort投げた場合、それは深刻な問題である可能性があります。私は昨年12月にそれについてブログ投稿を書きました:blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson

4

Castle動的プロキシを使用してDispose()の問題を解決し、チャネルが使用できない状態のときにチャネルを自動更新することも実装しました。これを使用するには、サービスコントラクトとIDisposableを継承する新しいインターフェイスを作成する必要があります。動的プロキシはこのインターフェイスを実装し、WCFチャネルをラップします。

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

消費者がWCFの詳細について心配する必要なくWCFサービスを注入できるので、私はこれが好きです。そして、他のソリューションのような追加のくずはありません。

コードを見てください。実際には非常に単純です 。WCFDynamic Proxy


4

拡張メソッドを使用します。

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

4

IoCが必要ない場合、または自動生成されたクライアント(サービス参照)を使用している場合は、ラッパーを使用してクローズを管理し、GCが例外をスローしない安全な状態にあるときにクライアントベースを取得することができます。GCはserviceclientでDisposeを呼び出し、これによりが呼び出されますClose。既に閉まっているのでダメージはありません。私はこれを製品コードで問題なく使用しています。

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

次に、サーバーにアクセスするときに、クライアントを作成usingしてautodisconectで使用します。

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}

3

概要

この回答で説明されている手法を使用すると、次の構文のusingブロックでWCFサービスを利用できます。

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

もちろん、これをさらに適応させて、状況に固有のより簡潔なプログラミングモデルを実現することもできます。IMyServiceただし、使い捨てパターンを正しく実装するチャネルを表す実装を作成できることが重要です。


細部

これまでのすべての回答は、のWCFチャネル実装での「バグ」を回避する問題に対処していますIDisposable。最も簡潔なプログラミングモデル(using管理されていないリソースをブロックするためにブロックを使用できるようにする)を提供しているように見える答えはこれIDisposableです。バグのない実装でプロキシを実装するように変更します。このアプローチの問題は、保守性です。使用するプロキシーに対しては、この機能を再実装する必要があります。この回答のバリエーションで、継承ではなくコンポジションを使用してこの手法を一般化する方法を説明します。

最初の試み

実装にはさまざまな実装があるようですIDisposableが、議論のために、現在受け入れられている回答で使用されているものを採用します

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

上記のクラスで武装して、今書くことができます

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

これにより、次のusingブロックを使用してサービスを利用できます。

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

このジェネリックを作る

これまでに行ったのは、トーマスのソリューションを再定式化することです。このコードがジェネリックにならないのは、ProxyWrapper必要なサービスコントラクトごとにクラスを再実装する必要があるという事実です。次に、ILを使用してこのタイプを動的に作成できるクラスを見てみましょう。

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

新しいヘルパークラスで、今書くことができます

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

(を使用するClientBase<>代わりにChannelFactory<>)を継承する自動生成クライアントに同じテクニックを(わずかに変更して)使用することもできIDisposableます。


2

私はこの接続を閉じる方法が好きです:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}

1

これを処理する単純な基本クラスを作成しましNuGetパッケージとして入手でき、非常に使いやすいです。

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}

VS2013-.net 4.5.1の更新はありますか?stackoverflow.com/a/9370880/206730のような再試行のオプションはありますか?–
Kiquenet 2014年

@Kiquenet私はもうWCFに取り組んでいません。プルリクエストを送っていただければ、マージしてパッケージを更新します。
UfukHacıoğulları2014年

1
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

したがって、returnステートメントを適切に記述できます。

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 

1

ChannelFactoryの代わりにServiceClientを使用する場合のMarc Gravellの回答からServiceの実装を追加したいと思います。

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}

1

興味のある方のために、ここに受け入れられた回答のVB.NETの翻訳があります(下記)。このスレッドでは他の人のヒントをいくつか組み合わせて、簡潔にするために少し調整しました。

元のタグ(C#)のトピックから外れていることは認めますが、この優れたソリューションのVB.NETバージョンを見つけることができなかったので、他の人も同様に探していると思います。Lambdaの翻訳は少し難しいので、誰かの手間を省きたいです。

この特定の実装は、ServiceEndpoint実行時にを構成する機能を提供することに注意してください。


コード:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub



    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function



    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

使用法:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property

1

私たちのシステムアーキテクチャは、Unity IoCフレームワークを使用してClientBaseのインスタンスを作成することが多いため、他の開発者が使用することを強制する確実な方法はありません。using{}ブロックを。できるだけ簡単にできるようにするために、ClientBaseを拡張するこのカスタムクラスを作成し、破棄時、または誰かがUnityで作成したインスタンスを明示的に破棄しない場合のファイナライズ時にチャネルのクローズを処理します。

また、カスタム資格情報などのチャネルを設定するためにコンストラクターで実行する必要があるものもあるので、それもここにあります...

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

次に、クライアントは単に次のことができます。

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

そして、呼び出し元はこれらのいずれかを行うことができます:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}

Disposeメソッドで
Disposing

@Chad-Microsoftの一般的なFinalize / Disposeデザインパターンに従っていました:msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx ただし、変数を使用していないのは事実です通常の破棄とファイナライズの間で異なるクリーンアップを行う必要はありません。FinalizeがDispose()を呼び出し、コードをDispose(bool)からDispose()に移動するように書き換えることができます。
CodingWithSpike、

ファイナライザはオーバーヘッドを追加し、確定的ではありません。可能な限り回避します。Unityの自動ファクトリーを使用してデリゲートを挿入し、それらをブロックを使用して配置するか、または(より良い)作成/呼び出し/破棄サービスの動作を、注入されたインターフェースのメソッドの背後に隠します。依存関係を呼び出すたびに、プロキシが作成され、呼び出され、破棄されます。
TrueWill 2012

0

この投稿でいくつかの回答を紹介し、必要に応じてカスタマイズしました。

DoSomethingWithClient()メソッドを使用する前に、WCFクライアントで何かを実行する機能が必要でした。

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

ここにヘルパークラスがあります:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

そして私はそれを次のように使うことができます:

string data = Service<ServiceClient>.Use(x => x.GetData(7));

バインディングとエンドポイントを使用したクライアントコンストラクターはどうですか?TClient(バインディング、エンドポイント)
Kiquenet 2014年

0

次のようにDisposeを実装するチャネル用の独自のラッパーがあります。

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

これはうまく機能しているようで、usingブロックを使用できます。


0

次のヘルパーを使用するとvoid、非voidメソッドを呼び出すことができます。使用法:

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

クラス自体は次のとおりです。

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}

0

クライアントのDispose()をオーバーライドします。ClientBaseに基づくプロキシクラスを生成する必要はありません。また、チャネルの作成とキャッシュ管理する必要もありません。(WcfClientはABSTRACTクラスではなく、ClientBaseに基づいていることに注意してください)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}

0

これを行う私の方法は、IDisposableを明示的に実装する継承クラスを作成することでした。これは、GUIを使用してサービス参照を追加するユーザー(Add Service Reference)に役立ちます。サービス参照を作成するプロジェクトにこのクラスをドロップし、デフォルトのクライアントの代わりに使用します。

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

注:これは単にdisposeの単純な実装です。必要に応じて、より複雑なdisposeロジックを実装できます。

次に、次のように、通常のサービスクライアントで行われたすべての呼び出しを安全なクライアントに置き換えることができます。

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

私はこのソリューションが好きです。インターフェイスの定義にアクセスする必要がなくusing、コードを多かれ少なかれ同じに見えるようにしながら、期待どおりにステートメントを使用できます。

このスレッドの他のコメントで指摘されているように、スローされる可能性のある例外を処理する必要があります。


-2

また、a DynamicProxyを使用してDispose()メソッドを拡張することもできます。このようにして、次のようなことができます。

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.