Laden...

WCF: Basisklasse für einen Proxy

Erstellt von gfoidl vor 12 Jahren Letzter Beitrag vor 12 Jahren 8.507 Views
gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 12 Jahren
WCF: Basisklasse für einen Proxy

Voraussetzung:
.net 4.0 oder Kompatible

Beschreibung:

Die Basisklasse ProxyBase<TChannel> stellt eine generische Basisimplementierung bereit, die vewendet werden kann um WCF-Clientobjekte zu erstellen, welche WCF-Dienste aufrufen können.
Es gibt zwar in .net die ClientBase(TChannel)-Klasse diese hat aber beim cachen von ChannelFactories Einschränkungen. Für detailliertere Information sei auf Performance Improvement for WCF Client Proxy Creation in .NET 3.5 and Best Practices verwiesen. Die hier vorgestellte Basisklasse kann natürlich angepasst werden um auf eigenen spezielle Bedürfnisse besser zu passen. ZB kann das Caching angepasst werden indem eine andere IFactoryCache-Instanz gesetzt wird.

Hier werden ChannelFactories auf AppDomain-Ebene gecached. Dies verkürzt die Erstellungszeit eines Proxies immens. Zusätzlich wird das Proxy lazy erzeugt, also erst beim tatsächlichen Zugriff.
Im Standardcache der dabei ist werden zwei ChannelFactories als gleich erachtet wenn die Endpunkt-Adresse und das Binding übereinstimmen sind.

Ich finde außerdem dass sich mit meiner Basisklasse transparenter arbeiten lässt - im Vergleich zu ClientBase<TChannel> 😃
Die ClientBase<TChannel> wird übrigens bei der Erstellung eines Clients mittels svcutil bzw. Add Service Reference verwendet.

Nachfolgend eine kurze Demonstration der Verwendung für folgenden Service-Contract:


[ServiceContract]
public interface IAnswerService
{
	[OperationContract]
	[FaultContract(typeof(Exception))]	// speziellere Exeption wäre sinnvoller in echten Anwendungen
	string GetUltimativeAnswer(string query);
}

Somit kann die Client-Klasse für diesen Vertrag wie folgt definiert werden:


public interface IGreetingsServiceClient : IAnswerService, ICommunicationObject { }
//-------------------------------------------------------------------------
public class AnswerServiceClient : ProxyBase<IGreetingsServiceClient>, IAnswerService
{
	public AnswerServiceClient(string endpointAddress)
		: base(endpointAddress) { }
	public AnswerServiceClient(string endpointAddress, Bindings bindings)
		: base(endpointAddress, bindings) { }
	//---------------------------------------------------------------------
	#region IAnswerService Members
	public string GetUltimativeAnswer(string query)
	{
		try
		{
			return this.Proxy.GetUltimativeAnswer(query);
		}
		catch (FaultException<Exception> ex)
		{
			throw ex.FromFaultException();
		}
	}
	#endregion
}

Das ist der ganze Aufwand der nötig ist um ein WCF-Client-Objekt zu erstellen. Für das komfortablere Exception-Handling sind in der Assembly auch Erweiterungsmethoden vorhanden - im obigen Beispiel die FromFaultException-Erweiterung. Das "Gegenstück" ist "ToFaultException". Für nähere Infos zum Thema Exception-Handling in WCF siehe Specifying and Handling Faults in Contracts and Services.

Dieser Client kann dann in einem Programm ganz ".net like" verwendet werden:


// Hier wäre der richtige Zeitpunkt zum Setzen eines eigen IFactoyCache:
// ProxyBase.ChannelFactoryCache = new MyVeryOwnFactoryCache();
using (AnswerServiceClient client = new AnswerServiceClient(
	"net.pipe://localhost/greetingsService",
	Bindings.NamedPipes))
{
	string query = "What's the answer?";
	string answer = client.GetUltimativeAnswer(query);
	Console.WriteLine(answer);
}


Die Enum Bindings ist im Projekt enthalten.

Anhang: Die DLL sowie das VS 2010 Projekt für den gesamten Code (inkl. Demo). XML-Kommentare sind keine vorhanden, aber mehr als Konstruktorüberladungen und zwei Eigenschaften (Proxy und ChannelFactoryCache) gibt es nicht - somit sollte die Verwendung auch ohne Kommentare klar und möglich sein 😉

Wenn jemand Änderungen, Ergänzungen, Anpassungen, etc. hat wäre es nett wenn diese hier als Antwort veröffentlicht werden könnten.

Eine leichte Änderung empfehle ich jedoch jedem: Anstatt der Enum Bindings kann das verwendete Bindung auch per Konvention ermittelt/erstellt werden. D.h. aus der EndpointAdress kann die Binding abgeleitet werden und somit erübrigt sich das Enum.

mfG Gü

Schlagwörter: WCF, Proxy, Client, WCF-Client

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 12 Jahren

Hallo zusammen,

die Proxies sind nur für das Erstellen der Channel und der ICommunication-Objekte zuständig. D.h. es lassen sich auch asynchrone Vorgänge damit bewerkstelligen, da dies eben unabhängig vom Proxy an sich ist.

Mit .net 4.0 und speziell mit den Tasks lassen sich asynchrone Methoden für WCF sehr einfach bewerkstelligen, da Task die Schnittstelle IAsyncResult implementiert. Auf eine allgemeine Übersicht über asynchrone Vorgänge in WCF verzichte ich und verweise auf Synchronous and Asynchronous Operations. Hier weise ich nur darauf hin, dass es verschiedene Möglichkeiten der Asynchronizität in WCF gibt:*Server asynchron und Client asynchron *Server asynchron und Client synchron *Server synchron und Client asynchron

Der Server sollte vorzugsweise dann asynchron laufen, wenn dieser als "Zwischenschicht" dient, z.B. wenn die Servicemethode einen weiteren Service, etc. aufruft. Dies erhöht den Durchsatzt und die Skalierbarkeit.
Der Client sollte dann asynchron laufen wenn z.B. [FAQ] Warum blockiert mein GUI? ein Problem sein könnte.

Die Implementierung eines asynchronen Vorgangs zeige ich an einem Beispiel. Angenommen wir haben folgenden ServiceContract:


using System;
using System.ServiceModel;

namespace Contracts
{
	[ServiceContract]
	public interface IMyService
	{
		[OperationContract]
		int Add(int a, int b);
		//---------------------------------------------------------------------
		[OperationContract(AsyncPattern = true)]
		IAsyncResult BeginAddAsync(int a, int b, AsyncCallback callback, object state);
		int EndAddAsync(IAsyncResult ar);
	}
}

Der Service kann dann wie folgt implementiert werden.


using System;
using System.Threading.Tasks;
using Contracts;
using gfoidl.Service.Utils;

namespace Service
{
	public class MyService : IMyService
	{
		#region IMyService Members
		public int Add(int a, int b)
		{
			return a + b;
		}
		//---------------------------------------------------------------------
		public IAsyncResult BeginAddAsync(int a, int b, AsyncCallback callback, object state)
		{
			var task = Task.Factory.StartNew(() => this.Add(a, b));

			task.ContinueWith(
				t => Console.WriteLine(t.Exception),  // produktiv: besser Loggen
				TaskContinuationOptions.OnlyOnFaulted);

			return task.ToApm(callback, state);
		}
		//---------------------------------------------------------------------
		public int EndAddAsync(IAsyncResult ar)
		{
			return ar.End<int>();
		}
		#endregion
	}
}

Und der Client schaut so aus:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Contracts;
using System.ServiceModel;
using gfoidl.Service.Utils;
using System.Threading.Tasks;

namespace Client
{
	public interface IMyServiceClient : IMyService
	{
		Task<int> AddAsync(int a, int b);
	}
	//-------------------------------------------------------------------------
	public interface IMyServiceClientChannel : IMyServiceClient, ICommunicationObject { }
	//-------------------------------------------------------------------------
	public class MyServiceClient : ProxyBase<IMyServiceClientChannel>, IMyServiceClient
	{
		public MyServiceClient(string endpointAddress)
			: base(endpointAddress) { }
		public MyServiceClient(string endpointAddress, Bindings bindings)
			: base(endpointAddress, bindings) { }
		//---------------------------------------------------------------------
		#region IMyService Members
		public int Add(int a, int b)
		{
			return this.Proxy.Add(a, b);
		}
		//---------------------------------------------------------------------
		IAsyncResult IMyService.BeginAddAsync(int a, int b, AsyncCallback callback, object state)
		{
			return this.Proxy.BeginAddAsync(a, b, callback, state);
		}
		//---------------------------------------------------------------------
		int IMyService.EndAddAsync(IAsyncResult ar)
		{
			return this.Proxy.EndAddAsync(ar);
		}
		//---------------------------------------------------------------------
		public Task<int> AddAsync(int a, int b)
		{
			return TaskHelper.FromAsync(
				(this as IMyService).BeginAddAsync,
				(this as IMyService).EndAddAsync,
				a, b,
				this.Proxy);
		}
		#endregion
	}
}

