fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
5 minut

Własna implementacja WCF Proxy


03.12.2009

Podczas korzystania z WCF najprostszą drogą do wywołania metody udostępnianej przez jakąś usługę jest pozwolenie Visual Studio na wygenerowanie odpowiedniego proxy, stworzenie jego instancji… i już – mamy metody usługi do dyspozycji. Bez wysiłku, bez kodu… bez sensu?

O tym, dlaczego takie podejście NIE jest wyborem słusznym, rozpisywać się nie będę. Zainteresowanych argumentami “przeciw” odsyłam do fajnego artykułu autorstwa Miguela Castro na Code-Magazine: “WCF the Manual Way… the Right Way“. Propozycji alternatyw jest w internecie sporo i mają dużą wartość edukacyjną. Jednak gdy przyszło do prawdziwej zabawy z WCF, rozszerzyłem trochę te rozwiązania. Całość kodu wykorzystującego usługi WCF rozbiłem na dwie części: właściwe Proxy oraz klasę udostępniającą odpowiednie Proxy wedle naszego zapotrzebowania.

Pierwsza część, czyli klasa Proxy eliminująca kod generowany przez Visual Studio, przedstawia się następująco:

  1:  public class ServiceProxy<T> : ClientBase<T>, IDisposable where T : class
  2:  {
  3:  	// empty ctor for default config if only one available
  4:  	public ServiceProxy()
  5:  	{
  6:  	}
  7:  
  8:  	// ctor with config name; other ctors not available to enable ChannelFactory caching
  9:  	public ServiceProxy(string endpointConfigurationName)
 10:  		: base(endpointConfigurationName)
 11:  	{
 12:  	}
 13:  
 14:  	public T GetChannel()
 15:  	{
 16:  		return base.Channel;
 17:  	}
 18:  
 19:  	public void Dispose()
 20:  	{
 21:  		try
 22:  		{
 23:  			if (base.Channel != null)
 24:  			{
 25:  				if (base.State != CommunicationState.Faulted)
 26:  				{
 27:  					base.Close();
 28:  				}
 29:  				else
 30:  				{
 31:  					base.Abort();
 32:  				}
 33:  			}
 34:  		}
 35:  		catch (CommunicationException)
 36:  		{
 37:  			base.Abort();
 38:  		}
 39:  		catch (TimeoutException)
 40:  		{
 41:  			base.Abort();
 42:  		}
 43:  		catch (Exception)
 44:  		{
 45:  			base.Abort();
 46:  			throw;
 47:  		}
 48:  	}
 49:  }

Wielkiego odkrycia nie ma tu żadnego. Klasa ta ma właściwie dwa zadania: dać nam dostęp do kanału implementującego komunikację z żądanym serwisem oraz odpowiednią obsługę Dispose(). O problemach z Dispose() można poczytać na MSDN w artykule “Avoiding Problems with the Using Statement“.

Wprowadziłem dość istotną modyfikację w stosunku do fruwających po necie przykładów: ograniczyłem liczbę konstruktorów. Moja klasa Proxy udostępnia tylko dwa konstruktory – domyślny oraz przyjmujący nazwę wpisu z konfiguracji. Powód jest bardzo prosty, choć niekoniecznie każdy musi o takim fakcie wiedzieć: te konstruktory umożliwiają cache’owanie przez WCF raz utworzonych obiektów ChannelFactory. Dzięki temu dalsze instancjonowanie samych Proxy jest bardzo lekkim procesem. Więcej o tym na blogu Wenlong Dong: “Performance Improvement for WCF Client Proxy Creation in .NET 3.5 and Best Practices“.

Przedstawiona powyżej klasa może znajdować się w jakimś współdzielonym assembly, dostępna dla każdej aplikacji klienckiej.

Ciągłe pisanie takiego kodu nie do końca mi się jednak podobało:

  1:  using (var proxy = new ServiceProxy<IMyService>())
  2:  {
  3:  	proxy.GetChannel().MyMethod();
  4:  }

Dlatego też bezpośrednio w aplikacji klienckiej umieszczam małego helpera, który pozwala skrócić te instrukcje do takich wywołań:

  1:  ServiceProxyProvider<IMyService>.Invoke(x => x.MyAction());
  2:  string result = ServiceProxyProvider<IMyService>.Invoke(x => x.MyFunction());

Oprócz oczywistej korzyści, jaką jest mniejsza ilość kodu (choć można by dyskutować czy w tym konkretnym przypadku to faktycznie tak wielka korzyść), otrzymujemy jeszcze jeden bonus: możemy w jednym miejscu zarządzać każdym wywołaniem zdalnej usługi. Błyskawicznie przychodzącym na myśl wykorzystaniem tej zalety jest wrzucenie tu ustawiania parametrów uwierzytelniania (jakie fancy określenie na login i hasło:) ). W poniższej implementacji, pochodzącej z aplikacji WinForms, zrobiłem jednak coś innego:

  1:  public class ServiceProxyProvider<TService> where TService : class
  2:  {
  3:  	public static void Invoke(Action<TService> operation)
  4:  	{
  5:  		using (var proxy = new ServiceProxy<TService>())
  6:  		{
  7:  			WaitingCursor(() =>
  8:  				operation(proxy.GetChannel())
  9:  			);
 10:  		}
 11:  	}
 12:  
 13:  	public static TResult Invoke<TResult>(Func<TService, TResult> operation)
 14:  	{
 15:  		TResult result = default(TResult);
 16:  
 17:  		Invoke(x =>
 18:  		{
 19:  			result = operation(x);
 20:  		});
 21:  
 22:  		return result;
 23:  	}
 24:  
 25:  	private static void WaitingCursor(Action operation)
 26:  	{
 27:  		Form activeForm = Form.ActiveForm;
 28:  
 29:  		if (activeForm != null)
 30:  		{
 31:  			activeForm.Cursor = Cursors.WaitCursor;
 32:  		}
 33:  
 34:  		try
 35:  		{
 36:  			operation();
 37:  		}
 38:  		finally
 39:  		{
 40:  			if (activeForm != null)
 41:  				activeForm.Cursor = Cursors.Default;
 42:  		}
 43:  	}
 44:  }

Każde wywołanie skutkuje zmianą kursora na klepsydrę bądź Viściano-Siódemkowe Błękitne Koło Zagłady, dzięki czemu użytkownik wie, że “coś się dzieje i ma być cierpliwy“.

Macie jakieś ciekawe doświadczenia w tym zakresie? Uwagi, sugestie, dotyczące przedstawionego rozwiązania?

Wkrótce powinien pojawić się dłuższy post traktujący o WCF, gdzie na żywym zarodku aplikacji będzie można zobaczyć to w praktyce. W tymczasem – borem lasem!

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
jdubrownik
jdubrownik

Dobry kawałek kodu. Zmienił bym tylko mały drobiazg: if (base.Channel == null) { return; } i wyrzucił bym to poza blok try/catch.

Szkolenie z testów

Szkolenie z baz danych

Facebook

Książka

Zobacz również