środa, 24 kwietnia 2013

Dynamiczne aplikacje Web API

ASP.NET Web Forms są powszechnie uważane za nie najnowszą już technologię. Dziś wszyscy sobie na ogół chwalą ASP.NET MVC lub coraz częściej Web API. Czy słusznie? Na ogół tak, dostajemy wszak uporządkowaną warstwową architekturę MVC czy same tylko serwisy Web API. Odkryłem jednak ostatnio, że im nowsza z wymienionych technologii tym mniej w niej dynamicznej kompilacji, co z kolei utrudnia modyfikację aplikacji bez jej ręcznej całościowej rekompilacji. I tak:

  • ASP.NET Web Forms - największy zakres dynamicznej kompilacji (można dużo zaszyć w stronach)
  • ASP.NET MVC - dynamiczną kompilacją objęte widoki (ale modeli  i kontrolerów już to nie dotyczy)
  • ASP.NET Web API - brak w standardzie dynamicznej kompilacji (same modele i kontrolery, domyślnie raz rozpoznana lista kontrolerów jest cachowana)

Czy jednak w nowszych technologiach nie da się uzyskać podobnych efektów? Czy musimy koniecznie żałować dynamicznej kompilacji ASP.NET? M.in na te pytania starałem się odpowiedzieć poszukując możliwości pisania aplikacji Web API w sposób dynamiczny / częściowo dynamiczny. Po praktycznych eksperymentach ukształtowały się następujące pozycje:

  1. Dynamiczne Web API (assembly resolver)
  2. Dynamiczne Web API (“Roslyn” - konsola)
  3. Dynamiczne Web API (“Roslyn” - dynamiczne odświeżanie)
  4. Dynamiczne Web API (“Roslyn” - assembly resolver)
  5. Dynamiczne Web API (scriptcs)

Omówię je teraz po kolei.

Assembly resolver

image

Zacznijmy od standardowych rozwiązań. I tak mamy w Web API interfejs IAssembliesResolver. Jego implementacja służy do określenia listy assemblies, które będą uwzględniane przy szukaniu kontrolerów. Domyślnie uwzględniane są rzeczy z naszego projektu po jego kompilacji, ale możemy to zmienić i załadować w locie dll-kę. Pokazuje to oficjalny Custom Assembly Resolver Sample. W moim wydaniu nieznacznie uległo to modyfikacji, tak by ładować dll-ki ze wskazanego folderu:

image

Tak zdefiniowany assembly resolver rejestrujemy:

Untitled

“Roslyn” (konsola, dynamiczne odświeżanie)

Jeśli rozważamy dynamiczność, to może w większym zakresie, tak by mieć dynamiczną kompilację?  W końcu ASP.NET takie coś oferuje. Z pomocą przychodzi nam przyszłościowy projekt “Roslyn”. Jest to realizacja idei “compilation as service”. Do dyspozycji dostajemy wysokopoziomowe API, które pozwala bardzo szybko w locie kompilować dynamicznie kod. Umożliwia to pisanie i dynamiczne wykonywanie C# w konsoli, co jest typowe dla skryptowych języków.

