web-dev-qa-db-ja.com

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

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

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ブロックを再度許可できるように見えます。

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

393
Eric King

実際、私は ブログLukeの答え を参照)ですが、 this はIDisposableラッパーよりも優れていると思います。典型的なコード:

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

(コメントごとに編集)

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

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

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

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

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

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

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 ...)」などの記述を行うことになります。

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

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

30
MichaelGG

これは、WCFクライアントコールを処理するMicrosoftの推奨方法です。

詳細については、次を参照してください: Expected Exceptions

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

この問題については非常に多くの問題があることを考慮して singステートメントを含む(heated?)内部議論 および threads コードカウボーイになろうとして私の時間を無駄にして、よりクリーンな方法を見つけるために。私はそれを吸い込み、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
}

確かに、これは書くべきちょっとした普通のコードです。現在、私は この回答 を好んでおり、そのコードに今後の問題を引き起こす可能性のある「ハッキング」は見当たりません。

27
Christopher

私はついに、この問題に対する明確な解決策に向けたいくつかの堅実なステップを見つけました。

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

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

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

14
Neil

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));

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

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

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

10
TrueWill

@マーク・グラヴェル

これを使用しても大丈夫ではないでしょうか:

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

これらは変数を返すのを簡単にします。

8
pangular

これは何ですか?

これは、受け入れられた回答の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:この投稿をコミュニティWikiにしました。この回答から「ポイント」を集めることはしませんが、実装に同意する場合は賛成するか、編集して改善することをお勧めします。

7
Christopher

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

.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.");
    }
}
7
Jesse C. Slicer

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

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.

ラッパーは、必要な場合、より多くの例外をキャッチできますが、原則は変わりません。

5
Tomas Jansson

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を使用します。

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

    Ws.Open();

    Ws.Test();
}
4
Luiz Felipe

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サービスを注入できるため、これが気に入っています。そして、他のソリューションのような追加のクラフはありません。

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

4
Jay Douglass

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

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;
    }
}
3
Johan Nyman

概要

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

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

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

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


詳細

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

最初の試み

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();
}

この汎用化

これまでに行ったことは、 Tomasの解 を再定式化することだけです。このコードがジェネリックになることを妨げるのは、必要なすべてのサービスコントラクトに対して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の別の実装を使用する場合もあります。

3
Lawrence

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

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

興味のある方のために、ここに受け入れられた答えの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
InteXX
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
Andriy Buday

私たちのシステムアーキテクチャは、多くの場合 nityIoC フレームワークを使用して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);
        }
    }
}
1
CodingWithSpike

これを処理する 単純な基本クラス を書きました。 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);
    }
}

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
PSsam

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

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..
        }
    }
}

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

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

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

インターフェース定義にアクセスする必要がないため、このソリューションが好きです。また、コードをほぼ同じように見せながら、期待どおりにusingステートメントを使用できます。

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

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));
0
hIpPy

次のヘルパーは、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

ClientBaseに基づいてプロキシクラスを生成する必要もなく、また チャネル作成とキャッシュを管理する !を必要とせずに、クライアントのDispose()をオーバーライドします。 (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
Murad Duraidi

次のように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
Joe