Damit das Ganze elegant funktioniert sind ein paar Erweiterungsmethoden notwendig, die dem obigen Projekt hinzugefügt werden können. Ich poste hier die wiederverwendbaren Klassen, so wie ich sie (od. fast so) produktiv verwende, und diese beinhalten auch Überladungen für die gängigen Anwendungsfälle.

Zuerst werden Erweiterungsmethoden für Tasks benötigt, um das asynchrone Pattern (Begin/End, auch als APM...asynchronous programming model bezeichnet) mit den Task kompatibel zu machen:


using System;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;

namespace System.Threading.Tasks
{
	public static partial class TaskExtensions
	{
		public static Task ToApm(
			this Task task,
			AsyncCallback callback,
			object state)
		{
			Contract.Requires(task != null);
			Contract.Requires(callback != null);
			Contract.Ensures(Contract.Result<Task>() != null);
			//-----------------------------------------------------------------
			if (task.AsyncState == state)
			{
				if (callback != null)
					task.ContinueWith(
						t => callback(t),
						TaskContinuationOptions.ExecuteSynchronously);

				return task;
			}

			var tcs = new TaskCompletionSource<object>(state);

			task.ContinueWith(t =>
			{
				if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions);
				else if (t.IsCanceled) tcs.TrySetCanceled();
				else tcs.TrySetResult(null);

				if (callback != null) callback(tcs.Task);
			}, TaskContinuationOptions.ExecuteSynchronously);

			return tcs.Task;
		}
		//---------------------------------------------------------------------
		public static Task<TResult> ToApm<TResult>(
			this Task<TResult> task,
			AsyncCallback callback,
			object state)
		{
			Contract.Requires(task != null);
			Contract.Requires(callback != null);
			Contract.Ensures(Contract.Result<Task<TResult>>() != null);
			//-----------------------------------------------------------------
			if (task.AsyncState == state)
			{
				if (callback != null)
					task.ContinueWith(
						t => callback(t),
						TaskContinuationOptions.ExecuteSynchronously);

				return task;
			}

			var tcs = new TaskCompletionSource<TResult>(state);

			task.ContinueWith(t =>
			{
				if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions);
				else if (t.IsCanceled) tcs.TrySetCanceled();
				else tcs.TrySetResult(t.Result);

				if (callback != null) callback(tcs.Task);
			}, TaskContinuationOptions.ExecuteSynchronously);

			return tcs.Task;
		}
	}
}

Genauso gibts eine Erweiterungsmethode um vom Task wieder zum APM zu gelangen:


using System;
using System.Diagnostics.Contracts;
using System.ServiceModel;
using System.Threading.Tasks;

namespace gfoidl.Service.Utils
{
	public static class IAsyncResulstExtensions
	{
		// Da es streng kanonisch ist => Erweiterungsmethode :-)
		public static T End<T>(this IAsyncResult ar)
		{
			if (!(ar is Task<T>)) throw new InvalidOperationException("Only Task<T> can be handeld.");
			Contract.EndContractBlock();
			//-----------------------------------------------------------------
			var task = ar as Task<T>;

			try
			{
				return task.Result;
			}
			catch (AggregateException aex)
			{
				throw EnsureFaultException(aex);
			}
		}
		//---------------------------------------------------------------------
		private static FaultException EnsureFaultException(AggregateException aex)
		{
			// ZB bei nested Tasks ist die InnerException auch eine AggregateException!
			Exception ex = aex.GetRootException();

			FaultException fex = ex as FaultException;

			// Wenn es keine FaultException ist dann erstellen:
			if (fex == null)
			{
				Type genericType = typeof(FaultException<>);
				Type[] typeArgs = { ex.GetType() };
				Type type = genericType.MakeGenericType(typeArgs);

				fex = Activator.CreateInstance(type, ex, ex.Message) as FaultException;
			}

			return fex;
		}
	}
}

Hierzu ist es auch notwendig die ExceptionExtensions, die im obigen Projekt dabei sind, zu erweitern. Der komplette Code für diese Klasse ist nun wie folgt:


using System;
using System.Diagnostics.Contracts;
using System.ServiceModel;
using System.Threading.Tasks;

namespace gfoidl.Service.Utils
{
	public static class ExceptionExtensions
	{
		public static FaultException<T> ToFaultException<T>(this T ex)
			where T : Exception
		{
			Contract.Requires(ex != null);
			Contract.Ensures(Contract.Result<FaultException<T>>() != null);
			//-----------------------------------------------------------------
			return new FaultException<T>(ex, ex.Message);
		}
		//---------------------------------------------------------------------
		public static FaultException<T> ToFaultException<T>(this Task task)
			where T : Exception
		{
			Contract.Requires(task != null);
			Contract.Ensures(Contract.Result<FaultException<T>>() != null);
			//-----------------------------------------------------------------
			return (task.Exception.InnerException as T).ToFaultException();
		}
		//---------------------------------------------------------------------
		public static T FromFaultException<T>(this FaultException<T> ex)
			where T : Exception
		{
			Contract.Requires(ex != null);
			Contract.Ensures(Contract.Result<T>() != null);
			//-----------------------------------------------------------------
			return ex.Detail;
		}
		//---------------------------------------------------------------------
		public static Exception GetRootException(this Exception ex)
		{
			Contract.Requires(ex != null);
			//-----------------------------------------------------------------
			return GetRootExceptionCore(ex);
		}
		//---------------------------------------------------------------------
		private static Exception GetRootExceptionCore(Exception ex)
		{
			if (ex is AggregateException) return GetRootExceptionCore(ex.InnerException);

			return ex;
		}
	}
}

Für den Client brauchen wir auch noch Hilfs-Methoden um aus dem APM einen Task zu erzeugen, damit die Client-Verwendung ganz transparent wird. Dies geschieht in folgender Hilfsklasse:


using System;
using System.Diagnostics.Contracts;
using System.ServiceModel;
using System.Threading.Tasks;

namespace gfoidl.Service.Utils
{
	public static class TaskHelper
	{
		public static Task<TResult> FromAsync<TArg1, TResult>(
			Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod,
			Func<IAsyncResult, TResult> endMethod,
			TArg1 arg1,
			object state)
		{
			Contract.Requires(beginMethod != null);
			Contract.Requires(endMethod   != null);
			//-----------------------------------------------------------------
			TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>(state);
			AsyncCallback callback = ar => FromAsyncCore(tcs, ar, endMethod);

			beginMethod(arg1, callback, state);
			return tcs.Task;
		}
		//---------------------------------------------------------------------
		public static Task<TResult> FromAsync<TArg1, TArg2, TResult>(
			Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod,
			Func<IAsyncResult, TResult> endMethod,
			TArg1 arg1,
			TArg2 arg2,
			object state)
		{
			Contract.Requires(beginMethod != null);
			Contract.Requires(endMethod   != null);
			//-----------------------------------------------------------------
			TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>(state);
			AsyncCallback callback = ar => FromAsyncCore(tcs, ar, endMethod);

			beginMethod(arg1, arg2, callback, state);
			return tcs.Task;
		}
		//---------------------------------------------------------------------
		public static Task<TResult> FromAsync<TArg1, TArg2, TArg3, TResult>(
			Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod,
			Func<IAsyncResult, TResult> endMethod,
			TArg1 arg1,
			TArg2 arg2,
			TArg3 arg3,
			object state)
		{
			Contract.Requires(beginMethod != null);
			Contract.Requires(endMethod != null);
			//-----------------------------------------------------------------
			TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>(state);
			AsyncCallback callback = ar => FromAsyncCore(tcs, ar, endMethod);

			beginMethod(arg1, arg2, arg3, callback, state);
			return tcs.Task;
		}
		//---------------------------------------------------------------------
		private static void FromAsyncCore<TResult>(
			TaskCompletionSource<TResult> tcs,
			IAsyncResult ar,
			Func<IAsyncResult, TResult> endMethod)
		{
			try
			{
				tcs.SetResult(endMethod(ar));
			}
			catch (FaultException fex)
			{
				Exception ex = EnsureException(fex);
				tcs.SetException(ex);
			}
			catch (Exception ex)
			{
				tcs.SetException(ex);
			}
		}
		//---------------------------------------------------------------------
		private static Exception EnsureException(FaultException fex)
		{
			Type exType = fex.GetType();
			object ex = exType.GetProperty("Detail").GetValue(fex, null);

			return ex as Exception;
		}
	}
}

Im obigen Beispiel ist die Methodenpaarung Server - Client so, dass nur synchron-synchron od. nur asynchron-asynchron gearbeitet werden kann. Dieses Verhalten kann geändert werden, siehe eingangs erwähnte Kombinationen, indem die im OperationContract mit der Action- und ReplyAction-Eigenschaft die jeweilige Adresse für die Methode angegeben wird. Dies kann sowohl beim Client als auch beim Server erfolgen. Aber dies ist mMn eher ein Sonderfall.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
439 Beiträge seit 2008
vor 12 Jahren

Und hier noch die Klassen, die statt dem Endpoint im Code zu definieren, den EndpointConfigurationName übernehmen:


public sealed class FactoryCache : IFactoryCache
    {
        private readonly Dictionary<FactoryCacheItem, ChannelFactory> _cache;

        public FactoryCache()
        {
            var comparer = new FactoryCacheItemEqualityComparer();
            _cache = new Dictionary<FactoryCacheItem, ChannelFactory>(comparer);
        }

        #region IFactoryCache Members
        public ChannelFactory<TChannel> GetFactory<TChannel>(string endpointConfigurationName)
        {
            FactoryCacheItem cacheItem = new FactoryCacheItem(endpointConfigurationName);
            ChannelFactory channelFactory = null;

            if (_cache.ContainsKey(cacheItem))
                channelFactory = _cache[cacheItem];
            else
            {
                channelFactory = CreateChannel<TChannel>(endpointConfigurationName);
                _cache.Add(cacheItem, channelFactory);
            }
            return channelFactory as ChannelFactory<TChannel>;
        }
        #endregion

        private static ChannelFactory<TChannel> CreateChannel<TChannel>(string endpointConfigurationName)
        {
            ChannelFactory<TChannel> channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName);
            channelFactory.Open();
            return channelFactory;
        }
        
        private struct FactoryCacheItem
        {
            public string EndpointConfigurationName { get; private set; }
            public FactoryCacheItem(string endpointConfigurationName)
                : this()
            {
                Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(endpointConfigurationName));
                this.EndpointConfigurationName = endpointConfigurationName;
            }
        }

        private class FactoryCacheItemEqualityComparer : EqualityComparer<FactoryCacheItem>
        {
            public override bool Equals(FactoryCacheItem x, FactoryCacheItem y)
            {
                return x.EndpointConfigurationName == y.EndpointConfigurationName;
            }
            public override int GetHashCode(FactoryCacheItem obj)
            {
                return obj.EndpointConfigurationName.GetHashCode();
            }
        }
    }

[ContractClass(typeof(IFactoryCacheContracts))]
    public interface IFactoryCache
    {
        ChannelFactory<TChannel> GetFactory<TChannel>(string endpointConfigurationName);
    }
    //---------------------------------------------------------------------
    [ContractClassFor(typeof(IFactoryCache))]
    internal abstract class IFactoryCacheContracts : IFactoryCache
    {
        #region IFactoryCache Members
        public ChannelFactory<TChannel> GetFactory<TChannel>(string endpointConfigurationName)
        {
            Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(endpointConfigurationName));
            Contract.Ensures(Contract.Result<ChannelFactory<TChannel>>() != null);
            return default(ChannelFactory<TChannel>);
        }
        #endregion
    }

 public abstract class ProxyBase
    {
        public static IFactoryCache ChannelFactoryCache { get; set; }
        static ProxyBase()
        {
            ProxyBase.ChannelFactoryCache = new FactoryCache();
        }
    }

public abstract class ProxyBase<TChannel> : ProxyBase, IDisposable
        where TChannel : ICommunicationObject
    {
        private readonly Lazy<TChannel> _proxy;

        protected TChannel Proxy { get { return _proxy.Value; } }

        #region Ctor
        protected ProxyBase(string endpointConfigurationName)
        {
            Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(endpointConfigurationName));
            _proxy = new Lazy<TChannel>(() =>
            {
                ChannelFactory<TChannel> channelFactory = ProxyBase
                    .ChannelFactoryCache
                    .GetFactory<TChannel>(endpointConfigurationName);

                TChannel proxy = channelFactory.CreateChannel();
                proxy.Open();
                return proxy;
            });
        }
        #endregion


        #region IDisposable Members
        private bool _isDisposed = false;
        
        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                    if (_proxy.IsValueCreated)
                        try
                        {
                            _proxy.Value.Close();
                        }
                        catch
                        {
                            _proxy.Value.Abort();
                        }

                _isDisposed = true;
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

A programmer is just a tool, which converts coffeine into code! 🙂

C
439 Beiträge seit 2008
vor 12 Jahren

Hallo gfoidl,

was mir beim Arbeiten mit dem Proxy noch aufgefallen ist:
Die Methoden für Open() und Close() fehlen beim Proxy komplett => Wenn das Programm lange läuft und der Timeout für den Proxy abgelaufen ist, dann bekomm ich ne TimeOutException. Kann ich die Methoden noch irgendwie "einbauen" oder wie machst du das?

A programmer is just a tool, which converts coffeine into code! 🙂

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Campy,

ich hoffe dass ich jetzt diese Implementierung richtig im Kopf habe, da ich eine leicht modifiziert verwende, aber das Grundgerüst ist dennoch das Gleiche.

Die Methoden für Open() und Close() fehlen beim Proxy komplett

Als explizite Methoden sind diese (bewusst) nicht vorhanden. Open und Close wird intern aufgerufen. Für die Verwendung halte ich es so, dass der Proxy für jede Verwendung neu erstellt wird und dann gleich wieder verworfen wird. Daher eignet sich eine Verwendung im using-Block besonders gut.
Laufzeittechnisch hat das keine Nachteile, denn alles schwergewichtige wird gecached, etnweder von der WCF selbst od. von der Basisklasse (und deren Helfer).

Die Verwendung ist also


// irgendeine Methode die den Service synchron aufrufen soll:
using (MyProxy proxy = new MyProxy(...))
{
    int sum = proxy.Add(40, 2);
}

// irgendeine Methode die den Service asynchron aufrufen soll:
MyProxy proxy = new MyProxy(...);
Task<int> sum = proxy.AddAsync(40, 2);

sum.ContinueWith(t => proxy.Dispose());
sum.ContinueWith(t => _logger.LogException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
sum.ContinueWith(t => UseResult(t.Result), TaskContinuationOptions.OnlyOnRunToCompletion);

Ein ständiges offenhalten des Proxies ist nicht gedacht. Wenn du das jedoch willst, so müsstest du im Konstruktor des Proxies den Lazy-Init-Teil weglassen und stattdessen dies in einer zu hinzufügenden Open-Methode implementieren.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
439 Beiträge seit 2008
vor 12 Jahren

Alles klar, das ist auch wahrscheinlich das sauberste und wenn die wichtigsten Dinge eh gecached werden ist das auch kein Problem (außer bissl Umbauarbeit für mich).

Bei mir sieht das dann folgendermaßen aus:


  using (XXXServiceClient customerService = new XXXServiceClient("NetTcpBinding_IXXService"))
                        {
                            ...
                        }

Hast du noch eine Idee wie ich mir das mit dem Bindingnamen vereinfachen könnte? Sonst muss ich das an jeder Stelle der Verwendung übergeben.

Vielen vielen Dank!

A programmer is just a tool, which converts coffeine into code! 🙂

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Campy,

noch als eher allg. Anmerkung zum Snippet:
Für die Bindungsart hab ich das Enum rausgeworfen, denn anhand der Adresse kann diese ja leicht ermittelt werden. http://... -> BasisHttpBinding, net.tcp://... -> NetTcpBinding, usw. Das ist alles nach Konvention.

Hast du noch eine Idee wie ich mir das mit dem Bindingnamen vereinfachen könnte?

Mach dir einfach eine Factory, welche den Proxy erstellt:


public class MyProxyFactory
{
    private readonly string _bindingName;

    public MyProxyFactory(string bindingName)
    {
        if (string.IsNullOrWhitespace(bindingName)) throw new ArgumentNullException("bindingName");
        _bindingName = bindingName;
    }

    public MyProxy Create()
    {
        // hier proxy erstellen
    }
}

public class Foo
{
    private readonly MyProxyFactory _proxyFactory; // z.B. per DI übergeben

    using (MyProxy proxy = _proxyFactory.Create())
    {

    }
}

Wobei dei Factory besser die Schnittstelle mit dem ServiceContract zurückgibt, denn somit ist alles gleich testbarer.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
439 Beiträge seit 2008
vor 12 Jahren

Hmm dann brauch ich aber für jeden Proxy auch eine angepasste ProxyFactory ? 😦

A programmer is just a tool, which converts coffeine into code! 🙂

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 12 Jahren

Hallo Campy,

du kannst in einer Factory auch mehrere Methoden haben, für jeden Proxy eine.
Wenn du der Konvention mit der *.config und dem BindingName folgst, so könntest du in der Factory ja auch die Werte aus der *.config lesen.

Du könntest, wenn du viele Proxies hast, auch eine Service-Facade einbauen die dieses kapselt und so einen vereinheitlichten Zugriff ermöglicht.

Du könntest... es gibt ja viele Möglichkeiten.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"