Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

22 Luty 2014

Zrób to szablonowo

Filed under: Codzienne dylematy modelarza — Tags: , , , , — Beniamin Zaborski @ 14:23

Z zawodem programisty związane są często prace powtarzalne i nudne. W sumie to mogą one stanowić znaczący ułamek całości. Nie zawsze są to wyzwania:). Po prostu robimy coś rutynowo, szablonowo, po raz n-ty. Oczywiście bezmyślne klepanie może nas kiedyś zgubić bo nie trudno o błąd będąc w stanie transu, szczególnie przed poranną kawą.

Rozwiązaniem przynajmniej części tych problemów są generatory tekstu oparte na szablonach – text templating. Takie hasło programistom aplikacji web-owych zapewne się kojarzy z generowaniem widoków na podstawie szablonów, np. Razor.

Jednak widoki w aplikacji web to nie wszystko co możemy generować. Chciałbym spojrzeć na to zagadnienie dużo szerzej. Efekt naszej pracy to kod źródłowy, który jest przecież tekstem. Czemu nie wykorzystać jakiegoś generatora opartego na szablonach do generowania kodu źródłowego. Szczególne pole do manewru jest w tych obszarach aplikacji które są powtarzalne i nudne? Tu liczę na Waszą fantazję.

Ja np. chciałbym aby jak za dotknięciem magicznej różdżki wygenerował mi się obiekt Repository dla wskazanej encji. Wyobrażam sobie, że raz tworzę szablon i … mam więcej czasu na rozkoszowanie się aromatem świeżo palonej brazylijskiej arabiki 100%:).

Visual Studio dostarcza (a dokładniej to chyba VS SDK) generator oparty na szablonach T4. Ja jednak w tym wpisie pokażę alternatywne rozwiązanie, a w zasadzie dwa, moje ulubione StringTemplate oraz NVelocity.

Zanim przejdę do konkretnego przykładu spójrzmy na poniższy diagram jak to w ogóle działa.

Text templating diagram

gdzie:

Templates – szablony przygotowane zgodnie ze składnią danego generatora.

Variables – zmienne przekazane do silnika które zostaną użyte do zastąpienia ciągów w szablonie.

Text Output – wyjściowy plik generatora.

Text Template Engine – silnik generatora.

Na pierwszy ogień przyjrzyjmy się rozwiązaniu StringTemplate. Instalacja jest łatwa, gdyż istnieje odpowiedni pakiet dla NuGet-a, a zatem:

install-package Antlr4.StringTemplate

Przejdźmy teraz do utworzenia szablonu obiektu typu repository. Powiedzmy, że mój prosty szablon będzie wyglądał tak:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Biz.TemplateApp
{
 public class <entity.Name>Repository : BaseEFRepository\<<entity.Name>>
 {
 public class <entity.Name>Repository(IDbContext context) : base(context) {}
 }
}

Widzimy kawałek kodu przedstawiający prostą klasę i chcemy, aby w odpowiednich miejscach została wstawiona nazwa encji. W tym celu użyliśmy składni <entity.Name>. W ten sposób silnik generatora zastąpi ciąg znaków wartością przekazanej zmiennej. Jak widać zmienną nie musi być prosty string. W tym wypadku przekazujemy całą encję a w szablonie bierzemy z niej tylko nazwę. W związku z tym, że używam typu generycznego stąd znak \ który escape-uje specjalny znak < w szablonie.

Spójrzmy jak wywołać generator:

var customerEntity = new { Name = "Customer" };
string templateContent = File.ReadAllText(@"Templates\EntityRepository.tpl");
Template generator = new Template(templateContent);
generator.Add("entity",customerEntity);
string outputCode = generator.Render();

Najpierw wczytujemy z pliku nasz szablon (treść pokazana na poprzednim listingu). W następnej kolejności instancjonujemy nasz generator i dodajemy zmienną o nazwie „entity” i wartości customerEntity. Na koniec wywołujemy metodę Render generatora i uzyskujemy wynik:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Biz.TemplateApp
{
 public class CustomerRepository : BaseEFRepository<Customer>
 {
   public class CustomerRepository(IDbContext context) : base(context) {}
 }
}

Gotowe! Oczywiście to bardzo prosty przykład, a sam StringTemplate ma dużo większe możliwości jak: szablony grupowe, instrukcje warunkowe w szablonach, formatowanie, agregacja, regiony, itp. Po szczegóły odsyłam do dokumentacji. Jak widać nawet tak trywialny przykład jak przedstawiłem może być użyteczny. Możliwości są spore i w zasadzie ograniczone tylko Waszą fantazją.

Zapytacie co dalej z tym zrobić? Jak zintegrować to z Visual Studio? Rozwiązań jest kilka począwszy od custom tooli, a kończąc na dedykowanym pakiecie VS, ale to już temat na osobny post.

Na koniec dla porównania pokaże analogiczne rozwiązanie oparte na NVelocity. Jest odpowiedni pakiet dla NuGet-a, podobnie jak w poprzednim przypadku, a zatem instalacja jest prosta:

install-package NVelocity

NVelocity używa nieco innej składni szablonów – posiada własny język szablonów VTL. VTL używa referencji, aby zawrzeć w kodzie szablonu dynamiczną treść. Jedną z typów referencji jest zmienna. Wartość tej zmiennej może być przekazana z zewnątrz jak i ustawiona w samym szablonie. Generalna zasada VTL-a to: dyrektywa # oznacza że chcemy coś wykonać w szablonie, a dyrektywa $ że chcemy pobrać wartość. W naszym prostym przykładzie używamy jedynie pobierania wartości zmiennych przekazanych z zewnątrz. Oczywiście nie będę opisywał tu całej składni VTL-a, bo nie ma sensu powielanie dokumentacji.

Tak wyglądałby szablon naszego repository dla NVelocity:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Biz.TemplateApp
{
 public class ${entity.Name}Repository : BaseEFRepository<${entity.Name}>
 {
   public class ${entity.Name}Repository(IDbContext context) : base(context) {}
 }
}

NVelocity ewaluuje zmienne za pomocą ${enity.Name}. A teraz wywołanie generatora:

var customerEntity = new { Name = "Customer" };
Velocity.Init();
VelocityContext velocityContext = new VelocityContext();
string templateContent = File.ReadAllText(@"Templates\EntityRepository.vtp");
velocityContext.Put("entity", customerEntity);
StringBuilder outputCode = new StringBuilder();
Velocity.Evaluate(
   velocityContext,
   new StringWriter(outputCode),
   "repo template",
   new StringReader(templateContent)
 );

Najpierw mamy zainicjowanie NVelocity i utworzenie kontekstu, a następnie przekazanie zmiennych za pomocą metody put i wywołanie generatora za pomocą Evaluate.
NVelocity to port java-owego Velocity na .NET i ma naprawdę spore możliwości. To co tu pokazałem to tylko promil jego możliwości.

Dodaj komentarz »

Brak komentarzy.

RSS feed for comments on this post. TrackBack URI

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

Stwórz darmową stronę albo bloga na WordPress.com.

%d bloggers like this: