415

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これにより、フォルト状態の例外をマスクする危険なしに、ブロックを再び許可できるようです。

では、これらの回避策を使用して注意しなければならない問題は他にありますか? 誰かがより良いものを思いつきましたか?

4

26 に答える 26

142

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

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

(コメントごとに編集)

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

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
于 2009-02-21T23:38:40.917 に答える
91

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

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

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
于 2009-09-14T23:24:46.627 に答える
34

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

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」メソッド ヘルパーを作成することで、チャネル ファクトリを希望どおりに構成できます。また、生成されたプロキシを使用する必要はありません。どのインターフェイスでも使用できます。 .

于 2009-02-21T23:06:01.933 に答える
29

これは、WCFクライアント呼び出しを処理するためのMicrosoftの推奨方法です。

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

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
}

確かに、これは書くべきちょっとしたありふれたコードです。私は現在この回答を好みますが、今後問題を引き起こす可能性のある「ハック」はコードに含まれていません。

于 2011-02-19T04:35:23.697 に答える
14

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

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

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

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

于 2010-01-22T08:06:55.247 に答える
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 の代わりに子孫で使用できます。

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

于 2012-08-24T20:41:27.480 に答える
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

これらにより、変数を簡単に返すことができます。

于 2013-05-02T10:53:57.173 に答える
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.");
    }
}
于 2011-08-30T19:23:19.933 に答える
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: この投稿をコミュニティ wiki にしました。この回答から「ポイント」を集めることはしませんが、実装に同意する場合は賛成するか、編集して改善することをお勧めします。

于 2012-02-21T01:22:26.993 に答える
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.

もちろん、ラッパーは必要に応じてより多くの例外をキャッチできますが、原則は変わりません。

于 2010-12-07T12:25:02.037 に答える
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 サービスを注入できるため、これが気に入っています。また、他のソリューションのように手間がかかることはありません。

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

于 2012-01-06T05:29:58.453 に答える
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し、自動切断で使用します。

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

    Ws.Open();

    Ws.Test();
}
于 2011-09-07T20:22:18.737 に答える
2

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

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
于 2015-10-07T07:07:59.583 に答える
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);
    }
}
于 2013-01-15T22:20:54.403 に答える
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
于 2015-01-07T06:08:36.857 に答える
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); 
}); 
于 2013-05-08T16:22:56.080 に答える
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);
        }
    }
}
于 2009-02-24T14:47:10.593 に答える
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();
            }
        }
    }
}
于 2015-01-14T14:10:30.750 に答える
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、コードを多かれ少なかれ同じように見せながら、期待どおりにステートメントを使用できるため、このソリューションが気に入っています。

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

于 2016-01-28T00:53:27.213 に答える
0

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

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

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));
于 2013-08-16T06:53:23.953 に答える
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;
        });
    }
}
于 2014-12-23T01:48:35.887 に答える
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 ブロックを使用できます。

于 2014-01-15T12:13:46.127 に答える
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();
    }
}
于 2015-09-03T10:54:30.413 に答える
-2

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
于 2013-05-23T12:03:21.260 に答える