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
ブロックを再度許可できるように見えます。
だから、これらの回避策を使用するために注意しなければならない他の落とし穴がありますか?誰かがもっと良いものを思いついていますか?
実際、私は ブログ ( 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
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();
}
}
高階関数 を書いて、正しく動作するようにしました。これをいくつかのプロジェクトで使用しましたが、うまく機能しているようです。これは、「使用」パラダイムなどを使用せずに、最初から物事を行う方法です。
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"メソッドヘルパーを記述することにより、チャネルファクトリを希望どおりに構成できます。生成されたプロキシを使用することもできません。どのインターフェイスでも実行できます。 。
これは、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
この問題については非常に多くの問題があることを考慮して 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
}
確かに、これは書くべきちょっとした普通のコードです。現在、私は この回答 を好んでおり、そのコードに今後の問題を引き起こす可能性のある「ハッキング」は見当たりません。
私はついに、この問題に対する明確な解決策に向けたいくつかの堅実なステップを見つけました。
このカスタムツールは、WCFProxyGeneratorを拡張して、例外処理プロキシを提供します。
ExceptionHandlingProxy<T>
と呼ばれる追加のプロキシを生成します。これはExceptionHandlingProxyBase<T>
を継承します。後者はプロキシの機能の本質を実装します。その結果、チャネルファクトリとチャネルのライフタイムの管理をカプセル化するClientBase<T>
またはExceptionHandlingProxy<T>
を継承するデフォルトプロキシを使用することを選択できます。 ExceptionHandlingProxyは、非同期メソッドとコレクションタイプに関して、[サービス参照の追加]ダイアログでの選択を尊重します。
Codeplex というプロジェクトがありますWCFプロキシジェネレーターの例外処理。基本的にはVisual Studio 2008に新しいカスタムツールをインストールし、このツールを使用して新しいサービスプロキシを生成します(サービス参照の追加)。障害のあるチャネル、タイムアウト、安全な廃棄に対処するための素晴らしい機能がいくつかあります。ここに ExceptionHandlingProxyWrapper と呼ばれる優れたビデオがあり、これがどのように機能するかを正確に説明しています。
Using
ステートメントを安全に再度使用できます。リクエスト(TimeoutExceptionまたはCommunicationException)でチャネルに障害が発生した場合、Wrapperは障害のあるチャネルを再初期化し、クエリを再試行します。それが失敗すると、Abort()
コマンドを呼び出してプロキシを破棄し、例外を再スローします。サービスがFaultException
コードをスローすると、実行が停止し、プロキシは予期したとおりに正しく例外をスローして安全に中止されます。
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>
の子孫でこれを使用できます。
開発者が代わりにプロキシ/チャネルを手動で破棄する場合、拡張メソッドが公開されます。
@マーク・グラヴェル
これを使用しても大丈夫ではないでしょうか:
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
これらは変数を返すのを簡単にします。
これは何ですか?
これは、受け入れられた回答の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にしました。この回答から「ポイント」を集めることはしませんが、実装に同意する場合は賛成するか、編集して改善することをお勧めします。
以下は 質問 からのソースの拡張バージョンで、複数のチャネルファクトリをキャッシュし、構成ファイルでエンドポイントを契約名で検索しようとするように拡張されています。
.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.");
}
}
このようなラッパーは機能します:
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.
ラッパーは、必要な場合、より多くの例外をキャッチできますが、原則は変わりません。
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();
}
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
拡張メソッドを使用します。
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;
}
}
この回答で説明されている手法を使用すると、次の構文の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
の別の実装を使用する場合もあります。
接続を閉じるこの方法が好きです:
var client = new ProxyClient();
try
{
...
client.Close();
}
finally
{
if(client.State != CommunicationState.Closed)
client.Abort();
}
興味のある方のために、ここに受け入れられた答えの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
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);
});
私たちのシステムアーキテクチャは、多くの場合 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);
}
}
}
これを処理する 単純な基本クラス を書きました。 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();
}
}
}
}
これを行う私の方法は、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
ステートメントを使用できます。
このスレッドの他のコメントで指摘されているようにスローされる例外を処理する必要があります。
この投稿でいくつかの回答を参照し、必要に応じてカスタマイズしました。
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));
次のヘルパーは、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;
});
}
}
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();
}
}
次のように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ブロックを使用できます。