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

QueryStringValue w Web Client Software Factory


20.10.2008

Web Client Software Factory udostępnia bardzo ciekawy i przydatny mechanizm komunikacji ze stanem przechowywanym w sesji. W poniższym przykładzie podczas tworzenia obiektu do pola zostanie wstrzyknięta odpowiednia wartość pobrana z sesji:

  1:  public class MyClass
  2:  {
  3:  	[SessionStateKey("MyNumber")]
  4:  	public StateValue<int> MyNumber;

Do wartości tej dostać się można następująco:

  1:  int number = MyNumber.Value;

Wszystko za sprawą Object Buildera. Jakie korzyści płyną z zastosowania takiego rozwiązania? Oprócz ustandaryzowanego i prostego sposobu wykorzystania sesji najważniejsza jest możliwość przeprowadzenia testów jednostkowych na obiektach polegających na wartościach pobieranych z sesji.

Ale ja nie do końca o tym chciałem… Kilka tygodni temu Kuba Binkowski w swoim wystąpieniu na wg.net pokazał podobne rozwiązanie w Unity, tyle że pobierające wartość z URL. No i właśnie coś takiego dodamy za chwilę do WCSF.


Cały proces rozpoczyna się od odpalenia Reflectora i analizy szczegółów implementacyjnych StateValue, ponieważ funkcjonalność będzie praktycznie ta sama. A potem – sam miód, czyli implementacja. Zatem po kolei, do dzieła!

1. Pierwszy krok to utworzenie interfejsu analogicznego do IStateValue. Interfejs ten zdefiniuje nam kontrakt komunikacyjny pomiędzy naszym systemem a URLem i będzie prawie taki sam jak wspomniane IStateValue. Jedyna różnica to typ zwracany przez właściwość Value. W naszym przypadku będzie to string, ponieważ to właśnie możemy z URLa wyciągnąć. Dodatkowo należy zwrócić uwagę na właściwość Request – nie wołamy bezpośrednio HttpContext.Current.Request. Zamiast tego uzupełnimy tą wartość później, korzystając z innych mechanizmów dostępnych w WCSF.

  1:  public interface IQueryStringValue
  2:  {
  3:  	string KeyName { get; set; }
  4:  	string Value { get; }
  5:  	HttpRequest Request { get; set; }
  6:  }

2. Następnie wypada interfejs ów zaimplementować. Tworzona klasa będzie wykorzystywać typ ogólny, dzięki czemu uzyskamy możliwość konwersji z ciągu znaków do liczby czy daty bez ingerencji końcowego programisty.

  1:  public class QueryStringValue<T> : IQueryStringValue
  2:  {
  3:  	public string KeyName { get; set; }
  4:  
  5:  	public HttpRequest Request { get; set; }
  6:  
  7:  	string IQueryStringValue.Value
  8:  	{
  9:  		get { return Request.QueryString[KeyName]; }
 10:  	}
 11:  
 12:  	public T Value
 13:  	{
 14:  		get
 15:  		{
 16:  			var v = ((IQueryStringValue)this).Value;
 17:  
 18:  			if (string.IsNullOrEmpty(v))
 19:  				return default(T);
 20:  
 21:  			return (T)Convert.ChangeType(v, T);
 22:  		}
 23:  	}
 24:  }

3. Mamy już strukturę potrzebną do przechowywania danych pobranych z QueryStringa. Zauważmy jednak, że brakuje jeszcze odpowiednika atrybutu SessionStateKey będącego znakiem dla ObjectBuildera że w tym miejscu należy się zatrzymać i “coś zrobić”. Implementacja takiego oznaczenia jest banalnie prosta, ponieważ tak naprawdę jedyne czego potrzebujemy to klucz pod którym należy szukać żądanej wartości:

  1:  [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
  2:  public sealed class QueryStringKeyAttribute : Attribute
  3:  {
  4:  	public string QueryStringKey { get; private set; }
  5:  
  6:  	public QueryStringKeyAttribute(string queryStringKey)
  7:  	{
  8:  		QueryStringKey = queryStringKey;
  9:  	}
 10:  }

4. Zatrzymajmy się na chwilę i spójrzmy co już napisaliśmy. Cała instrastruktura konieczna do wykorzystania mechanizmu jest gotowa. W akcji będzie to wyglądać tak:

  1:  	[QueryStringkey("MyNumber")]
  2:  	private QueryStringValue<int> SomeNumber;

…przy czym obsługiwany URL to http://www.xxx.com/yyy.aspx?MyNumber=666.
Ale to oczywiście nie koniec. Nigdzie jeszcze fizycznie nie dobieramy się do adresu, nigdzie nie wciskamy się w proces tworzenia obiektu przez ObjectBuilder! Kroczmy zatem dalej ścieżką prawych i sprawiedliwych. Jesteśmy blisko!

5. Teraz czeka nas najtrudniejsze zadanie – musimy napisać własną strategię ObjectBuildera, która powypełnia wszystkie pola oznaczone tym ślicznym atrybutem. Bez kilkukrotnego zerknięcia do Reflectora na implementację SessionStateBindingStrategy się nie obejdzie, ale w końcu po coś to narzędzie mamy, prawda? Tak więc wykonujemy wszystkie konieczne czynności, które ot tak wymienię jedna po drugiej:

  • za pomocą WCSFowego pojemnika IoC uzyskujemy instancję usługi, która dostarczy nam aktualny kontekst HTTP (a więc i obiekt HttpRequest)
  • przejeżdżamy się po wszystkich polach tworzonego obiektu pomijając te mające inny typ niż implementację naszego interfejsu IQueryStringValue
  • z deklaracji w/w pól pobieramy instancję atrybutu QueryStringKey zawierającą klucz wskazujący na żądaną wartość w URLu (poniższa implementacja wyrzuci wyjątek, gdy pole takiego typu nie zostanie oznaczone takim atrybutem)
  • wstawiamy w owo pole nowa instancję pożądanego typu, wypełniając jego właściwości przechowujące Key oraz Request

W tym momencie mamy już pole, którego właściwość Value zwróci nam to czego oczekujemy!

  1:  public class QueryStringBindingStrategy : BuilderStrategy
  2:  {
  3:  	public override object BuildUp(IBuilderContext context, System.Type typeToBuild, object existing, string idToBuild)
  4:  	{
  5:  		IHttpContextLocatorService service = context.Locator.Get<IHttpContextLocatorService>(new DependencyResolutionLocatorKey(typeof(IHttpContextLocatorService), null));
  6:  
  7:  		if (service != null)
  8:  		{
  9:  			var httpContext = service.GetCurrentContext();
 10:  			foreach (var field in typeToBuild.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
 11:  			{
 12:  				if (typeof(IQueryStringValue).IsAssignableFrom(field.FieldType) == false)
 13:  					continue;
 14:  
 15:  				IQueryStringValue queryStringValue = (IQueryStringValue)Activator.CreateInstance(field.FieldType);
 16:  				QueryStringKeyAttribute attribute = (QueryStringKeyAttribute)field.GetCustomAttributes(typeof(QueryStringKeyAttribute), false)[0];
 17:  
 18:  				queryStringValue.KeyName = attribute.QueryStringKey;
 19:  				queryStringValue.Request = httpContext.Request;
 20:  				field.SetValue(existing, queryStringValue);
 21:  			}
 22:  		}
 23:  
 24:  		return base.BuildUp(context, typeToBuild, existing, idToBuild);
 25:  	}
 26:  }

Jeszcze jedna mała uwaga: powyższa implementacja pozwala na potraktowanie w ten sposób WSZYSTKICH, także prywatnych, pól. Dostępny w WCSF mechanizm można zastosować jedynie do pól publicznych. Powód? Linijka numer 10. Tutaj jawnie żądamy dostępu do pól publicznych i niepublicznych, podczas gdy implementacja strategii StateValue wykorzystuje bezparametryczną wersję metody GetFields() zwracającą jedynie publiczne pola.

6. Został ostatni kroczek. Musimy powiedzieć ObjectBuilderowi że mamy o taki cudny mechanizm, który chcielibyśmy w proces tworzenia obiektów wprząc. Reflector w kilka chwil pokazuje nam w którą stronę gęby nasze należy zwrócić i co uczynić, aby było to możliwe. Rozwiązaniem jest własna klasa dziedzicząca z WebClientApplication nadpisująca jedną metodę:

  1:  public class CustomWebApplication : WebClientApplication
  2:  {
  3:  	protected override void AddBuilderStrategies(Microsoft.Practices.ObjectBuilder.IBuilder<WCSFBuilderStage> builder)
  4:  	{
  5:  		base.AddBuilderStrategies(builder);
  6:  
  7:  		builder.Strategies.AddNew<QueryStringBindingStrategy>(WCSFBuilderStage.Initialization);
  8:  	}
  9:  }

A co dalej z tą klasą zrobić – było ostatnio.


That’s all folks!
Żeby nie było tak słodko dodam, że całą tą pracę wykonałem właściwie na marne. Po napisaniu owego rozwiązania wpisałem z ciekawości w Google “WCSF QueryStringValue” i… co się okazało? Istnieje sobie projekcik WCSF Contrib i tam dokładnie takie samo rozwiązanie siedzi już od jakiegoś czasu. No ale, co się człowiek nauczy to jego.

Jest też jeszcze jedna sprawa. W WCSF możemy wykorzystywać sesję (jak to brzmi…) również w inny sposób. Istnieje sobie atrybut StateDependency, dzięki któremu możemy podobne sztuczki robić z parametrami metod! To jednak dużo wyższa szkoła jazdy – bez zagłębienia się po uszy w kod źródłowy WCSF i jego modyfikacji się nie obejdzie. Zatem odpowiednik, czyli QueryStringDependency (którego już w WCSF Contrib nie uświadczymy, co jest absolutnie zrozumiałe jeśli się poprzednie zdanie jeszcze raz przeczyta), stworzymy sobie może innym razem.

Comments are closed.

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również