Generera kod med T4-templates
Något som har funnits med ett tag, men som inte har använts särskilt mycket förrän när ASP.NET MVC introducerades är T4-templates. T4 är en akronym för ”Text Template Transformation Toolkit”, och gör det möjligt att skapa upp en mall som sedan kan användas för att generera färdig kod.
När man skapar templates med T4 så kan man använda antingen C# eller Visual Basic för syntaxen. All kod skrivs i kodblock, vilket påminner en del om hur vi normalt sett skapar vyer i ASP.NET MVC.
Visual Studio har inbyggt stöd för att skapa T4-templates och sedan generera kod från dem, men det finns inget inbyggt stöd för att få färgkodning eller IntelliSense. Det resulterar i att man sitter med vit bakgrund och svart text som om det vore ett vanligt textdokument. Det finns dock en enkel lösning på det, nämligen Tangible T4 Editor, vilket är en gratis-editor för T4 och finns att ladda ned för Visual Studio 2010 här:
http://visualstudiogallery.msdn.microsoft.com/en-us/60297607-5fd4-4da4-97e1-3715e90c1a23
Tangible T4 Editor gör arbetet så otroligt mycket enklare att det inte finns någon anledning till att inte ha det installerat.
Det jag kommer att göra nu är att börja med en enkel template för att visa vad som genereras, och hur det genereras. Sedan kommer jag att använda ASP.NET MVC för att kunna skriva en egen template för vyer som skapas.
Skapa en första template
Till att börja med så behöver vi ett vanligt klassbibliotek där vi plockar bort cs-filen som kommer som standard. Sedan behöver vi skapa upp en T4-template. Om man har installerat Tangible T4 Editor så kommer ett par projektmallar upp när vi tar Create -> New File, men vi kommer att skapa en tom template, vilken heter ”Text Template”. Vi kallar sedan filen för MyTemplate.tt. Just ”tt” är filändelsen för alla T4-templates.
T4-filen som skapas upp ser ut så här:
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
I Solution Explorer så kan vi även expandera MyTemplate.tt och under den filen ha en fil med namn MyTemplate.txt. Txt-filen är genererad från vår template, och vill vi göra ändringar i txt-filen så skall dessa göras i templaten, då de skrivs över annars.
I T4-filen så kan vi se att vi har ett par olika attribut. Debug är en boolean som avgör om vi vill ha möjlighet att debugga mallen eller inte. Hostspecific är en boolean som i det här fallet skall vara false då vi använder TextTemplateFileGenerator för att rendera filen (det kan vi se under Properties för tt-filen), och language som här är C#, men även kan vara VB visar vilken syntax vi vill använda i mallen. Vi kan även se att vi har ”.txt” som filändelse på filen vi renderar, och ändrar vi det till t.ex. ”.cs” så genereras en cs-fil istället.
En skillnad från när vi sitter i ASP.NET MVC är att koden finns inom <# och #> istället för <% och %> som vi är vana vid. Det här gäller överallt där vi vill ha kod i vår mall.
För att få in något innehåll i textfilen så börjar vi med att lägga till det här i mallen:
Hello, world!
När vi sparar tt-filen och tittar i txt-filen så kan vi se att texten har dykt upp där. Det här är dock ingen bra anledning till att använda T4-templates då vi lika gärna skulle kunna skriva det direkt i textfilen. Så hur tar vi nytta av C#?
Vi lägger nu till det här i filen:
Klockan är: <#= System.DateTime.Now.ToShortTimeString() #>
Lägg märke till ”=”, vilket skriver ut texten direkt, precis som i ASP.NET MVC. Sparar vi nu om filen så ser vi att vi har fått ut aktuell tid i txt-filen. Om vi vill uppdatera texten utan att spara om mallen så kan vi högerklicka på tt-filen och välja ”Run custom tool”.
Det som är så elegant med templates är att vi får tillgång till hela .NET, och enkelt kan generera filer. Kikar vi på POCO-stödet i Entity Framework 4.0 så kan vi se att T4-templates används för att generera klasser för modellen, baserat på edmx-filen. Det sker på samma sätt som vi nu skriver ut tiden, så det går att göra väldigt avancerade saker med det.
För att ta ett lite mer avancerat exempel så kommer vi nu att ha en loop där vi skriver ut siffror:
<#
for (int i = 1; i <= 10; i++)
WriteLine(i.ToString());
#>
Sparar vi om filen så får vi fram siffrorna 1-10 på en varsin rad. Det är alltså väldigt smidigt att generera dessa filer, och vi behöver inte ens kompilera koden för det!
Templates i ASP.NET MVC
Så hur kan vi nu ta vara på den här kunskapen och generera mallar för våra vyer i ASP.NET MVC?
Först och främst så kommer jag att öppna projektet i artikeln jag skrev om att skapa en egen ViewEngine:
http://weblogs.asp.net/mikaelsoderstrom/archive/2010/06/17/skapa-en-egen-viewengine.aspx
När vi skapar nya vyer i ASP.NET MVC så har vi en del standardmallar som renderar kod som passar bra för WebFormViewEngine, men om vi nu vill göra detsamma för SimpleViewEngine?
Först och främst så kommer jag att bygga ut SimpleView för att ge den stöd för modeller. Det är inte den snyggaste koden för det, men den fungerar som den ska och gör det snabbt och enkelt. I metoden Render i klassen SimpleView så kommer jag att lägga till det här efter koden som byter ut – mot ett ViewData-värde (all kod kommer att finnas tillgänglig för nedladdning):
object model = viewContext.ViewData.Model;
if (model != null)
{
Type t = model.GetType();
PropertyInfo[] props = t.GetProperties();
foreach (PropertyInfo item in props)
{
object o = item.GetValue(model, null);
if (o != null)
view = view.Replace("%%" + item.Name, o.ToString());
}
}
Det vi gör i koden är att vi går igenom alla properties i modellen och ersätter %%PropertyNamn i vyn med dess värden.
Vi skapar sedan upp en modell:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
Och så skickar vi med den i Index-metoden i HomeController:
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
Customer customer = new Customer() {
Id = 1,
Name = "Mikael"
};
return View(customer);
}
Jag kommer inte att gå igenom det här alltför noggrant, utan koden är ganska självförklarande.
Istället så ska vi nu skapa upp en T4-template som kan rendera rätt HTML åt oss.
Den HTML vi vill få ut är:
<h2>Index</h2>
<p> --Message </p>
<ul>
<li>Id: %%Id </li>
<li>Name: %%Name </li>
</ul>
Så hur kan vi göra detta?
Först och främst så skapar vi upp en mapp i roten för projektet med namnet ”CodeTemplates”. Här kan vi sedan ha två mappar, ”AddController” som innehåller templates för nya controllers, och ”AddView”, vilken innehåller templates för nya views. Vi kommer bara att rendera nya views, så vi skapar upp AddView och där skapar vi en fil vid namn ”SimpleDetails.tt”.
Vi kan nu se att vi får en fil skapad åt oss automatiskt direkt under mallen. Den vill vi inte ha, så vi tar bort värdet i Custom tool.
Vår template i sin helhet ser ut så här:
<#@ template language="C#" HostSpecific="True" #>
<#@ assembly name="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Reflection" #>
<#@ output extension=".html" #>
<#
var mvcHost = (MvcTextTemplateHost)Host;
var properties = new Dictionary<string, string>();
if (mvcHost.ViewDataType != null)
{
foreach (var pi in mvcHost.ViewDataType.GetProperties())
properties.Add(pi.Name, "%%" + pi.Name);
}
#>
<h2><#= mvcHost.ViewName #></h2>
<p> --Message </p>
<ul>
<# foreach(var property in properties) { #>
<li><#= property.Key #>: <#= property.Value #> </li>
<# } #>
</ul>
Det vi kan se här är att vi först har satt HostSpecific till true. Anledningen till det är att vi nu inte har något verktyg som genererar kod direkt, utan när vi skapar nya vyer så kommer MvcTextTemplateHost att användas. Den innehåller det vi behöver veta när vi skapar upp nya vyer.
Vi har sedan ett par referenser till de bibliotek vi behöver komma åt, samt sätter output extension till ”.html” då våra vyer är rena HTML-filer.
Efter det kommer koden, vilket är ren C#. Här sparar vi först undan hosten, vilken är en MvcTextTemplateHost, och sedan skapar vi upp en Dictionary<string, string> som kommer att innehålla namnet på vår property, samt det som krävs för att den skall renderas rätt i vyn.
ViewDataType som vi kollar efter är modellen som skickas från vår action-metod. I vårt fall kommer det att vara en Customer, men det skulle även kunna vara något annat.
Sedan har vi vår vanliga HTML, samt skriver ut de värden vi vill generera.
Nästa steg blir att använda mallen för att generera en ny Index.html. Om du redan har en Index.html i /Views/Home så ta bort den.
Öppna sedan upp HomeController, högerklicka i Index-metoden och välj Add View. Som View data class sätter vi Customer-modellen vi har skapat upp, och under View content så väljer vi SimpleDetails, vilket är den mallen vi har skapat upp nu.
När filen nu skapas upp så har den fått det här utseendet:
<h2>Index</h2>
<p> --Message </p>
<ul>
<li>Id: %%Id </li>
<li>Name: %%Name </li>
</ul>
Den har alltså läst av vår T4-template, läst av alla properties i modellen och sedan genererat en vy anpassad för SimpleViewEngine!
Ser vi på hur sidan ser ut nu så kan vi se att allt renderas precis så som vi önskar.
Med T4-templates kan vi skapa upp alla typer av dokument snabbt och enkelt, och då med hjälp av allt .NET har att ge oss.