Templating mit Razor aber ohne MVC

Ich habe unlängst nach einer Möglichkeit gesucht, Seiten schnell und effektiv zu generieren, sowohl für Web als auch für E-Mails als Background-Jobs. Ein Kollege hat mich auf @razorengine aufmerksam gemacht und diese Templating Engine scheint alles zu bieten, was ich gesucht habe.

A templating engine built upon Microsoft’s Razor parsing technology. The RazorEngine allows you to use Razor syntax to build robust templates. Currently we have integrated the vanilla Html + Code support, but we hope to support other markup languages in future.

Die Installation ist so einfach wie möglich:

//mit NuGet:
Install-Package RazorEngine

Man muss danach nur noch die RazorEngine.dll und System.Web.Razor.dll referenzieren und das war’s.

 

Das Generieren von Seiten kann entweder direkt oder mit vorkompilierten Templates erfolgen:

[Test]
public void PageCanBeParsed()
{
   const string template = "Hello @Model.Name! Welcome to Razor!";
   var result = Razor.Parse(template, new {Name = "World"});

   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}
//unterstützt anonyme Klassen
[Test]
public void PageCanBeParsedWithCompiledTemplate()
{
   const string template = "Hello @Model.Name! Welcome to Razor!";
   Razor.CompileWithAnonymous(template, "world");
   var result = Razor.Run(new {Name = "World"}, "world");

   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}
//oder konkrete Typen
[Test]
public void TemplateIsCompiledWithModelType()
{
   const string template = "Hello @Model.Name! Welcome to Razor!";
   var testModel = new TestModel{ Name = "World" };
   Razor.Compile( template, typeof(TestModel), "world2" );
   var result = Razor.Run(testModel, "world2");
   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}

public class TestModel { public string Name; }

Um ehrlich zu sein, ich habe noch keinen Grund gefunden, warum man nicht die vorkompilierte Variante nutzen soll. In diesem Fall wird das Template EIN MAL kompiliert und in Cache abgelegt. Ein Vergleichs- und Stresstest, in dem ich ein halbwegs komplexes Template 500-mal mit Razor.Parse bzw. mit Razor.Compile + Razor.Run aufgerufen habe, hat folgendes geliefert:

   Parse: 03:35.97 min
   Compile+Run: 00:00.63 min

Ich glaube, damit ist alles gesagt. Es sei denn, eine Zeile weniger gilt auch als Argument 🙄

 

RazorEngine unterstüzt fast alles, was Microsoft’s Razor in Views anbietet, wie zum Beispiel Helper-Methoden, Model-Definitionen direkt im Template oder partielle Views:

[Test]
public void EngineSupportsInlineHelper()
{
   const string template = @"@helper MyMethod(string name) {Hello @name}@MyMethod(Model.Name)! Welcome to Razor!";
   var testModel = new TestModel{ Name = "World" };
   var result = Razor.Parse(template, testModel);
   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}

[Test]
public void EngineSupportsInheritsCommand()
{
   const string template = @"@inherits RazorEngine.Templating.TemplateBase
@helper MyMethod(string name) {Hello @name}@MyMethod(Model.Name)! Welcome to Razor!";
   var testModel = new TestModel{ Name = "World" };
   Razor.Compile(template, typeof(TestModel),"testModel");
   var result = Razor.Run(testModel, "testModel");

   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}

[Test]
public void EngineSupportsSubtemplating()
{
    const string masterTemplate = "You are on www.yellow-brick-code.org!";
    Razor.Compile(masterTemplate, typeof(TestModel), "master");

    const string contentView = @"@inherits RazorEngine.Templating.TemplateBase
@helper MyMethod(string name) {Hello @name}@MyMethod(Model.Name)! Welcome to Razor!";
    var testModel = new TestModel{ Name = "World" };

    const string template = contentView + " @Include(\"master\")";

    Razor.Compile(template, typeof(TestModel), "testModelMaster");
    var result = Razor.Run(testModel, "testModelMaster");
    Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor! You are on www.yellow-brick-code.org!"));
}

Und nun sind wir soweit, das Ganze im Web zu testen.

 

Die Engine funktioniert so, dass sie zur Laufzeit aus dem Template eine dll mit einem Zufallsnamen erstellt. Also wenn man die Engine im Web nutzen will, müssen noch ein paar Dinge getan werden:

  1. RazorEngine.Web.dll referenzieren
  2. Ein VirtualPathProvider in Global.asax.cs registrieren
    public class Global : System.Web.HttpApplication
    {
       protected void Application_Start(object sender, EventArgs e)
       {
          HostingEnvironment.RegisterVirtualPathProvider(new RazorVirtualPathProvider());
       }
    ...
    
  3. In Web.Config muss der BuildProvider registriert werden:
    <configuration>
    <configSections>
    <section name="razorEngine" type="RazorEngine.Configuration.RazorEngineConfigurationSection, RazorEngine" requirePermission="false" />
    </configSections>   <razorEngine factory="RazorEngine.Web.WebCompilerServiceFactory, RazorEngine.Web" /><system.web>
    <compilation debug="true" targetFramework="4.0">
    <buildProviders>
    <add extension=".csrzr" type="RazorEngine.Web.CSharp.CSharpRazorBuildProvider, RazorEngine.Web" />
    </buildProviders>
    </compilation>

So aufgerüstet kann man mit den Models und Templates beginnen. Man kann die Templates als html-Dateien speichern. Allerdings wenn man IntelliSense haben möchte, dann muss MVC3 auf den Rechner installiert und die Datei als cshtml gespeichert werden.

 

Das Beispiel hier ist über ein MasterHeader mit dem Anfang der Seite, ein MasterFooter mit dem Ende, beide eingebettet mit " @Include(\"master...\")" in Content. Die ganze Seite bekommt ein Objekt vom Typ Model zum Parsen. Alle Templates werden mit einem ITemplateFinder geladen.

...
ITemplateFinder templateFinder = new TemplateFinder(path);
Razor.Compile(templateFinder.GetTemplate("masterHeader.cshtml"), typeof(Model), "masterHeader");
Razor.Compile(templateFinder.GetTemplate("masterFooter.cshtml"), typeof(Model), "masterFooter");

var model = new Model
{
    PurchaseNo = "011313074142",
    Amount = "270.63",
    Date = "20110121"
};

var template = templateFinder.GetTemplate("Content.cshtml");
Razor.Compile(template, typeof(Model), "content");
var parsedTemplate = Razor.Run(model, "content");
context.Response.ContentType = "text/HTML";
context.Response.Write(parsedTemplate);

Bevor ihr was über Namen oder Verantwortlichkeiten was sagt: das Projekt wurde als Spike erstellt, und als solche hat seine Rolle  perfekt erfüllt 🙂 In Produktion würde ich das Kompilieren von statischen Templates in Application_Start verschieben und das ITemplateFinder sollte auf  jeden Fall Injected werden.

 

Ich muss mich bei den 2 Jungs, die das Projekt entwickeln, sehr bedanken, es war eine super Idee! Schaut es einfach an, die dll kann noch viel mehr.

NOS Süd – ein etabliertes Konzept

Man nehme 50 bis 80 engagierte Menschen, ein gemeinsames Thema (Softwareentwicklung rund um .NET), 4 oder 5 Räume mit vielen Stühlen und ein paar Steckdosen und Flipcharts oder Beamer, gebe ausreichend Kaffee und ein Versprechen auf ein späteres Bier dazu. Man gebe ihnen alle Freiheiten der Kreativität – und eventuell ein Paar Gadgets, da es vorwiegend um Männer geht 😉 – und lasse sie machen. Fertig ist das OpenSpace-Gericht, mit Erfolgsgarantie.

 

Nach ca. 60 Stunden werden alle NOS-Süd 2011 mit einem gefüllten “mentalen” Rucksack verlassen! Manche werden konkrete technische Fragen geklärt bekommen haben, manche werden in ihrem Vorgehensweisen bestätigt und manchen werde eine ganz neue Richtung aufgezeigt. Manche lernen endlich die kennen, dessen (deren) Blogs sie seit langem lesen und manche werden aus der Anonymität “des einsamen Softwareentwicklers” herauskommen und sich als “Nerds” outen. Es werden Freundschaften gegründet und sicherlich auch künftige Geschäftsbeziehungen.

 

Und alle gehen müde und voll mit Tatendrang nach Hause, sich in der Community weiter zu engagieren und nächstes Jahr wieder zu kommen.

 

Das alles ist Open Space und gilt speziell für den .NET Open Space Süd, der an diesem Wochenende abgehalten wurde. Erstens war er schon für das zweite Mal in Karlsruhe, wo es bekanntermaßen immer schönes Wetter ist 😉 Zweitens waren da gerade so viele Menschen, dass keine Person und/oder Frage untergehen konnte. Aber das besondere “Salz” lieferten die Organisatoren dazu: Frank, Alex, Ralf und Aydin und all ihren großartigen Helfer. Es ist für mich immer wieder erstaunlich, wo sie die Zeit und Energie finden, das alles “so nebenbei” zu organisieren. Manchanderer findet nicht mal genug Zeit, zu einem User Group-Treffen hin zu gehen und die Jungs opfern ihre Freizeit dafür, uns diese jährliche Treffen zu organisieren. Nochmal DANKE dafür!

 

Ja, und nur zur Info: ich habe mir ein Twitteraccount eingerichtet 🙂

dotnet-cologne: veni-vidi-vici!

Am letzten Freitag fand die dotnet Cologne zum 3. Mal statt. Ich habe die ersten 2 Male nicht teilgenommen, aber wie ein Freund meinte, diesmal hatte ich “Heimvorteil” 🙂 Nach der langen Pause, bedingt durch Beinbrüche, Umzüge und ähnliche Erfahrungen, hatte ich regelrechte Entzugserscheinungen: Entzug von der Community.

Principles, patterns, and practices are important, but it’s the people who make them work. – Robert C. Martin aka Uncle Bob

Es war super! Über 300 Leute, darunter sehr-sehr viele bekannte Gesichter, riesen Freude sie wieder zu sehen. Der erste Vortrag, den ich besucht habe, hieß “Agile Architekturen” und wurde von Ilker abgehalten. Er hat zwei konkrete Beispiele – die Hexagonale Architektur von Alistair Cockburn und Data Context Interaction, der “neue MVC” – vorgestellt, samt Fallbeschreibung und Code für jeweils ein User Case. Wir dürften/sollten uns dann eine Meinung über die Lösungen bilden und diese Meinung auch kundtun. Es wurde schnell klar, dass jede Variante ihre Vor- und Nachteile hat, und dass die allgemein gültige “agile Architektur” nicht wirklich existiert.

 

Nach der Pause ging es dann für mich mit Daniel “REST Wars: WCF WebHTTP vs. ASP.NET MVC” weiter. Es war eine sehr interessante und kompetente Präsentation. Am Ende hatte ich eine ganz genaue Vorstellung darüber, WOZU man REST verwenden soll und welche technologische Möglichkeiten man hat. Notiz an mich: WCF WebAPI bei Codeplex anschauen.

 

Als Lunch-Session wollte ich unbedingt von Sergey über psake hören, aber die Zeit ist bei den Gesprächen mit den Jungs von User Group Karlsruhe irgendwie verflogen. Dafür habe ich stattdessen Kay Giza kennengelernt 🙂 Endlich kann ich ein Gesicht zu jener Person zuordnen, die mir seit Jahren regelmäßig Tipps und Empfehlungen schickt. Er hatte selbst eine Session über MSDN vorbereitet und ich hätte ihn sehr gern gehört, aber er war parallel mit der Azure-Session und an dieser musste ich auf jedem Fall teilnehmen. Also hat Kay meine Fragen zum MSDN zwischen a flying coke ( 😉 you know what I mean) und einen Kaffee im Foyer beantwortet. Danke schön 🙂

 

Der “Überflieger” des Tages war eindeutig Bart de Smet – der Mann, der in seiner Freizeit u.a. Sudoku mit c# (Stichwort Microsoft Solver Foundation) löst.
Ich konnte leider nur den ersten von 2 Vorträgen besuchen, über “LINQ to Everything” – und das hat er wirklich wortwörtlich gemeint. (Stichwort IQbservable, IScheduler)

 

Der vorletzte Vortrag war der bereits erwähnte “Azure: Portierung einer Anwendung”. Wir haben einen kleinen Überblick von der Zusammensetzung von “the Cloud” bekommen, und die “Do’s and Dont’s” dazu. Und mir wurden diese zwei Tatsachen klar: in der Cloud alles kostet Geld und ohne Clean-Code (insbesondere ohne Dependency Injection) braucht man darüber gar nicht nachzudenken. Wenn aber die Vorbedingungen erfüllt sind, dann geht das Portieren super schnell und einfach.

 

Last but not least habe ich mir eine Session über “Rich-Internet Apps & Mobile Anwendungen mit HTML5…” gegönnt, ein Thema, mit dem ich mich zur Zeit nicht direkt beschäftigen muss – zur Entspannung so zusagen 😉 Also dies war eine super Entscheidung, Tim Fischer ist ein großartiger Vortragender! Als Nebeneffekt habe ich erfahren, wie man JavaScript-Code ohne es zu schreiben generiert (Stichtwort Sencha) und was hinter dem Begriff HTML5 steckt.

 

Damit war für mich das Pflichtprogramm zu Ende, aber natürlich nicht der Tag.
Jeder, der bei solchen Community-Events schon teilgenommen hat, weiß, dass die besten Gespräche rund um Entwickler-Sein bei dem/den abschließenden Bier/en danach geführt werden. So hatte ich die Ehre, endlich codemurai kennen zu lernen und die Meinungen der anderen über Themen wie “warum ist Softwareentwicklung einer Männerdomaine” oder “Absatzmarkt China” oder “das Geheimniss hinter dem Erfolg von osteuropäischen Softwareunternehmen” zu hören.

 

Es war schön, es war anstrengend und es war sehr lehrreich.
Genauso, wie eine Konferenz sein soll.

 

Tausend Dank an das Orga-Team und an die Sprecher für ihre großartige Arbeit und für ihre Mühe, diese Community zu stärken und zu pflegen.
“Individuals and Interactions over Processes and Tools” – wie das Agile Manifest es so schön definiert – weil wo wären wir, wenn das nicht war wäre?!

 

Das nächste Event – NOS Süd 2011 – wartet schon…