Skapa en egen ViewEngine

När man skapar nya vyer i ASP.NET MVC så skapas en aspx-fil upp, vilken påminner en del om klassisk ASP. När man sedan går in på sidan så kan man se att kodblocken är utbytta mot HTML. Den här transformeringen sker i vad som kallas en View Engine. Som standard i ASP.NET MVC används en View Engine vid namn WebFormViewEngine. WebFormViewEngine ärver klassen VirtualPathProviderViewEngine, som i sin tur implementerar IViewEngine.

Tack vare skalbarheten i ASP.NET MVC så kan vi byta ut denna helt och hållet mot en egen implementation mot antingen VirtualPathProviderViewEngine, eller direkt mot interfacet IViewEngine. I exemplet jag strax tar upp så kommer jag att gå direkt mot IViewEngine för att skapa en egen View Engine, samt skapa upp en implementation av Iview (ASP.NET MVC kör med WebFormView som standard).

Exemplet jag kommer att skapa är bara ett väldigt enkelt sådant, och bör ses som just ett exempel på hur man kan skapa en View Engine. Det kommer inte att vara optimalt, sexigt eller annat, utan är bara en väldigt enkel View Engine.

Skapa en View Engine

Det vår View Engine ska göra är att läsa av dels en Master.html, och sedan hämta in den vyn som vi returnerar i controllern och läsa in den. Även vyn kommer att vara en HTML-fil.

Det första steget är att skapa ett nytt vanligt ASP.NET MVC 2-projekt. Här skapar vi upp två klasser, SimpleView och SimpleViewEngine. SimpleView kommer att implementera IView och dess metoder.

namespace SimpleViewEngine
{
    public class SimpleView : IView
    {
        private string _masterPath;
        private string _viewPath;
 
        public SimpleView(string masterPath, string viewPath)
        {
            _masterPath = masterPath;
            _viewPath = viewPath;
        }
 
        public void Render(ViewContext viewContext, TextWriter writer)
        {
            //Läs in _masterPath och _viewPath.
        }
    }
}

I konstruktorn tar vi emot masterPath och viewPath från vår ViewEngine. Vi kommer sedan att läsa av masterPath och ersätta en viss del av den filen med viewPath.

I SimpleViewEngine som implementerar IViewEngine så kommer vi att hårdkoda sökvägen till Master.html för enkelhetens skull. Vi har även andra fasta sökvägar för att slippa tänka på det nu.

Klassen i sin helhet ser ut så här:

namespace SimpleViewEngine
{
    public class SimpleViewEngine : IViewEngine
    {
        private string pathPattern = @"{0}\views\{1}\{2}.html";
 
        public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            return FindView(controllerContext, partialViewName, "master", useCache);
        }
 
        public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            string viewPath = String.Format(pathPattern,
                controllerContext.HttpContext.Server.MapPath("~/"),
                controllerContext.RouteData.Values["controller"].ToString(),
                viewName);
 
            string masterPath = String.Format(pathPattern,
                controllerContext.HttpContext.Server.MapPath("~/"),
                "shared",
                "master");
 
            return new ViewEngineResult(new SimpleView(masterPath, viewPath), this);
        }
 
        public void ReleaseView(ControllerContext controllerContext, IView view)
        {
 
        }
    }
}

Det vi gör är att vi helt enkelt formaterar sökvägarna till filerna och skickar in i konstruktorn för SimpleView genom en klass kallad ViewEngineResult.

För att registrera vår ViewEngine lägger vi in det här i Application_Start i global.asax.cs:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new SimpleViewEngine());

Det gör att vi plockar bort WebFormViewEngine, och istället kör med SimpleViewEngine. Varje gång vi returnerar View() i en action-metod kommer SimpleViewEngine att köras.

Det som återstår nu innan vi kan testköra sidan är att skapa upp våra html-sidor som kommer att vara view och master page.

Först börjar vi med Index.html som ligger i /Views/Home tillsammans med Index.aspx. Den får det här utseendet:

<b>En enkel vy. :-)</b>
<p>
    Klockan är: %TIME%
</p>
<p>
    --Message 
</p>

Det är väldigt enkel HTML. Vi kommer dock att ersätta %TIME% med den aktuella tiden, samt –Message med ViewData[”Message”]. Det kommer att bli en vanlig replace och vi kommer inte att ha stöd för att skicka in någon modell till vyn (det är dock möjligt att bygga vidare på vår SimpleView för att ge stöd för sådant).

Master.html som ligger i /Shared ser ut så här:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Test av ViewEngine</title>
</head>
<body>
    <h1>Master!</h1>
    <div>
        %VIEW%
    </div>
    <div>
        Coolt!
    </div>
</body>
</html>

%VIEW% kommer att ersättas med vyn som anropas.

Om vi försöker gå till /Home/Index nu så ser vi att skärmen blir blank. Anledningen till det är att vi fortfarande inte skriver ut något i Render-metoden i SimpleView.

Nästa steg blir att skriva logiken för att läsa in filerna (varning för fulkod):

public void Render(ViewContext viewContext, TextWriter writer)
{
    try
    {
        string view = File.ReadAllText(_viewPath);
        view = view.Replace("%TIME%", DateTime.Now.ToLongTimeString());
 
        if (view.Contains("--"))
        {
            int keyIndex = view.IndexOf("--");
            int length = view.IndexOf(" ", keyIndex) - keyIndex;
 
            string key = view.Substring(keyIndex, length);
            string value = viewContext.ViewData[key.Remove(0, 2)].ToString();
 
            view = view.Replace(key, value);
        }
 
        string master = File.ReadAllText(_masterPath);
        master = master.Replace("%VIEW%", view);
 
        writer.Write(master);
    }
    catch (Exception ex)
    {
        writer.Write("Oops..! Nu blev det knas!<div>" + ex.Message + "</div>");
    }
}

Det vi gör här är helt enkelt att läsa in filerna rakt av och sedan köra några replace för att få in all text.

Om vi surfar till startsidan nu så kan vi se att det här renderas på sidan:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Test av ViewEngine</title>
</head>
<body>
    <h1>Master!</h1>
    <div>
        <b>En enkel vy. :-)</b>
<p>
    Klockan är: 23:13:45
</p>
<p>
    Welcome to ASP.NET MVC! 
</p>
    </div>
    <div>
        Coolt!
    </div>
</body>
</html>

Utan att ändra något i controllern så har vi plockat fram ViewData och renderat vyerna baserat på HTML-filer.

Prestanda

Så hur blir det med prestandan? Då det inte blir ett lager ovanpå det befintliga, utan en ersättning, så påverkas inte prestandan negativt så länge som det inte är en dåligt skriven View Engine. Faktum är att det faktiskt kan gå snabbare om man skriver en View Engine som är optimerad för det aktuella syftet istället för att ha en som är anpassad för att vara generell.

Andra implementationer av ViewEngine

Så hur kan man gå vidare? Ett bra sätt är att kika på källkoden för ASP.NET MVC. Där finns allt tillgängligt och det är enkelt att se hur allt fungerar i bakgrunden.

Ett annat alternativ är att ta en titt på de olika varianter som finns tillgängliga på internet. En populär ViewEngine är Spark View Engine, vilket ger en HTML-liknande syntax i vyerna, men som även tillåter databindning och annat med samma syntax. Det är även open source, så man kan lätt se hur det fungerar.

Källkod, exempel och annat finns här:
http://sparkviewengine.com

No Comments