ASP.NET MVC i Unity

9

Nadejszła wiekopomna chwila – ASP.NET MVC już od jakiegoś czasu egzystuje jako oficjalny produkt, więc oto najwyższa pora na zapoznanie się z nim. Cierpliwie przeczekałem wszystkie ochy i achy, pokonałem chęć bycia "trendy";) i zabieram się za to dopiero teraz.

Na pierwszy ogień poszło wpasowanie w cały mechanizm kontenera Dependency Injection tak, aby kontrolery brały się właśnie z niego. Wklejenie gotowych kilku linijek kodu byłoby dość nudne, więc postanowiłem tym razem przedstawić swoje poszczególne kroki i myśli lęgnące się w mózgu podczas poznawania zupełnie nieznanego wcześniej kodu (co wcale, uprzedzam, nie oznacza, że będzie ciekawie!). Poniżej zatem wierne odzwierciedlenie mojej ścieżki od koncepcji do rozwiązania:

1) Najpierw należało zidentyfikować SKĄD te kontrolery się biorą. Bystre moje, niczym woda wiadomo gdzie, oczy, zauważyły, że każdy kontroler dziedziczy z klasy bazowej o niespodziewanej i zaskakującej nazwie… Controller. I to tyle czego oczekuję od Visual Studio, czas na przesiadkę do Reflectora. Po uruchomieniu tego cuda i wciśnięciu ctrl+o (czyli Open File) odszukałem dllkę zawierającą wszystkie tajemnice ASP.NET MVC, czyli:

(zawczasu dodałem ją do repozytorium projektu wiedząc, że mój wybór tym razem padnie na tą technologię)
2) Załadowawszy żądany zestaw (jakie megamądre stwierdzenia z mych rąk dziś płyną, a niech mnie!) zabrałem się za jego przeszukiwanie. Klawisz F3 otwierający panel wyszukiwania był w tym wypadku niezastąpiony. Niezwłocznie przystąpiłem do poszukiwań:

3) Jakże gładko poszło! Dwuklik na tym interesującym wierszu przeniósł mnie w panel nawigacji po bibliotekach i typach. Z ciekawości należało zanalizować pochodzenie klasy Controller, bardzo prawdopodobne było że w infrastrukturze ASP.NET MVC jest jakaś bardziej generalna klasa czy interfejs. A jakże:

Nie dość, że mamy ControllerBase, to jeszcze implementuje ona najwyższy w hierarchii interfejs IController. To musiało być to, byłem święcie przekonany że cokolwiek nie tworzyłoby instancji poszczególnych kontrolerów, zwracałoby je właśnie w postaci implementacji tego interfejsu. Kolejny dwuklik scentralizował mój świat na tym interfejsie, w którym mogłem się zagrzebać.
4) Zawartość interfejsu interesowała mnie co najwyżej średnio, co nie przeszkodziło jednak w chwilowym zboczeniu z drogi w celu dowiedzenia się, że jedyna odpowiedzialność kontrolera to "zrobienie czegoś" (w metodzie Execute()) mając do dyspozycji informacje o bieżącym żądaniu http oraz coścośtam związanego z url rewritingiem. Na szczęście Reflector posiada przycisk BACK, który pozwoli wygrzebać się z kodowego bagna i wrócić do odpowiedniego miejsca nawet jeśli bardzo damy się ponieść duchowi eksploratora:

Po powrocie do interfejsu kliknąłem prawym przyciskiem, a następnie wybrałem Analyze… zobaczymy gdzież to ów interfejs jest wykorzystywany.
5) W oknie analizatora rozwinąłem odpowiednie grupy i cóż się okazało? A-HA, mamy cię! To już nawet Gosiu by się domyślił, że IControllerFactory tworzy kontrolery:

Przeszedłem zatem w to miejsce aby kontynuować badania.
6) Po wciśnięciu ctrl+r, równoważnym z wybraniem Analyze z menu kontekstowego, ponownie znalazłem się w oknie analizatora. I jakież to ciekawe rzeczy ukazały się moim astygmatycznym ślepiom:

