DotLiquid-Asp.net模板引擎

之前用過一段時間的PHP,感受很是不錯,其中最讓我難忘的就是Smarty模板引擎,當時就微微地想Asp.net裏有沒有像這樣的模板引擎呢?不過因爲以後的工做內容都用不到,或者說沒有想到用模板,這想法也沒致使我作一些事情,就不了了之了。
    如今也是工做須要,用模板是一個不錯的選擇。以前沒用過這種東西,盲搜一片沒找到本身想要的,因而本身就試着寫寫,大思路用的是Smarty的,用html頁面作爲模板,生成aspx頁面,把數據放在HttpContext.Items裏,代碼以下:
<div>{$title}</div>
<select>
    {foreach $l in $list}
    <option value="{$l.Age}">{$l.Name}</option>
    {/foreach}
</select>

生成的aspx頁面: javascript

<%@ Page Language="C#" %>
<%
    if (HttpContext.Current.Items["SMARTY_TEMPLATE_DIR"]==null)
    {
        Response.Write("no direct access allowed");
        Response.End();
    }
%>
<div><%=DotSmarty.Smarty.GetTemplateArg("title") %></div>
<% var list = DotSmarty.Smarty.GetTemplateArg("list") as System.Collections.Generic.IList<SmartyTest.User>; %>
<select>
    <% foreach (var l in list){ %>
    <option value="<%=l.Age %>"><%=l.Name %></option>
    <%}%>
</select>
調用如

Smarty smarty = new Smarty();
List<User> list = new List<User>();
list.Add(new User() {  Age=1, Name="name111"});
list.Add(new User() { Age = 2, Name = "name222" });
smarty.Assign("title", "標題");
smarty.Assign("list", list, TemplateArgType.List);
smarty.Display("user/userInfo.htm");
看起來很像Smarty,可越寫難度越大!唉,能力有限,未來有能力再說吧,如今只能放棄。

    前幾天幸運地據說了DotLiquid,網址是:http://dotliquidmarkup.org。上面曰:「DotLiquid is a templating system ported to the .net framework from Ruby’s Liquid Markup.It’s easy to learn, fast and safe"。我想我終於找到了asp.net中的smarty了,更有圖說明: css

這裏先介紹一下她的幾個主要的概念: html

 Filter:"Filters are simple methods(過濾器是一些簡單的方法)" java

     如標準Filter中的upcase,{{ "looGn" | upcase }}  值爲"LOOGN"。  jquery

 Tag:"Tags are used for the logic in your template(標籤用於實現模板中的邏輯)"。  git

     如標準Tag中的assign,{% assign freestyle = false %} 定義值爲false的freestyle變量。 github

 Block:其實block也是tag,如if..else,for..in, 能夠說Block是有endtag的Tag。 數據庫

    如:{% for i in (1..5) %} 編程

          {{ i }} c#

         {% endfor %}

下面跟你們分享一下這幾天對她的理解及代碼實現:

下載過發佈的壓縮包DotLiquid v1.5.zip裏會有v3.5和v4.0兩個版,引用DotLiquid.dll的對應版本到本身項目便可。其實她真的是easy to learn!看一個handler的代碼:

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/plain";
    Template template = Template.Parse("模板內容:{{hw}}");//用模板內容作爲參數解析獲得Template對象
    string result = template.Render(Hash.FromAnonymousObject(new { hw = "Hello World!" }));//用模板所需的元素作爲參數呈現處理後的結果
    context.Response.Write(result);
}
一切的一切,其實就這兩步!關鍵是!咱們如何應用!"技術不是祕密,祕密是如何善用技術!" 不記得在哪看得一句話,不過無所謂啦!

 第一步: 從文件讀入模板內容。模板大多數不是程序中的一個字符串變量,可能存在文件、數據庫裏等,這裏說的是html文件模板。TemplateHelper以下

