c# test - Was ist die beste Problemumgehung für das `using` Blockproblem des WCF-Clients?




download wcftestclient (22)

Ich möchte meine WCF-Dienstclients in einem using Block instanziieren, da es sich um die Standardmethode zur Verwendung von Ressourcen handelt, die IDisposable implementieren:

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

Wie jedoch in diesem MSDN-Artikel erwähnt , kann das Umbrechen eines WCF-Clients in einem using Block alle Fehler maskieren, die dazu führen, dass der Client in einem fehlerhaften Zustand verbleibt (z. B. bei einem Timeout- oder Kommunikationsproblem). Um es kurz zu machen, wenn Dispose () aufgerufen wird, wird die Close () -Methode des Clients ausgelöst, aber es wird ein Fehler ausgegeben, weil er sich in einem fehlerhaften Zustand befindet. Die ursprüngliche Ausnahme wird dann durch die zweite Ausnahme maskiert. Nicht gut.

Die vorgeschlagene Problemumgehung in dem MSDN-Artikel besteht darin, vollständig zu vermeiden, einen using und stattdessen Ihre Clients zu instanziieren und sie in etwa so zu verwenden:

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

Verglichen mit dem using finde ich das hässlich. Und viel Code zum Schreiben jedes Mal, wenn Sie einen Client benötigen.

Glücklicherweise habe ich einige andere Problemumgehungen gefunden, wie diese auf IServiceOriented. Du beginnst mit:

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

Was dann erlaubt:

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

Das ist nicht schlecht, aber ich denke nicht, dass es so ausdrucksstark und leicht verständlich ist wie der using .

Die Problemumgehung, die ich gerade versuche zu verwenden, las ich zuerst auf blog.davidbarret.net . Grundsätzlich überschreiben Sie die Dispose() -Methode des Clients, wo immer Sie sie verwenden. Etwas wie:

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

Dies scheint in der Lage zu sein, den using wieder zuzulassen using ohne dass die Gefahr besteht, dass eine Ausnahmebedingung für einen fehlerhaften Zustand maskiert wird.

Also, gibt es noch andere Probleme, auf die ich achten muss, um diese Problemumgehungen zu nutzen? Hat jemand sich etwas Besseres ausgedacht?


Answers

Basierend auf den Antworten von Marc Gravell, MichaelGG und Matt Davis haben unsere Entwickler folgendes herausgefunden:

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

Anwendungsbeispiel:

string result = string.Empty;

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

Es ist so nah wie möglich an der "Using" -Syntax, Sie müssen keinen Dummy-Wert zurückgeben, wenn Sie eine void-Methode aufrufen, und Sie können mehrere Aufrufe an den Dienst senden (und mehrere Werte zurückgeben), ohne Tupel verwenden zu müssen.

Sie können dies auch mit ClientBase<T> -Dekennanten anstelle von ChannelFactory verwenden, falls gewünscht.

Die Erweiterungsmethode wird angezeigt, wenn ein Entwickler stattdessen manuell einen Proxy / Kanal entfernen möchte.


Verwenden Sie eine Erweiterungsmethode:

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

Ich habe endlich einige solide Schritte in Richtung einer sauberen Lösung für dieses Problem gefunden.

Dieses benutzerdefinierte Tool erweitert WCFProxyGenerator, um einen Ausnahmeverarbeitungs-Proxy bereitzustellen. Es generiert einen zusätzlichen Proxy mit dem Namen ExceptionHandlingProxy<T> der ExceptionHandlingProxy<T> erbt - letzteres implementiert die Funktionalität des Proxys. Das Ergebnis ist, dass Sie auswählen können, den Standardproxy zu verwenden, der ClientBase<T> oder ExceptionHandlingProxy<T> erbt, der die Verwaltung der Lebensdauer der ClientBase<T> und des Channels kapselt. ExceptionHandlingProxy berücksichtigt Ihre Auswahl im Dialogfeld Dienstreferenz hinzufügen in Bezug auf asynchrone Methoden und Auflistungstypen.