BINGO! Co prawda od razu pojawiła się myśl "hmm, dwa tak mocno powiązane ze sobą typy nazywające się – IControllerFactory oraz ControllerBuilder – tak, jakby robiły dokładnie to samo… ciekawe dlaczego?", ale tym razem poskromiłem ciekawość i zostawiłem inwestygację na kiedy indziej.
Czyli już wiem co trzeba wywołać, aby podmienić standardowy sposób tworzenia kontrolerów. Zostały pytania: GDZIE to zrobić, JAK się do owej metody dostać i CO jej przekazać?
7) Kolejne sekundy z Reflectorem przyniosły odpowiedzi na dwa pierwsze pytania:

W statycznym konstruktorze tworzona jest domyślna instancja Buildera, a właściwość Current właśnie ją zwraca. Nie mamy co prawda do czynienia z Singletonem, ale już znam odpowiedź na pytanie "JAK?". O tak: ControllerBuilder.Current.SetControllerFactory(…). A "GDZIE"? Najpewniej najstosowniejszym miejscem będzie kod wykonywany podczas uruchomienia aplikacji, więc domniemana lokalizacja to Application_Start() w Global.asax.cs.
8) Czas zmierzyć się z pytaniem trzecim: CO tam wrzucić? Aby na nie sensownie odpowiedzieć, należy przyjrzeć się istniejącym implementacjom IControllerFactory. A w tym przypadku – jedynej istniejącej implementacji.

Krótki rzut oka na zawartość klasy DefaultControllerFactory wystarczył do podjęcia decyzji: oto będzie właśnie matka mojej implementacji; za dużo mądrych rzeczy tam się musi dziać w tych wszystkich prywatnych składowych, aby wymyślać koło na nowo i próbować przekodować to samo w swoim, notabene wolnym, czasie. Zawęziłem zatem swoją analizę jedynie do metod zwracających IController – jako kandydatów do ewentualnego nadpisania. Idealna do tego celu okazała się metoda GetControllerInstance():

Jedyne co tak naprawdę robi oprócz sprawdzania argumentów i rzucania wyjątków to wywołanie Activator.CreateInstance(Type), czyli jest to perfekcyjnie zlokalizowany kawałek kodu do podmiany! Zauważyć warto, że oznaczona jest modyfikatorami "protected internal", których występowanie czasami BŁĘDNIE tłumaczone jest jako: "metoda widoczna jedynie dla klas zadeklarowanych w tym samym assembly i dziedziczących z jej właściciela”. Zapamiętać należy, że tak naprawdę stawiany tam jest operator OR, a nie AND! Poprawne wyjaśnienie: "metoda widoczna dla klas zadeklarowanych w tym samym assembly ORAZ dla klas dziedziczących z jej właściciela”. Tak więc możemy ją sobie spokojnie nadpisać.
9) OKej, posiadając te wszystkie informacje można zakodować co następuje:

  1:  public class UnityControllerFactory : DefaultControllerFactory
  2:  {
  3:  	private readonly IUnityContainer _container;
  4:  
  5:  	public UnityControllerFactory(IUnityContainer container)
  6:  	{
  7:  		_container = container;
  8:  	}
  9:  
 10:  	protected override IController GetControllerInstance(Type controllerType)
 11:  	{
 12:  		return (IController)_container.Resolve(controllerType);
 13:  	}
 14:  }

Tyle zabiegów sprowadziło się do właściwie jednej linijki, uzupełnionej o:

  1:  protected void Application_Start()
  2:  {
  3:  	ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(Unity.Container));
  4:  	RegisterRoutes(RouteTable.Routes);
  5:  }

, gdzie statyczna klasa Unity to mój własny helper.

I bardzo dobrze. Tym lepiej, że działa bez zarzutu!