public static class TemplateHelper
    {
        #region Template路徑

        static TemplateHelper()
        {
            //可用文件配置,例子中用了字典
            _map = new Dictionary<string, string>(50);
            _map.Add("master", "~/template/master.htm");
            _map.Add("index_content_main", "~/template/index_content_main.htm");
            _map.Add("list_content_main", "~/template/list_content_main.htm");
            _map.Add("list_content_script", "~/template/list_content_script.htm");
            _map.Add("detail", "~/template/uc/detail.htm");
        }

        #endregion

        private static Dictionary<string, string> _map;

        public static bool ContainsKey(string key)
        {
            return _map.ContainsKey(key);
        }
        public static string GetTemplateURL(string key)
        {
            try
            {
                return _map[key];
            }
            catch (KeyNotFoundException e)
            {
                KeyNotFoundException ne = new KeyNotFoundException(e.Message + "key:" + key);
                throw ne;
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /*這個方法限制了文件模板路徑必要配置,若是須要能夠添加直接以文件路徑爲參數的方法,
         *不過感受這裏配置起來是個好的習慣 */
        public static Template GetFileTemplate(string templateKey, Encoding encoding)
        {
            Template template = HttpContext.Current.Cache[templateKey] as Template;
            if (template == null)
            {
                string path = HttpContext.Current.Server.MapPath(GetTemplateURL(templateKey));
                template = Template.Parse(File.ReadAllText(path, encoding));
                CacheDependency dependency = new CacheDependency(path);
                HttpContext.Current.Cache.Add(templateKey, template, dependency, Cache.NoAbsoluteExpiration,
                    Cache.NoSlidingExpiration, CacheItemPriority.Default, null);//把模板緩存起來
            }
            return template;
        }

        public static Template GetFileTemplate(string templateKey)
        {
            return GetFileTemplate(templateKey, Encoding.UTF8);
        }
    }

第二步:include文件。DotLiquid的include是一個標準Tag,如{% include top %}。調用include須要給Template.FileSystem賦值,它是一個IFileSystem接口,在IFileSystem裏只有一個方法:

public interface IFileSystem
{
    string ReadTemplateFile(Context context, string templateName);
}

很顯然,解析到include top時會把top作爲templateName調用Template.FileSystem.ReadTemplateFile方法,以此來實現include。因此實現IFileSystem的類要以templateName參數返回對應的內容:

public class IncludeFileSystem : IFileSystem
    {
        private Encoding _encoding = Encoding.Default;

        public IncludeFileSystem(){}

        public IncludeFileSystem(Encoding encoding)
        {
            _encoding = encoding;
        }

        public string ReadTemplateFile(Context context, string templateName)
        {
            bool isOptional = false; //是否可選
            string templateKey = templateName;
            if (templateName.EndsWith("_optional"))
            {
                isOptional = true;
                templateKey = templateKey.Replace("_optional", "");
            }
            if (templateKey.StartsWith("content_"))
            {
                object ns = context.Environments[0]["ns"];
                if (ns == null)
                {
                    ns = Path.GetFileNameWithoutExtension(HttpContext.Current.Request.RawUrl);
                }
                templateKey = ns + "_" + templateKey;
            }
            object result = HttpContext.Current.Cache[templateKey];
            if (result == null)
            {
                if (isOptional && !TemplateHelper.ContainsKey(templateKey))
                {
                    return string.Empty;
                }
                string path = HttpContext.Current.Server.MapPath(TemplateHelper.GetTemplateURL(templateKey));
                result = File.ReadAllText(path, Encoding.UTF8);
                CacheDependency dependency = new CacheDependency(path);
                HttpContext.Current.Cache.Add(templateKey, result, dependency, Cache.NoAbsoluteExpiration, 
                    Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
            }
            return result.ToString();
        }
    }
這裏的 ReadTemplateFile 有 點複雜,其實就是根據templateName來讀文件,緩存,至於開始的判斷後面再說。在適當的地方設置一下Template.FileSystem 可。Template.FileSystem = new IncludeFileSystem();我寫在了Application_Start事件處理程序裏。

 

 第三步:提供測試數據。下面爲了簡單,沒從數據庫讀取:

public static class WebHelper
    {
        public static Family GetFamily()
        {
            Family family = new Family()
            {
                Host = "孫悟空",
                Address = "大佛山小石村",
                Count = 4,
                Desc = "快樂的一家人!"
            };
            return family;
        }

        public static List<Member> GetMembers()
        {
            List<Member> members = new List<Member>() { 
                new Member(){ ID=1, Name="孫悟空", Age=42, Sex=true, Relation="本人", Desc="下等戰士,重情重義、毫不欺騙朋友、喜歡幫助人,就算對着敵人也會幫助他。 屢次救了地球和全人類。" },
                new Member(){ ID=2, Name="牛琪琪", Age=40, Sex=false, Relation="妻子", Desc="孫悟空和琪琪結婚了,孫悟空沒老可琪琪老了!"},
                new Member(){ ID=3, Name="孫悟飯", Age=22, Sex=true , Relation="長子", Desc="擁有極高的潛力,每次遇到危險時都會發揮出強大力量來保護本身。"},
                new Member(){ ID=4, Name="孫悟天", Age=15, Sex=true , Relation="次子", Desc="天賦極高,可老想着泡妞!"},
            };
            return members;
        }

        public static Member GetMemberInfo(int id)
        {
            return GetMembers().Single(m => m.ID == id);
        }
    }
    public class Family:Drop
    {
        public string Host { get; set; }

        public string Address { get; set; }

        public int Count { get; set; }

        public string Desc { get; set; }

    }
    public class Member : Drop
    {
        public int ID { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }

        public bool Sex { get; set; }

        public string Relation { get; set; }

        public string Desc { get; set; }
    }

第四步:實現母版編程 。一直感受Asp.net中母版很不錯,Asp.net頁面的對象模型來實際母版和用戶控件也是瓜熟蒂落的事。要用DotLiquid的include標籤來實現母版應該怎麼作呢?這裏說說個人解決方法。先看母版模板文件master.htm

<!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>{{title}}</title>
    <link href="/css/style.css" rel="stylesheet" type="text/css" />

    <script type="text/javascript" src="/js/jquery-1.6.1.min.js"></script>

    {% include content_script_optional %}
</head>
<body>
    <div id="root">
        <div class="top">
            <h2>
                Top in master</h2>
        </div>
        {% include content_main %}
        <div class="bottom">
            <h2>
                Bottom in master
            </h2>
        </div>
    </div>
</body>
</html>

對,關鍵仍是在templateName上,"content_main"和"content_script_optional" 中的"content"和"optional"是兩個約定,還記得IncludeFileSystem.ReadTemplateFile方法吧,就是有點複雜的那個!content指明這裏是包含"內容模板(名詞來自內容窗體)",optional指明這個include能夠沒有,模板裏到這就能夠了,再看ReadTemplateFile方法有個object ns;默認是請求文件名,由於a頁面能夠用master.htm,b頁面也能夠用master.htm,ns默認就是"a"或"b",這樣作只是爲了找到a或b的模板。假如index_content_main.htm首頁模板: 

<div class="center">
    <table class="tb" style="width:400px;">
        <caption>家庭:{{f.Host}}
        <a href="/list.ashx">查當作員</a>
        </caption>
        <tr>
            <th>戶主:</th><td>{{f.Host}}</td>
        </tr>
        <tr>
            <th>地址:</th><td>{{f.Address}}</td>
        </tr>
        <tr>
            <th>家庭成員數:</th><td>{{f.Count}}</td>
        </tr>
        <tr>
            <th>描述:</th><td>{{f.Desc}}</td>
        </tr>
    </table>
</div>

 index.ashx爲訪問接口:

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/plain";
    Template template = TemplateHelper.GetFileTemplate("master");
    string html = template.Render(Hash.FromAnonymousObject(new { title = "dotliquid demo index",f=WebHelper.GetFamily()}));
    context.Response.Write(html);
}
嘿嘿,這裏有點怪吧,代碼裏看不出來和index頁面有半點關係,template是根據master.htm獲得的。這主要歸功於那兩個約定和一個ns默認值,若是把index.ashx更名爲abc.ashx,上面呈現的代碼就要這樣寫了:

string html = template.Render(Hash.FromAnonymousObject(new {ns="index" , title = "dotliquid demo index",f=WebHelper.GetFamily()}));

這幾步到些完結!完整例子會附下載!

 

擴展:還記得代碼圖右邊的幾個主要概念吧,下面是三個例子,我寫的一個,他們的兩個!

public class TemplateFilters
{
    public static string Text(bool? input, string trueText, string falseText, string nullText)
    {
        if (input == null) return nullText;
        return input == true ? trueText : falseText;
    }
    public static string Text(bool input, string trueText, string falseText)
    {
        return input ? trueText : falseText;
    }
}
 這 個是本身的Filters類,裏面的每一個方法均可以是一個Filter,{% true | text: "男" , "女" %} 此值爲"男" ,調用第二個重載,text爲方法名稱(注意大小寫),true是第一個參數input,"男"是第二個參數trueText,"女"是第三個參數 falseText。Filter要註冊纔可用,Template.RegisterFilter(typeof(TemplateFilters)); 這個我也寫在Application_Start。

public class Rd : Tag //隨機數
    {
        int _max;
        public override void Initialize(string tagName, string markup, List<string> tokens)
        {
            base.Initialize(tagName, markup, tokens);
            _max = Convert.ToInt32(markup);
        }
        public override void Render(Context context, StreamWriter result)
        {
            result.Write(new Random().Next(_max).ToString());
        }
    }

    public class Scale : Block//機率出現,用的單詞應該不當!~
    {
        int _max;
        public override void Initialize(string tagName, string markup, List<string> tokens)
        {
            base.Initialize(tagName, markup, tokens);
            _max = Convert.ToInt32(markup);
        }
        public override void Render(Context context, StreamWriter result)
        {
            if (new Random().Next(_max) == 0)
                base.Render(context, result);
        }
    }



仍是要先註冊,Template.RegisterTag<Rd>("rd");Template.RegisterTag<Scale>("scale");

用法分別是  {% rd 10 %} 值爲0到10任意一個數字, {% scale 10 %}這裏有10%的機率出現{% endscale %}。

相關文章
相關標籤/搜索