Create class from JSON

Ich habe vor einem Jahr bei einer katastrophalen MSFT-Veranstaltung über ein super Feature in VS2012 erfahren, das ich – brownfield-bedingt – noch nie ausprobieren konnte. Heute hat es mir allerdings mindestens eine halbe Stunde Arbeit und eine große Menge Frust erspart!

Das Feature ist sehr schnell erklärt: wenn man ein Objekt, das als JSON oder XML serialisiert vorliegt – also einen string 😉 – in Zwischenspeicher hat, dann kann man das direkt als Klassenmodel abspeichern. Tatata..Fertig 🙂
Edit/Paste Special

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.

Tausende Codezeilen verstehen – aber wie?

Nach 7 Wochen Zwangsurlaub (siehe unten) bin ich wieder back to life: mit einem halbwegs neuen Bein (mit Titaneinlagen und Schlitzschrauben 😉 ), in einer neuen Stadt mit 100%-er Snowcoverage, in einem neuen Job – nach einem Monat Verspätung.

Das letzte Mal, als ich bei einer Firma den Code verstehen musste, ging es um ASP-Classic. Die Abhängigkeiten waren überschaubar, das Debugen ging mit Response.Write-Zeilen ;). Aber wie macht man das heute, wie versteht man den bestehende Code, der in vielen Jahren aus den fleißigen Fingern der Programmierern herausgeflossen ist? Und das im Web, in einer unglaublich flexiblen E-Commerce-Anwendung…

Die Lösung war einfach: mit Unit-Tests ! Ich musste nicht erklärt bekommen haben, WAS der Code tut, nur welche Aufrufe zu welchen Ergebnissen führen. Immer, wenn ich ein Szenario verstanden habe, wurden dafür Tests geschrieben und das Ergebnis besprochen. Der Begriff unit wurde teilweise natürlich ausgedehnt, aber das hat nichts an der Tatsachen geändert, dass am Ende

  1. ich den Code verstanden habe
  2. die meisten analysierten Zeilen durch einen Test abgedeckt wurden
  3. die Diskussionen über den Testnamen dazugeführt haben, dass manch unnötige Zeilen (lese “Szenarien”) entfernt wurden, also der Code besser geworden ist