Codeplex hat ein Projekt namens Exception Handling WCF Proxy Generator . Es installiert im Grunde ein neues benutzerdefiniertes Tool für Visual Studio 2008 und verwendet dann dieses Tool zum Generieren des neuen Service-Proxys (Service-Referenz hinzufügen) . Es hat einige nette Funktionen, um mit fehlerhaften Kanälen, Timeouts und sicherer Entsorgung umzugehen. Es gibt ein exzellentes Video namens ExceptionHandlingProxyWrapper , das genau erklärt, wie das funktioniert.

Sie können die Using Anweisung erneut verwenden, und wenn der Kanal bei einer Anforderung (TimeoutException oder CommunicationException) fehlerhaft ist, initialisiert der Wrapper den fehlerhaften Kanal erneut und versucht die Abfrage erneut. Wenn das fehlschlägt, wird es den Befehl Abort() aufrufen und den Proxy entfernen und die Exception erneut auslösen. Wenn der Dienst einen FaultException Code FaultException , wird die Ausführung beendet, und der Proxy wird abgebrochen, wobei die korrekte Ausnahme wie erwartet FaultException wird.


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

So it allows to write return statements nicely:

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

Im Folgenden finden Sie eine erweiterte Version der Quelle aus der Frage, die erweitert wurde, um mehrere Channel-Factorys zwischenzuspeichern und den Endpunkt in der Konfigurationsdatei nach Vertragsnamen zu suchen.

Es verwendet .NET 4 (speziell: 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.");
    }
}

For those interested, here's a VB.NET translation of the accepted answer (below). I've refined it a bit for brevity, combining some of the tips by others in this thread.

I admit it's off-topic for the originating tags (C#), but as I wasn't able to find a VB.NET version of this fine solution I assume that others will be looking as well. The Lambda translation can be a bit tricky, so I'd like to save someone the trouble.

Note that this particular implementation provides the ability to configure the ServiceEndpoint at runtime.

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

Verwendung:

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

Dies ist die empfohlene Methode von Microsoft zum Behandeln von WCF-Clientaufrufen:

Weitere Informationen finden Sie unter Erwartete Ausnahmen

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

Weitere Informationen So viele Leute scheinen diese Frage zu WCF zu stellen, dass Microsoft sogar ein dediziertes Beispiel erstellt hat, um zu demonstrieren, wie mit Ausnahmen umzugehen ist:

c: \ WF_WCF_Samples \ WCF \ Basis \ Client \ ExpectedExceptions \ CS \ Client

Laden Sie das Beispiel herunter: C# oder VB

Wenn man bedenkt, dass es so viele Probleme gibt, die die using-Aussage betreffen (erhitzt?) Interne Diskussionen und threads zu diesem Thema, werde ich meine Zeit nicht damit verschwenden, ein Codecowboy zu werden und einen saubereren Weg zu finden. Ich sauge es nur auf und implementiere WCF-Clients diesen ausführlichen (aber vertrauenswürdigen) Weg für meine Server-Anwendungen.

Optionale zusätzliche Fehler zu fangen

Viele Ausnahmen stammen von CommunicationException und ich denke nicht, dass die meisten dieser Ausnahmen erneut versucht werden sollten. Ich habe jede Ausnahme auf MSDN TimeOutException und eine kurze Liste von wiederholbaren Ausnahmen gefunden (zusätzlich zu TimeOutException oben). Lassen Sie mich wissen, ob ich eine Ausnahme verpasst habe, die erneut versucht werden sollte.

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

Zugegeben, das ist ein banaler Code zum schreiben. Ich bevorzuge derzeit diese Antwort und sehe keine "Hacks" in diesem Code, die Probleme auf der Straße verursachen können.


Ich habe eine Funktion höherer Ordnung geschrieben , damit es richtig funktioniert. Wir haben das in mehreren Projekten verwendet und es scheint großartig zu funktionieren. So hätte es von Anfang an gemacht werden sollen, ohne das "using" -Paradigma oder so.

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

Sie können Anrufe wie folgt tätigen:

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

Das ist ziemlich genau wie in Ihrem Beispiel. In einigen Projekten schreiben wir stark typisierte Hilfsmethoden, also schreiben wir Dinge wie "Wcf.UseFooService (f => f ...)".

Ich finde es ziemlich elegant, alles in allem. Gibt es ein bestimmtes Problem, auf das Sie gestoßen sind?

Dadurch können andere nützliche Funktionen angeschlossen werden. Beispielsweise authentifiziert sich die Site an einer Site gegenüber dem Service im Namen des angemeldeten Benutzers. (Die Site hat selbst keine Anmeldeinformationen.) Indem wir unseren eigenen "UseService" -Methoden-Helper schreiben, können wir die Channel-Factory so konfigurieren, wie wir wollen, etc. Wir sind auch nicht verpflichtet, die generierten Proxies zu verwenden - jede Schnittstelle reicht aus .


I have my own wrapper for a channel which implements Dispose as follows:

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

This seems to work well and allows a using block to be used.


Zusammenfassung

Unter Verwendung der in dieser Antwort beschriebenen Techniken kann ein WCF-Dienst in einem using-Block mit der folgenden Syntax verwendet werden:

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

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

Sie können dies natürlich noch weiter anpassen, um ein prägnanteres Programmierungsmodell für Ihre Situation zu erreichen - aber der Punkt ist, dass wir eine Implementierung von IMyService erstellen können, die den Kanal darstellt, der das Wegwerfmuster korrekt implementiert.

Einzelheiten

Alle Antworten, die bisher gegeben wurden, adressieren das Problem, den "Bug" in der WCF-Kanal-Implementierung von IDisposable . Die Antwort, die das prägnanteste Programmiermodell zu bieten scheint (so dass Sie den using Block verwenden können, um nicht verwaltete Ressourcen zu beseitigen), ist dieser - wo der Proxy geändert wird, um IDisposable mit einer fehlerfreien Implementierung zu implementieren. Das Problem bei diesem Ansatz ist die Wartbarkeit - wir müssen diese Funktionalität für jeden Proxy, den wir verwenden, neu implementieren. Auf einer Variation dieser Antwort werden wir sehen, wie wir die Zusammensetzung anstelle der Vererbung verwenden können, um diese Technik generisch zu machen.

Erster Versuch

Es scheint verschiedene Implementierungen für die IDisposable Implementierung zu geben, aber aus Gründen der Argumentation werden wir eine Adaption der von der aktuell akzeptierten Antwort verwendeten verwenden .

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

Mit den oben genannten Klassen können wir jetzt schreiben

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

Dies erlaubt uns, unseren Service mit dem using Block zu nutzen:

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

Making this generic

All we have done so far is to reformulate Tomas' solution . What prevents this code from being generic is the fact that ProxyWrapper class has to be re-implemented for every service contract we want. We will now look at a class that allows us to create this type dynamically using 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();
    }
}

With our new helper class we can now write

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

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

Note that you could also use the same technique (with slight modifications) for auto-generated clients inheriting for ClientBase<> (instead of using ChannelFactory<> ), or if you want to use a different implementation of IDisposable to close your channel.


Wenn Sie IoC nicht benötigen oder einen automatisch generierten Client (Service-Referenz) verwenden, können Sie den Closing einfach mithilfe eines Wrappers verwalten und den Client-Client in einen sicheren Status versetzen, der keine Ausnahme auslöst. Der GC ruft Dispose in serviceclient auf und dies ruft Close . Da es bereits geschlossen ist, kann es keinen Schaden verursachen. Ich benutze das ohne Probleme im Produktionscode.

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

Wenn Sie dann auf den Server zugreifen, erstellen Sie den Client und verwenden ihn in autodisconect:

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

    Ws.Open();

    Ws.Test();
}

Our system architecture often uses the Unity IoC framework to create instances of ClientBase so there's no sure way to enforce that the other developers even use using{} blocks. In order to make it as fool-proof as possible, I made this custom class that extends ClientBase, and handles closing down the channel on dispose, or on finalize in case someone doesn't explicitly dispose of the Unity created instance.

There is also stuff that needed to be done in the constructor to set up the channel for custom credentials and stuff, so that's in here too...

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

Then a client can simply:

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

And the caller can do any of these:

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

Override the client's Dispose() without the need to generate a proxy class based on ClientBase, also without the need to manage channel creation and caching ! (Note that WcfClient is not an ABSTRACT class and is based on 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();
    }
}

You could also use a DynamicProxy to extend the Dispose() method. This way you could do something like:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}

Eigentlich, obwohl ich blogged (siehe Lukes Antwort ), denke ich, dass this besser ist als mein IDisposable Wrapper. Typischer Code:

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

(nach Kommentaren bearbeiten)

Da Use void zurückgibt, erfolgt die Verarbeitung von Rückgabewerten am einfachsten über eine erfasste Variable:

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

I have written a simple base class that handles this. It's available as a NuGet package and it's quite easy to use.

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

Ich habe Castle Dynamic Proxy verwendet, um das Dispose () - Problem zu lösen, und auch den Kanal automatisch aktualisiert, wenn er sich in einem unbrauchbaren Zustand befindet. Um dies zu verwenden, müssen Sie eine neue Schnittstelle erstellen, die Ihren Servicevertrag und IDisposable erbt. Der dynamische Proxy implementiert diese Schnittstelle und umschließt einen WCF-Kanal:

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

Ich mag das, weil Sie WCF-Dienste injizieren können, ohne dass die Verbraucher sich um WCF-Details kümmern müssen. Und es gibt keinen zusätzlichen Cruft wie die anderen Lösungen.

Werfen Sie einen Blick auf den Code, es ist eigentlich ziemlich einfach: WCF Dynamic Proxy


I referred few answers on this post and customized it as per my needs.

I wanted the ability to do something with WCF client before using it so the DoSomethingWithClient() method.

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

Here is the helper class:

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

And I can use it as:

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

Ein solcher Wrapper würde funktionieren:

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

Das sollte Ihnen ermöglichen, Code wie zu schreiben:

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

Der Wrapper könnte natürlich weitere Ausnahmen erfassen, wenn dies erforderlich ist, aber das Prinzip bleibt gleich.


Was ist das?

Dies ist die CW-Version der akzeptierten Antwort, aber mit (was ich für vollständig halte) Exception-Handling enthalten.

Die akzeptierte Antwort verweist auf this . Um dir Ärger zu ersparen, füge ich hier die wichtigsten Teile ein. Darüber hinaus habe ich es geringfügig modifiziert, um die Behandlung von Ausnahmebeschwerden zu berücksichtigen , um diese lästigen Netzwerk-Timeouts zu behandeln.

Einfache WCF-Client-Nutzung

Sobald Sie Ihren clientseitigen Proxy erstellt haben, ist dies alles, was Sie für die Implementierung benötigen.

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

ServiceDelegate.cs

Fügen Sie diese Datei zu Ihrer Lösung hinzu. Für diese Datei sind keine Änderungen erforderlich, es sei denn, Sie möchten die Anzahl der Wiederholungen ändern oder welche Ausnahmen Sie behandeln möchten.

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: Ich habe diesen Beitrag zu einem Community-Wiki gemacht. Ich werde von dieser Antwort keine "Punkte" sammeln, aber Sie bevorzugen es, wenn Sie mit der Implementierung einverstanden sind, oder wenn Sie es bearbeiten, um es besser zu machen.


My method of doing this has been to create an inherited class that explicitly implements IDisposable. This is useful for folks who use the gui to add the service reference ( Add Service Reference ). I just drop this class in the project making the service reference and use it instead of the default client:

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

Note: This is just a simple implementation of dispose, you can implement more complex dispose logic if you like.

You can then replace all your calls made with the regular service client with the safe clients, like this:

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

I like this solution as it does not require me to have access to the Interface definitions and I can use the using statement as I would expect while allowing my code to look more or less the same.

You will still need to handle the exceptions which can be thrown as pointed out in other comments in this thread.


weil

count++

gibt zurück, zählt nicht count + 1

habe count ++ ohne Zuweisung oder:

count = ++count;

das letzte nur zu erklären, aber du solltest es nicht benutzen ...

von: ++ Operator (C # -Referenz)

Die erste Form ist eine Präfixinkrementoperation. Das Ergebnis der Operation ist der Wert des Operanden, nachdem dieser inkrementiert wurde.

Die zweite Form ist eine Postfix-Inkrementierungsoperation. Das Ergebnis der Operation ist der Wert des Operanden, bevor er inkrementiert wurde.

Numerische Typen und Aufzählungstypen haben vordefinierte Inkrementoperatoren. Benutzerdefinierte Typen können den Operator ++ überladen. Operationen auf ganzzahligen Typen sind im Allgemeinen bei der Aufzählung erlaubt.





c# vb.net wcf using wcf-client