上節講了頁面的整個生產流程,大家都期待第三篇,也就是生產的核心內容——Label的替換。說實話,我很有壓力啊:)一個人一個實現思路,所以...可能你不能接受。我的標籤分為2種,一種是組態變數標籤(就是網站和系統的Config),用 %變數名%表示,在初始化Labels之前是要執行替換的。另外一種就是資料調用的Label咯。看下風格://簡單的迴圈列表{Article:List Top="10" CategoryId="5"}<a href ="/details/[field:FileName/]" target="_blank">[field:Title/]</a>{/Article:List}//引用使用者控制項模版,CategoryId是需要傳遞的參數{System:Include TemplateId="5" CategoryId="7"/}//詳情頁模版{Article:Model ArticleId="Url(articleid)"}<h1>[field:Title/]</h1>{/Article:Model}{Artcile:Model name="PostTime" dateformat="yyyy年MM月dd日"/}大家可以看出點端倪了吧,格式都是統一的。我來說下:Article:List:是Article模組下的List標籤Top:調用條數CategoryId:分類ID當然還支援其他的屬性比如Skip,Class,Cache等等,這些屬性關鍵是看List標籤的支援度。下面的<a>...</a>當然是迴圈部分,而[field:FieldName/]則是具體的欄位,接著是關閉標籤。但例如System模組的Include標籤卻沒有內容部分。而詳情頁的欄位展示和列表不同,他的欄位可以任意位置擺放。所以可以下面的那個Model雖沒有ID也可以輸出:) 這些七七八八的細節比較多。我們如何解釋這些標籤代碼呢?其實這些都是文本,依靠文本執行代碼就得靠反射了。所以得反射!是的,Article是程式集(或者是命名空間),而List其實就是個類。List又包含了好多參數,還包含了迴圈體,所以參數其實也是類(Parameter),而迴圈體裡有[field]其實也是類(Field)。呵呵,一切皆是類。那麼,各種的標籤都是類,我們需要抽象出他們的公用部分作為基類,或許還要設計些介面?根據我們提到的所有資訊裡,目前能想到的就是Id,Parameters,Fields,Cache,Html和GetHtml()方法。從上面的標籤裡我們有看到include會給子模版裡的標籤傳參,所以Parameters應該是可變的,Fields也最好可變的,所以數組都不合適。另外迴圈的時候要替換Field,所以Fields最好是索引值對集合(k/v)。Parameters也存成K/V合適嗎?暫時也這麼存吧。每個標籤在網頁裡出現的目的是什嗎?轉換成Html,哪怕他是空(或許是在某些條件下輸出的是空),那麼我們設計成為virtual函數還是抽象成介面呢? 首先說虛函數的意義,就是子類可以去覆蓋,但也可以直接使用,而介面則是必須實現。如果設計成介面,就算不輸出的標籤也要多去實現,那不是很煩。所以暫時我們設計成虛函數,或許我們的決定是錯的。 另外GetHtml感覺名稱不夠準確,因為每個Label都有原始的Html代碼,所以改名為 GetRenderHtml()。
/// <summary> /// Label基類 /// </summary> public class Label { /// <summary> /// ID,一般用於緩衝的Key /// </summary> public string ID { get; set; } /// <summary> /// 原始的HTML代碼 /// </summary> public string Html { get; set; } /// <summary> /// 標籤的參數 /// </summary> public IDictionary<string,Parameter> Parameters { get; set; } /// <summary> /// 標籤的欄位 /// </summary> public IDictionary<string, Field> Fields { get; set; } /// <summary> /// 緩衝 /// </summary> public Cache Cache { get; set; } /// <summary> /// 擷取需要呈現的HTML /// </summary> /// <returns></returns> public virtual string GetRenderHtml() { return string.Empty; } }大家是否覺得Parameters和Fields很難看呢?因為關於他們的操作(擷取某個parameter,刪除,增加,枚舉等)還很多,所以應該單獨封裝,而且萬一哪天發現IDictionary不合適,所以封裝是合適的。所以改成了,public ParameterCollection Parameters { get; set; }public FieldCollection Fields { get; set; }那麼怎麼在頁面裡發現這些Label,並執行個體化他們呢? 當然是強大的正則了。{((?<a>\w+):(?<c>\w+))(?<p>[^}]*)((/})|(}(?<t>(?>(?<o>{\1[^}]*})|(?<-o>{/\1})|(?:(?!{/?\1)[\s\S]))*)(?(o)(?!)){/\1}))懂正則的朋友我想說:你懂的:)。字串被分為了4個組分別是assembly,class,parameters,template。而Label的ParameterCollection和FiledCollection則需要從<parameters>組和<template>組再次使用正則擷取。Parameter的正則:(?<name>\w+)=(?<value>("([^"]+)")|('[^']+')|([^\s\}]+))Field的正則:\[field:(?<name>[\w\.]+)(?<parameters>[^]]+)?/\]我說下嵌套的實現思路:1、遞迴Template找到所有的Label,被嵌套的必須有ID號2、當替換外層Label每行資料時,需要把當前行的資料DataItem傳遞給裡層的Label,裡層的Label執行個體可以通過FindLabel(id)來找到。是不是覺得有點像Repeater啊?哈哈。3、外層Label的Template是需要Replace掉內層Label的Html的。不然Field就亂了。說了這麼多不如看代碼明白,那就建立個LabelFactory類,負責Label的生產。
public class LabelFactory { /// <summary> /// 匹配Label的正則 /// </summary> private static readonly Regex LabelRegex = new Regex(@"{((?<a>\w+):(?<c>\w+))(?<p>[^}]*)((/})|(}(?<t>(?>(?<o>{\1[^}]*})|(?<-o>{/\1})|(?:(?!{/?\1)[\s\S]))*)(?(o)(?!)){/\1}))"); /// <summary> /// 根據模版擷取其包含的所有Label /// </summary> /// <param name="template">模版</param> /// <param name="preInit">Label初始化前需要的工作</param> /// <returns></returns> public static IList<Label> Find(string template, Action<Label> preInit) { var ms = LabelRegex.Matches(template); if (ms.Count == 0) return null; var list = new List<Label>(); foreach (Match m in ms) { var label = Create(m.Groups[0].Value, m.Groups["a"].Value, m.Groups["c"].Value, m.Groups["p"].Value, m.Groups["t"].Value); //訂閱事件 if (preInit != null) { label.PreInit += preInit; } //尋找Label的子Label,如果存在則會替換Label的TemplateString var labels = Find(label.TemplateString); if (labels != null) { label.TemplateString = label.TemplateString.Replace(labels[0].TemplateString, string.Empty); } //label.Init(); list.Add(label); if (labels != null) list.AddRange(labels); } return list; } /// <summary> /// 重載上面的Find,一般情況下使用該方法,除非需要特殊處理某些標籤 /// </summary> /// <param name="template"></param> /// <returns></returns> public static IList<Label> Find(string template) { return Find(template, null); } /// <summary> /// 反射建立一個Label /// </summary> /// <param name="template">標籤的原始HTML,用於替換使用</param> /// <param name="a">程式集名稱</param> /// <param name="c">標籤類名稱</param> /// <param name="p">標籤參數</param> /// <param name="t">標籤的模版</param> /// <returns></returns> private static Label Create(string template, string a, string c, string p, string t) { var assembly = Assembly.Load(a); var label = assembly.CreateInstance(c, true) as Label; label.Html = template; label.TemplateString = t; label.ParameterString = p; return label; } }這代碼只是比較簡單的,異常肯定是有的,我只是寫思路:)
細心的朋友會發現Label又增加了些新內容,是的,這是在設計過程中的填充和修改。沒有人一開始就考慮的十分周全,這是一個正常的設計過程。看看Label的改動,增加了幾個屬性,一個preinit事件,和一個初始化方法init給定一段html代碼,裡面會包含若干個label,所以find會返回一個list,另外我們還需要一個Create方法類反射每一個label。在執行個體化一個label後,還需要繼續看這個label是否嵌套了label,所以要對該label的template繼續find,如此遞迴。。如果能找到label,則把父親的template裡最先發的label的template替換掉。不然初始化Fields的時候會出問題。
為什麼設計了一個事件?
因為Include標籤是需要傳參給裡面的label的,所以在label初始化之前可能會改動label的parameterString和templateString:) 希望您能理解。
/// <summary> /// 原始的HTML代碼 /// </summary> public string Html { get; set; } /// <summary> /// Label的Parameter字串 /// </summary> public string ParameterString { get; set; } /// <summary> /// Label的模版 /// </summary> public string TemplateString { get; set; } /// <summary> /// 初始化之前的事件 /// </summary> public event Action<Label> PreInit; /// <summary> /// 初始化Label /// </summary> public virtual void Init() { if (PreInit != null) { PreInit(this); } //初始化所有參數 Parameters = new ParameterCollection(ParameterString); //初始化所有欄位 Fields = new FieldCollection(TemplateString); }
好了,寫了太久了,大家和我都消化消化,休息下:)後面繼續講Parameters和Fields的設計。