To już koniec wycieczki, proszę wysiadać. Mam nadzieję, że przekonałem choć jedną “niewierzącą” osobę jak potrzebnym i wspaniałym narzędziem jest Reflector. Okazało się również, że o ile Google jest zdecydowanie najszybszym sposobem na znalezienie odpowiedzi, jednocześnie nie jest to najbardziej satysfakcjonująca i niosąca najwięcej wartości edukacyjnych droga.
Dopowiem jeszcze, że w rzeczywistości podróż ta trwała około kwadransa. Dopowiem też, że po tym wszystkim z ciekawości poprzeglądałem internet w poszukiwaniu lepszych rozwiązań i… zdziwiłem się jak bardzo można nakombinować, żeby skomplikować sobie życie. Znalazłem sporo alternatyw (bardzo zresztą podobnych), każda jednak jednak miała więcej linii kodu. Po co?

Najczęściej the simpler => the better.

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

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

9 Comments

  1. Co do Twojego ostatniej linijki:
    Make everything as simple
    as possible, but not simpler
    Albert Einstein
    mnie interesuje jedno, bo teraz masa ludzi mowi IoC, DI to jest to, poprostu slinka cieknie itp itd. tak jak swojego czasu mowili o AOP. Nie twierdze ze DI i AOP sa zle, ale to tak jak moda na EMO ;)
    dlaczego chciales wykorzystasc DI i co Ci to daje w tym konkretnym rozwiazaniu ktore robisz?
    :) ach trzeba pamietac ze nie ma glupich pytan ;)
    Gutek

  2. @Gutek:
    Ja sam po dluzszym zastanowieniu nad AOP stwierdzilem ze nie bede tego uzywal, chociaz kod wyglada wtedy na o wiele “prostszy” niz jest w rzeczywistosci – i wlasnie to “prostsze wygladanie” mnie troche odstrasza.
    Co do DI… w tym rozwiazaniu daje mi to samo co w kazdym:) – wyciagniecie kontroli nad tworzeniem obiektow w jedno miejsce, ktore jednoczesnie dba o odpowiednie uzupelnienie zaleznosci podczas ich konstrukcji. To z kolei pozwala mi na bezbolesne i jasne zadeklarowanie wszystkich tych zaleznosci danej klasy w jej kontstruktorze – podczas normalnego dzialania programu o dostarczenie ich dba konterer DI. A przy testach jednostkowych moge wstawic swoje mocki. O to przeciez chodzi…?

  3. Apropo korzystania z Reflectora. Nie szybciej i prościej było ściągnąć kod i odpalić go w Visual Studio??? :)

  4. Można też popatrzeć w jakiś już istniejący projekt oparty na MVC, który korzysta z DI/Unity. Ja uczyłem się tego na Kiggu, przy okazji stawiania dotnetomaniaka. Czasem trochę jest to zawiłe tam, ale da się po mału rozczaić i zrozumieć co i jak. Tak czy inaczej dobry tekst.
    Pozdrawiam,
    Paweł

  5. @dario-g
    nie wiem jak ty ale jezeli chodzi o przegladanie jak jest zrobione assembly ja dalej wole Reflectora, VS narazie (do 2010) nie ma az tak plynnego i prostego przechodzenia jak REflector :)
    Gutek

  6. @Paweł Łukasik:
    Zanim zaczne przegladac cudze rozwiazania, wole najpierw sam sie pobawic danym frameworkiem. W ten sposob moge probowac wymyslic cos fajnego nie sugerujac sie praktykami innych programistow, a pozniej weryfikowac swoje pomysly z istniejacymi implementacjami. Moim zdaniem taki przebieg ‘procesu poznawczego’ lepiej cwiczy umiejetnosci developerskie niz startowanie z pulapu czyichs doswiadczen i przyzwyczajen.
    @dario-g:
    Jak sprawdzilem maila i w skrzynce zobaczylem 3 komentarze do wpisu, a nastepnie przeczytalem zawartosc Twojego – od razu wiedzialem ze ktorys z pozostalych to Gutek o takiej wlasnie tresci:). Popieram go, jak dla mnie Reflector tez jest póki co niezastąpiony i VS do pięt mu nie sięga jeżeli chodzi o analizę bibliotek. Zapoznajac się z WCSF też z niego korzystalem mimo tego, ze mialem obok na dysku sciagniete zrodla. Po prostu wymiata i tyle:).