Podobno są plany, by zastępować kompilatory w językach .NET “Roslynem”. Podobno jest też używany wewnętrznie i został użyty w Web Matrix 2. To technologia wciąż rozwijana. Ostatnie wydanie to Microsoft "Roslyn" September 2012 CTP. Doinstalowuje się ładnie do Visual Studio 2012, także po wszystkich najnowszych aktualizacjach (co sprawdziłem osobiście). To wydanie nie wspiera już VS 2010. Nie możemy rozpowszechniać binariów z tej rozwojowej wersji, ale instalacja pakietu nuget o nazwie roslyn nie podlega już takim ograniczeniom. Dystrybucja przez nuget zawiera wszystkie niezbędne biblioteki do używania, jedynie nie dostaniemy większego wsparcia przez Visual Studio (m.in szablony, kolorowanie składni i intellisense na plikach skryptowych np. C# Script, konsola interaktywna). Warto pamiętać o “Roslyn”, zwłaszcza że na zaczynającej się jutro dotnetconf będzie to temat jednej z sesji.

Jeśli chodzi o Web API, to polecam ostatnio odkryty blog Filipa W. http://www.strathweb.com/. Jeśli chodzi o tematykę Web API + “Roslyn”, to na polecam szczególnie lekturę postów:

image

Na ich podstawie odtworzyłem sobie:

  • skryptową aplikację  hostującą Web API (uruchamianą przez program rcsi lub w Visual Studio przez View > Other Windows > C# Interactive)
  • aplikację Web API hostowaną na IIS, która buduje dynamiczne assembly ze skryptów z kontrolerami i modelami (co odbywa się w specjalnej implementacji selektora kontrolerów za każdym odświeżeniem strony lub wywołaniem z przeglądarki)

Zatrzymajmy się na chwilę przy pełnym rozwiązaniu z dynamicznym odświeżaniem kontrolerów przy każdym strzale. Umożliwia to klasa NoCacheSelector, która u mnie ma zparametryzowane ścieżki do folderów ze skryptami:

image

Powyższy selektor kontrolerów rejestrujemy:

Untitled2

Skrypty kompilowane w locie:

image   image

Dostajemy największą elastyczność. Bez rekompilacji całego projektu przy każdej interakacji dostajemy kontrolery odpowiadające aktualnie znajdującym się plikom C# w folderach Controllers (z modelami z plików C# w Models). Jak pisze jednak autor, to eksperyment z pewnym ryzykiem. Przy każdym strzale zostaje dodawane nowe assembly do domeny i pozostaje jedynie liczyć na wymuszone sprzątanie przez garbage collector.

“Roslyn” - assembly resolver

Tą wersję wymyśliłem jako miks koncepcji dotychczas zaprezentowanych. Rezygnuję z ciągłego odświeżania kontrolerów (ale także ich rekompilacji) przy każdym strzale, a w resolverze assemblies zamiast szukania i ładowania statycznych dll-ek z dysku buduję dynamiczne assembly ze skryptów z kontrolerami i modelami.

image

scriptcs

Projekt scriptcs to ostatnio bardzo medialne pojęcie. Glenn Block po swoich doświadczeniach z node.js w Windows Azure postanowił zrobić coś podobnego w… C#. Powstał projekt open source, który pozwala pisać skryptowo aplikacje w C#, taki znacznie lepszy PowerShell. Piszemy tyle C# ile potrzebujemy, odpowiednie biblioteki są dociągane przez nuget, a całość jest dynamicznie kompilowana przez “Roslyn”. Wystarczy notatnik, nie jest potrzebne Visual Studio. Do projektu jako jeden z trzech głównych osób dokoptował Filip W. który wniósł swoje wcześniejsze doświadczenia z Web API i “Roslyn”.

Kilka linków:

Warto być świadomym obecności:

Na początek wziąłem przykład webapihost.

image

Po zainstalowaniu/zbudowaniu aplikacji konsolowej scriptcs.exe, dodaniu jej do ścieżki path w systemie, możemy uruchomić przykład. Zostaną pobrane wymagane pakiety i zapisane do folderu packages. Do folderu bin trafiają używane biblioteki. Dostajemy w pełni skryptową aplikację Web API w C# hostowaną w konsoli. Czyż nie jest to wyrażenie idei “node.cs” ?

Jeszcze prościej możemy to osiagnąć za pomocą scriptcs-webapi. Całość aplikacji może wyrażać jeden plik:

image

W tym przypadku widać jeszcze większe podobieństwo do node.js. Po co definiować za każdym razem routing? Czyż nie wystarczająco i bardziej minimalistycznie jest definiować same kontrolery, które wyrażają także w jakiś sposób adres? Bawiąc się node.js miałem właśnie skojarzenie, że wystawiane metody REST-owe to jakby akcje kontrolerów… A cała prostota wynika z odpowiedniego zaprojektowania pakietu express.

Na tym kończę przegląd dynamicznych rozwiązań dla Web API. Dodam tylko, że w tym temacie miałem jeszcze zajawki odnośnie:

  • wykorzystania MEF w Web API
  • integracji ASP.NET z Web API

W przypadku MEF dla .NET 4.5 mamy obecnie jego drugą lekką wersję w postaci  MEF for web and Windows Store apps. Owszem można znaleźć w sieci wiele przykładów łączenia Web API z różnymi wersjami MEF, jednak często dotyczą one wpięcia w infrastrukturę dla IoC. Często wydają się też dość skomplikowane (np. http://kennytordeur.blogspot.com/2012/08/mef-in-aspnet-mvc-4-and-webapi.html). Dużo informacji dostarcza też wątek http://mef.codeplex.com/discussions/389317.

Jak patrzyłem po sieci, to niektórzy próbowali definiować kompozycję na folderze, ale nie wiem czy komuś udało się osiągnąć wykrywanie w czasie run-time nowych kontrolerów, bo są one domyślnie cachowane przez Web API. Może za mało wszedłem w temat, ale wydaje mi się, że również MEF musiałby robić rekompozycję przy każdym strzale, co z pewnością byłoby nie najszybsze (lub trzeba by pomyśleć o wykrywaniu zmian). Z kolei samo jednokrotnie zaczytanie dll-ek w sposób dynamiczny mogę zrealizować w całości w oparciu o infrastrukturę Web API, jak pokazałem w pierwszym przykładzie.

Co do integracji z ASP.NET, to zainspirował mnie oficjalny przykład Web Form Sample oraz post. Jednak po przeczytaniu posta architekta ASP.NET, widać jak na dłoni, że standardowa dynamiczna kompilacja ASP.NET/Razora jest znacznie gorsza od kompilacji wykonywanej za pomocą “Roslyn” (jest znacznie wolniesza, bo korzysta z emitowania i potrzebuje innego procesu). Nawet powstał projekt https://github.com/davidebbo/RoslynRazorViewEngine zaprzęgający “Roslyn” do kompilacji Razora, co znacząco zwiększa wydajność.

Brak komentarzy: