J'aime instancier mes clients du service WCF dans un bloc using
, car il s’agit plus ou moins du moyen standard d’utiliser des ressources qui implémentent IDisposable
:
using (var client = new SomeWCFServiceClient())
{
//Do something with the client
}
Mais, comme indiqué dans cet article MSDN , emballer un client WCF dans un bloc using
pourrait masquer les erreurs entraînant le client à rester dans un état défaillant (comme un problème de délai ou de communication ). En résumé, lorsque Dispose () est appelée, la méthode Close () du client se déclenche, mais renvoie une erreur car elle est dans un état défaillant. L'exception d'origine est alors masquée par la deuxième exception. Pas bon.
La solution suggérée dans l'article MSDN consiste à éviter complètement l'utilisation d'un bloc using
, mais à instancier vos clients et à les utiliser comme suit:
try
{
...
client.Close();
}
catch (CommunicationException e)
{
...
client.Abort();
}
catch (TimeoutException e)
{
...
client.Abort();
}
catch (Exception e)
{
...
client.Abort();
throw;
}
Comparé au bloc using
, je trouve ça moche. Et beaucoup de code à écrire à chaque fois que vous avez besoin d'un client.
Heureusement, j'ai trouvé quelques solutions de contournement, telles que celle sur IServiceOriented. Vous commencez avec:
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();
}
}
}
}
Ce qui permet alors:
Service<IOrderService>.Use(orderService =>
{
orderService.PlaceOrder(request);
});
Ce n'est pas mal, mais je ne pense pas que ce soit aussi expressif et compréhensible que le bloc using
.
La solution que j'essaie actuellement d'utiliser a d'abord été lue à propos de blog.davidbarret.net . Fondamentalement, vous substituez la méthode Dispose()
du client partout où vous l'utilisez. Quelque chose comme:
public partial class SomeWCFServiceClient : IDisposable
{
void IDisposable.Dispose()
{
if (this.State == CommunicationState.Faulted)
{
this.Abort();
}
else
{
this.Close();
}
}
}
Cela semble pouvoir autoriser le bloc using
à nouveau sans risque de masquer une exception d'état défaillante.
Alors, y a-t-il d'autres pièges que je dois surveiller pour utiliser ces solutions de contournement? Est-ce que quelqu'un a trouvé quelque chose de mieux?
En fait, bien que je blogué (voir réponse de Luke ), je pense que this est meilleur que mon emballage indélébile. Code typique:
Service<IOrderService>.Use(orderService=>
{
orderService.PlaceOrder(request);
});
(éditer par commentaires)
Puisque Use
renvoie un vide, le moyen le plus simple de gérer les valeurs de retour consiste à utiliser une variable capturée:
int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
{
newOrderId = orderService.PlaceOrder(request);
});
Console.WriteLine(newOrderId); // should be updated
Étant donné le choix entre la solution préconisée par IServiceOriented.com et la solution préconisée par blog de David Barret , je préfère la simplicité offerte par le remplacement de la méthode Dispose () du client. Cela me permet de continuer à utiliser l'instruction using () comme on pourrait s'y attendre avec un objet jetable. Cependant, comme l'a souligné @Brian, cette solution contient une condition de concurrence critique en ce sens que l'État ne peut être blâmé lors de la vérification mais qu'il peut l'être au moment où Close () est appelé, auquel cas l'exception CommunicationException se produit toujours.
Alors, pour résoudre ce problème, j'ai utilisé une solution qui mélange le meilleur des deux mondes.
void IDisposable.Dispose()
{
bool success = false;
try
{
if (State != CommunicationState.Faulted)
{
Close();
success = true;
}
}
finally
{
if (!success)
Abort();
}
}
J'ai écrit un fonction d'ordre supérieur pour que cela fonctionne correctement. Nous l'avons utilisé dans plusieurs projets et cela semble bien fonctionner. C’est ainsi que les choses auraient dû être faites depuis le début, sans le paradigme "d’utilisation" ou autre.
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();
}
}
}
Vous pouvez faire des appels comme ceci:
int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);
C'est à peu près comme vous avez dans votre exemple. Dans certains projets, nous écrivons des méthodes d'assistance fortement typées, nous finissons donc par écrire des choses comme "Wcf.UseFooService (f => f ...)".
Je le trouve assez élégant, tout compte fait. Avez-vous rencontré un problème particulier?
Cela permet de brancher d’autres fonctionnalités intéressantes. Par exemple, sur un site, le site s’authentifie auprès du service pour le compte de l’utilisateur connecté. (Le site n'a aucune information d'identification par lui-même.) En écrivant notre propre assistant de méthode "UseService", nous pouvons configurer la fabrique de canaux comme nous le souhaitons, etc. .
C’est la méthode recommandée par Microsoft pour gérer les appels du client WCF:
Pour plus de détails, voir: Exceptions attendues
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();
}
Informations complémentaires Tant de gens semblent poser cette question à la WCF et Microsoft a même créé un exemple dédié pour montrer comment gérer les exceptions:
c:\WF_WCF_Samples\WCF\Base\Client\ExpectedExceptions\CS\client
Téléchargez l'exemple: C # ou VB
Considérant qu'il y a tellement de problèmes impliquant la déclaration using , (chauffée?) Discussions internes et discussions sur cette question, je ne vais pas perdre mon temps à essayer de devenir un cow-boy de code et à trouver un moyen plus propre. Je vais me contenter d'implémenter les clients WCF de cette manière prolixe (mais fiable) pour mes applications serveur.
Autres échecs facultatifs à intercepter
Beaucoup d'exceptions proviennent de CommunicationException
et je ne pense pas que la plupart de ces exceptions devraient être retentées. J'ai parcouru chaque exception sur MSDN et trouvé une courte liste d'exceptions ré-essayables (en plus de TimeOutException
ci-dessus). Faites-moi savoir si j'ai manqué une exception qui devrait être retentée.
// 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
}
Certes, c'est un peu de code banal à écrire. Je préfère actuellement cette réponse , et je ne vois dans ce code aucun "piratage" qui puisse causer des problèmes par la suite.
J'ai enfin trouvé des étapes solides vers une solution propre à ce problème.
Cet outil personnalisé étend WCFProxyGenerator pour fournir un proxy de traitement des exceptions. Il génère un proxy supplémentaire appelé
ExceptionHandlingProxy<T>
, qui hérite deExceptionHandlingProxyBase<T>
, ce dernier implémentant le contenu de base de la fonctionnalité du proxy. Le résultat est que vous pouvez choisir d'utiliser le proxy par défaut qui hérite deClientBase<T>
ouExceptionHandlingProxy<T>
qui encapsule la gestion de la durée de vie de la fabrique de canaux et du canal. ExceptionHandlingProxy respecte vos sélections dans la boîte de dialogue Ajouter une référence de service en ce qui concerne les méthodes asynchrones et les types de collection.
Codeplex a un projet appelé Gestion des exceptions, générateur de proxy WCF . Il installe essentiellement un nouvel outil personnalisé dans Visual Studio 2008, puis utilise cet outil pour générer le nouveau proxy de service (Ajouter une référence de service) . Il a quelques fonctionnalités intéressantes pour traiter les canaux défectueux, les délais d’expiration et l’élimination en toute sécurité. Il y a une excellente vidéo ici appelée ExceptionHandlingProxyWrapper expliquant exactement comment cela fonctionne.
Vous pouvez utiliser à nouveau l'instruction Using
en toute sécurité et si le canal est défaillant pour toute requête (TimeoutException ou CommunicationException), le wrapper ré-initialisera le canal défaillant et relancera la requête. Si cela échoue, il appelle la commande Abort()
, supprime le proxy et rediffuse l'exception. Si le service lève un code FaultException
, il cessera de s'exécuter et le proxy sera abandonné en toute sécurité en lançant l'exception correcte comme prévu.
Sur la base des réponses de Marc Gravell, MichaelGG et Matt Davis, nos développeurs ont proposé ce qui suit:
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();
}
}
}
}
Exemple d'utilisation:
string result = string.Empty;
UsingServiceClient.Do(
new MyServiceClient(),
client =>
result = client.GetServiceResult(parameters));
C'est aussi proche que possible de la syntaxe "using", vous n'avez pas à renvoyer de valeur factice lorsque vous appelez une méthode void, et vous pouvez effectuer plusieurs appels au service (et renvoyer plusieurs valeurs) sans avoir à utiliser des n-uplets.
En outre, vous pouvez utiliser ceci avec ClientBase<T>
descendants au lieu de ChannelFactory si vous le souhaitez.
La méthode d'extension est exposée si un développeur souhaite disposer manuellement d'un proxy/canal.
@ Marc Gravell
Ne serait-il pas acceptable d'utiliser ceci:
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;
}
}
Ou, la même chose (Func<T, TResult>)
en cas de Service<IOrderService>.Use
Cela faciliterait le retour des variables.
Qu'est-ce que c'est?
Ceci est la version CW de la réponse acceptée mais avec (ce que je considère comme complet) le traitement des exceptions inclus.
La réponse acceptée fait référence à ce site Web n’est plus disponible . Pour vous éviter des ennuis, j'inclus ici les parties les plus pertinentes. De plus, je l'ai légèrement modifié pour inclure traitement des tentatives d'exception afin de gérer ces délais d'attente fastidieux sur le réseau.
Utilisation simple du client WCF
Une fois que vous avez généré votre proxy côté client, vous n’avez plus qu’à le mettre en œuvre.
Service<IOrderService>.Use(orderService=>
{
orderService.PlaceOrder(request);
});
ServiceDelegate.cs
Ajoutez ce fichier à votre solution. Aucune modification n'est requise dans ce fichier, sauf si vous souhaitez modifier le nombre de tentatives ou les exceptions que vous souhaitez gérer.
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: J'ai fait de ce post un wiki de communauté. Je ne collectionnerai pas les "points" de cette réponse, mais préférez-le à nouveau voter si vous êtes d'accord avec l'implémentation ou à le modifier pour le rendre meilleur.
Vous trouverez ci-dessous une version améliorée de la source de la question et étendue pour mettre en cache plusieurs fabriques de canaux et tenter de rechercher le noeud final dans le fichier de configuration par nom de contrat.
Il utilise .NET 4 (spécifiquement: contravariance, 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.");
}
}
Un emballage comme celui-ci fonctionnerait:
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
}
}
}
Cela devrait vous permettre d'écrire du code comme:
ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
var request = ...
response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.
Le wrapper pourrait bien entendu capturer plus d'exceptions si cela est requis, mais le principe reste le même.
Si vous n'avez pas besoin de IoC ou utilisez un client généré automatiquement (référence du service), vous pouvez simplement utiliser un wrapper pour gérer la fermeture et laisser le GC la base client quand elle est dans un état sûr ne lève aucune exception. Le GC appellera Dispose dans serviceclient, et ceci appellera Close
. Comme il est déjà fermé, il ne peut causer aucun dommage. J'utilise ceci sans problèmes dans le code de production.
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;
}
}
}
Ensuite, lorsque vous accédez au serveur, vous créez le client et utilisez using
dans la reconnaissance automatique:
var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {
Ws.Open();
Ws.Test();
}
J'ai utilisé le proxy dynamique Castle pour résoudre le problème de Dispose () et j'ai également implémenté l'actualisation automatique du canal lorsqu'il est inutilisable. Pour utiliser cela, vous devez créer une nouvelle interface qui hérite de votre contrat de service et d'IDisposable. Le proxy dynamique implémente cette interface et encapsule un canal WCF:
Func<object> createChannel = () =>
ChannelFactory<IHelloWorldService>
.CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();
J'aime cela, car vous pouvez injecter des services WCF sans que les consommateurs aient à se soucier des détails de WCF. Et il n'y a pas plus cruellement ajouté comme les autres solutions.
Regardez le code, il est en fait assez simple: proxy dynamique WCF
Utilisez une méthode d'extension:
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;
}
}
En utilisant les techniques décrites dans cette réponse, on peut utiliser un service WCF dans un bloc using avec la syntaxe suivante:
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Vous pouvez bien sûr adapter cela encore davantage pour obtenir un modèle de programmation plus concis et adapté à votre situation - mais le fait est que nous pouvons créer une implémentation de IMyService
représentant le canal qui implémente correctement le modèle à usage unique.
Toutes les réponses données jusqu'ici traitent du problème de contournement du "bogue" dans la mise en oeuvre du canal WCF de IDisposable
. La réponse qui semble offrir le modèle de programmation le plus concis (vous permettant d'utiliser le bloc using
pour disposer sur des ressources non gérées) est celui-ci - où le proxy est modifié pour implémenter IDisposable
avec une implémentation sans bug. Le problème avec cette approche est la maintenabilité - nous devons ré-implémenter cette fonctionnalité pour chaque proxy que nous utilisons. Sur une variante de cette réponse, nous verrons comment utiliser la composition plutôt que l’héritage pour rendre cette technique générique.
Il semble y avoir différentes implémentations pour l'implémentation IDisposable
, mais pour les besoins de l'argumentation, nous utiliserons une adaptation de celle utilisée par = réponse actuellement acceptée .
[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();
}
}
Armés des classes ci-dessus, nous pouvons maintenant écrire
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);
}
}
Cela nous permet de consommer notre service en utilisant le bloc using
:
ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Tout ce que nous avons fait jusqu’à présent est de reformuler solution de Tomas . Ce qui empêche ce code d’être générique, c’est le fait que la classe ProxyWrapper
doit être réimplémentée pour chaque contrat de service souhaité. Nous allons maintenant regarder une classe qui nous permet de créer ce type dynamiquement en utilisant 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();
}
}
Avec notre nouvelle classe d'assistance, nous pouvons maintenant écrire
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Notez que vous pouvez également utiliser la même technique (avec de légères modifications) pour les clients générés automatiquement qui héritent pour ClientBase<>
(au lieu d'utiliser ChannelFactory<>
), ou si vous souhaitez utiliser une implémentation différente de IDisposable
pour fermer votre chaîne.
J'aime cette façon de fermer la connexion:
var client = new ProxyClient();
try
{
...
client.Close();
}
finally
{
if(client.State != CommunicationState.Closed)
client.Abort();
}
Pour les intéressés, voici une traduction VB.NET de la réponse acceptée (ci-dessous). Je l'ai affiné un peu par souci de concision, en combinant certains des conseils donnés par d'autres dans ce fil.
J'admets que cela est hors sujet pour les balises d'origine (C #), mais comme je n'ai pas été en mesure de trouver une version VB.NET de cette solution de qualité, je suppose que d'autres rechercheront également. La traduction Lambda peut être un peu délicate, alors je voudrais éviter à quelqu'un les ennuis.
Notez que cette implémentation particulière offre la possibilité de configurer le ServiceEndpoint
au moment de l'exécution.
Code:
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
tilisation:
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();
}
}
}
}
Cela permet donc d’écrire joliment des instructions de retour:
return Service<IOrderService>.Use(orderService =>
{
return orderService.PlaceOrder(request);
});
Notre architecture système utilise souvent le cadre nityIoC pour créer des instances de ClientBase. Il est donc impossible de faire en sorte que les autres développeurs utilisent même des blocs using{}
. Afin de le rendre aussi infaillible que possible, j'ai créé cette classe personnalisée qui étend ClientBase et gère la fermeture du canal lors de la suppression ou de la finalisation au cas où quelqu'un ne disposerait pas explicitement de l'instance créée par Unity.
Il y a aussi des choses à faire dans le constructeur pour configurer le canal pour les informations d'identification personnalisées, donc c'est ici aussi.
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;
}
}
}
Ensuite, un client peut simplement:
internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
public string TestMethod(int value)
{
return base.Channel.TestMethod(value);
}
}
Et l'appelant peut faire n'importe lequel de ces tâches:
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);
}
}
}
J'ai écrit ne classe de base simple qui gère cela. Il est disponible sous forme de paquet NuGet et il est assez facile à utiliser.
//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);
}
}
J'aimerais ajouter l'implémentation de Service from réponse de Marc Gravell pour utiliser ServiceClient à la place de ChannelFactory.
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();
}
}
}
}
Ma méthode a consisté à créer une classe héritée qui implémente explicitement IDisposable. Ceci est utile pour les personnes qui utilisent l'interface graphique pour ajouter la référence de service (Ajouter une référence de service). Je viens de déposer cette classe dans le projet faisant la référence de service et de l’utiliser à la place du client par défaut:
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..
}
}
}
Remarque: Ceci est juste une simple implémentation de dispose, vous pouvez mettre en œuvre une logique de disposition plus complexe si vous le souhaitez.
Vous pouvez ensuite remplacer tous vos appels passés avec le client du service régulier par les clients sécurisés, comme ceci:
using (MyServiceClientSafe client = new MyServiceClientSafe())
{
var result = client.MyServiceMethod();
}
J'aime cette solution car elle ne nécessite pas l'accès aux définitions d'interface et je peux utiliser l'instruction using
comme je le souhaiterais tout en permettant à mon code de se ressembler plus ou moins.
Vous aurez toujours besoin de gérer les exceptions qui peuvent être levées comme indiqué dans d'autres commentaires de ce fil.
J'ai référé quelques réponses sur ce post et l'ai personnalisé selon mes besoins.
Je voulais pouvoir faire quelque chose avec le client WCF avant de l'utiliser, donc la méthode 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;
}
}
Voici la classe d'assistance:
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();
}
}
}
}
Et je peux l'utiliser comme:
string data = Service<ServiceClient>.Use(x => x.GetData(7));
L'assistant suivant permet d'appeler void
et des méthodes non vides. Usage:
var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());
La classe elle-même est:
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;
});
}
}
Remplacez Dispose () du client sans qu'il soit nécessaire de générer une classe proxy basée sur ClientBase, également sans avoir besoin de gérer la création et la mise en cache de canaux ! (Notez que WcfClient n'est pas une classe ABSTRACT et est basé sur 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();
}
}
J'ai mon propre wrapper pour un canal qui implémente Dispose comme suit:
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;
}
}
Cela semble bien fonctionner et permet d'utiliser un bloc d'utilisation.