天天看點

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)

簡述

      Razor是ASP.NET MVC 3中新加入的技術,以作為ASPX引擎的一個新的替代項。在早期的MVC版本中預設使用的是ASPX模闆引擎,Razor在文法上的确不錯,用起來非常友善,簡潔的文法與.NET Framework 結合,廣泛應用于ASP.NET MVC 項目。

      我們在很多項目開發中會常常用到頁面靜态化,頁面靜态化有許多方式,最常見的就是類似很多PHP CMS種使用的 标簽替換的方式(如:帝國CMS、EcShop等),還有很多都是僞靜态,僞靜态我們就不做過多解釋,通過路由或Url重寫來實作就可以了。Razor為我們提供了更加友善的模闆解析方式,任何東西都是兩方面的,技術也是如此,Razor解析模闆雖然更加友善、簡潔,但是對于模闆制作人員來說也是有一定的技術要求,或者對于開發一套模闆制作功能來說,考慮的要更多一些。我們不再去探究這些問題,我們更注重哪種技術更容易、更友善、更好的滿足我們項目的需求。

如何使用RazorEngine

       今天來簡單介紹一下如何使用RazorEngine解析模闆生成靜态頁面,RazorEngine它是基于微軟的Razor之上,包裝而成的一個可以獨立使用的模闆引擎。也就是說,保留了Razor的模闆功能,但是使得Razor脫離于Asp.net MVC,能夠在其它應用環境下使用,項目位址:https://github.com/Antaris/RazorEngine

首先我們去codeplex上下兩個需要的dll http://razorengine.codeplex.com

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)

      看到網上很多介紹RazorEngine的基礎用法的,講解的都比較詳細,對于RazorEngine運作原理很清晰,我們在這裡就不重複介紹了。寫這篇文章是對于很多新手同學來說比較喜歡“拿來主義”,基本的用法原理都能看懂,但是如何應用到項目中還是有些不是很清晰,我們隻講講如何在項目中運用。

本文分為兩部分:第一個部分,基本的單資料模型模闆解析;第二部分,面向接口的多資料模型模闆解析

第一個部分 基本的單資料模型模闆解析

一、我們建立一個MVC項目,并且添加上面的兩個DLL引用,然後我們建立一個簡單的文章類

public class Articles
    {
        /// <summary>
        /// 文章ID
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 文章标題
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 文章内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 作者
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// 釋出時間
        /// </summary>
        public DateTime CreateDate { get; set; }
    }      

二、我們建立一個Razor的Html模闆

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>@Model.Title</title>
</head>
<body>
    <h1>@Model.Title</h1>
    <p>作者:@Model.Author - 釋出時間:@Model.CreateDate</p>
    <p>@Raw(Model.Content)</p>
</body>
</html>      

說明:Model就是我們的文章實體類  在MVC的試圖頁cshtml中 我們一般都是在控制器裡傳遞這個實體類 然後在視圖頁中 @model Models.Articles 來接收這個實體類 然後通過“@Model.”來輸出内容,在Razor模闆中是一樣的,隻是不用@model Models.Articles 來接收了,其它的文法跟在.cshtml試圖頁中是一樣的,這麼說多餘了,因為寫法不一樣他就不是Razor了

三、我們寫一個方法來擷取模闆頁的Html代碼

/// <summary>
        /// 擷取頁面的Html代碼
        /// </summary>
        /// <param name="url">模闆頁面路徑</param>
        /// <param name="encoding">頁面編碼</param>
        /// <returns></returns>
        public string GetHtml(string url, System.Text.Encoding encoding)
        {
            byte[] buf = new WebClient().DownloadData(url);
            if (encoding != null) return encoding.GetString(buf);
            string html = System.Text.Encoding.UTF8.GetString(buf);
            encoding = GetEncoding(html);
            if (encoding == null || encoding == System.Text.Encoding.UTF8) return html;
            return encoding.GetString(buf);
        }

        /// <summary>
        /// 擷取頁面的編碼
        /// </summary>
        /// <param name="html">Html源碼</param>
        /// <returns></returns>
        public System.Text.Encoding GetEncoding(string html)
        {
            string pattern = @"(?i)\bcharset=(?<charset>[-a-zA-Z_0-9]+)";
            string charset = Regex.Match(html, pattern).Groups["charset"].Value;
            try { return System.Text.Encoding.GetEncoding(charset); }
            catch (ArgumentException) { return null; }
        }      

四、我們寫一個方法 用于生成Html靜态頁

/// <summary>
        /// 建立靜态檔案
        /// </summary>
        /// <param name="result">Html代碼</param>
        /// <param name="createpath">生成路徑</param>
        /// <returns></returns>
        public bool CreateFileHtmlByTemp(string result, string createpath)
        {
            if (!string.IsNullOrEmpty(result))
            {
                if (string.IsNullOrEmpty(createpath))
                {
                    createpath = "/default.html";
                }
                string filepath = createpath.Substring(createpath.LastIndexOf(@"\"));
                createpath = createpath.Substring(0, createpath.LastIndexOf(@"\"));
                if (!Directory.Exists(createpath))
                {
                    Directory.CreateDirectory(createpath);
                }
                createpath = createpath + filepath;
                try
                {
                    FileStream fs2 = new FileStream(createpath, FileMode.Create);
                    StreamWriter sw = new StreamWriter(fs2, new System.Text.UTF8Encoding(false));//去除UTF-8 BOM
                    sw.Write(result);
                    sw.Close();
                    fs2.Close();
                    fs2.Dispose();
                    return true;
                }
                catch { return false; }
            }
            return false;
        }      

五、我們來寫個方法調用靜态模闆,并且傳遞資料模型實體類 建立Html靜态頁 

/// <summary>
        /// 解析模闆生成靜态頁
        /// </summary>
        /// <param name="temppath">模闆位址</param>
        /// <param name="path">靜态頁位址</param>
        /// <param name="t">資料模型</param>
        /// <returns></returns>
        public bool CreateStaticPage(string temppath, string path, RazorEngineTemplates.Models.Articles t)
        {
            try
            {
                //擷取模闆Html
                string TemplateContent = GetHtml(temppath, System.Text.Encoding.UTF8);

                //初始化結果
                string result = string.Empty;

                //解析模闆生成靜态頁Html代碼
                result = Razor.Parse(TemplateContent, t);

                //建立靜态檔案
                return CreateFileHtmlByTemp(result, path);
            }
            catch (Exception e)
            {
                throw e;
            }
        }      

好了,大功告成,是不是很簡單。 

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)

這裡隻是一個很簡單的應用,沒有讀取資料,也沒有清單,隻有一個文章資料模型,下一部分我們将介紹 多模型模闆解析,因為是多模型 是以 生成靜态頁面的時候 就不是傳遞一個具體模型實體類 我們會用到 反射,通過反射模型屬性 擷取資料,有不熟悉反射的可以提前研究一下,也可以直接看下一部分的反射代碼也很簡單的。

第二部分 面向接口的多資料模型模闆解析

這一部分,我們介紹使用接口來解析模闆,包括清單等多種模型解析,用到了Spring注入和反射還有接口等,有不熟悉的可以百度搜一下或者評論留言。

我們接着上面的示例,我們建立兩個類庫 一個是存放資料模型的 我們叫Domain;另外一個是接口和實作類的 我們叫Service,然後我們添加他們之間的引用

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)

一、我們在Domain下建立幾個測試類

Articles - 文章測試類

Company - 公司測試類

Column - 欄目測試類

TemplateView - 模型解析類(這個是不是比較弱智?我也沒深入研究多個模型怎麼反射出來 是以 我加了這麼個算是公用的類 沒有對應的資料表 隻是解析模闆的時候 作為中間件用用)

  public class Articles
    {
        /// <summary>
        /// 文章ID
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 文章标題
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 文章内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 作者
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// 釋出時間
        /// </summary>
        public DateTime CreateDate { get; set; }
    }
  public class Company
    {
        /// <summary>
        /// 公司Id
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 公司名稱
        /// </summary>
        public string CompanyName { get; set; }
       /// <summary>
       /// 公司電話
       /// </summary>
        public string CompanyTel { get; set; }
        /// <summary>
        /// 聯系人
        /// </summary>
        public string ContectUser { get; set; }
        /// <summary>
        /// 建立時間
        /// </summary>
        public DateTime CreateDate { get; set; }
    }
   public class Column
    {
       /// <summary>
       /// 欄目ID
       /// </summary>
       public int Id { get; set; }
       /// <summary>
       /// 欄目名稱
       /// </summary>
       public string Title { get; set; }
       /// <summary>
       /// 文章清單
       /// </summary>

       public virtual ICollection<Articles> Articles { get; set; }
    }
   public class TemplateView
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 标題
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 作者
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// 時間
        /// </summary>
        public DateTime CreateDate { get; set; }       
        /// <summary>
        /// 公司名稱
        /// </summary>
        public string CompanyName { get; set; }
        /// <summary>
        /// 公司電話
        /// </summary>
        public string CompanyTel { get; set; }
        /// <summary>
        /// 聯系人
        /// </summary>
        public string ContectUser { get; set; }
        /// <summary>
        /// 文章清單
        /// </summary>
        public virtual ICollection<Articles> Articles { get; set; }
    }      

二、我們在Service下建立一個基礎操作接口以及其實作類(裡面的很多方法 比如:擷取頁面的Html代碼、擷取頁面的編碼以及建立靜态檔案等 是沒有必要寫在接口的 這個可以寫到公用的類庫裡,因為這裡就用到這麼幾個方法 是以我沒有加公用類庫 就直接寫在這裡面了)

/// <summary>
    /// 基礎操作接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IRepository<T> where T : class
    {
        /// <summary>
        /// 解析模闆生成靜态頁
        /// </summary>
        /// <param name="temppath">模闆位址</param>
        /// <param name="path">靜态頁位址</param>
        /// <param name="t">資料模型</param>
        /// <returns></returns>
        bool CreateStaticPage(string temppath, string path, T t);        

        /// <summary>
        /// 擷取頁面的Html代碼
        /// </summary>
        /// <param name="url">模闆頁面路徑</param>
        /// <param name="encoding">頁面編碼</param>
        /// <returns></returns>
        string GetHtml(string url, System.Text.Encoding encoding);

        /// <summary>
        /// 擷取頁面的編碼
        /// </summary>
        /// <param name="html">Html源碼</param>
        /// <returns></returns>
        System.Text.Encoding GetEncoding(string html);

        /// <summary>
        /// 建立靜态檔案
        /// </summary>
        /// <param name="result">Html代碼</param>
        /// <param name="createpath">生成路徑</param>
        /// <returns></returns>
        bool CreateFileHtmlByTemp(string result, string createpath);
    }
/// <summary>
    /// 基礎接口實作類
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class RepositoryBase<T> : IRepository<T> where T : class
    {
        /// <summary>
        /// 解析模闆生成靜态頁
        /// </summary>
        /// <param name="temppath">模闆位址</param>
        /// <param name="path">靜态頁位址</param>
        /// <param name="t">資料模型</param>
        /// <returns></returns>
        public bool CreateStaticPage(string temppath, string path, T t)
        {
            try
            {
                //執行個體化模型
                var Entity = new Domain.TemplateView();

                //擷取模闆Html
                string TemplateContent = GetHtml(temppath, System.Text.Encoding.UTF8);
                //初始化結果
                string result = "";

                //反射指派
                Type typeT = t.GetType();
                Type typeEn = Entity.GetType();

                System.Reflection.PropertyInfo[] propertyinfosT = typeT.GetProperties();

                foreach (System.Reflection.PropertyInfo propertyinfoT in propertyinfosT)
                {
                    System.Reflection.PropertyInfo propertyinfoEn = typeEn.GetProperty(propertyinfoT.Name);
                    if (propertyinfoEn != null && propertyinfoT.GetValue(t, null) != null)
                    {
                        propertyinfoEn.SetValue(Entity, propertyinfoT.GetValue(t, null), null);
                    }
                }

                //很多時候 我們并沒有建立複雜的主外鍵關系 例如欄目下的文章 我們僅僅是在文章表中添加了一個所屬欄目ID的字段
                //并沒有建立關聯 這種情況下 我們直接擷取欄目的時候 是擷取不到文章清單的
                //包括很多自定義的模型和字段 比如 文章的内容 可能不跟文章一個表 而是一個單獨的大資料字段表 這種情況下 我們的
                //TemplateView.Content就需要單獨擷取一下另一個資料模型裡的 這個文章的内容 這種時候 我們可以在這裡重新給他指派

                //如 傳入的模型是 文章
                //if(t is Domain.Articles)
                //{
                //    Entity.Content= 查詢大資料字段表中這篇文章的内容;
                    
                //}

                result = Razor.Parse(TemplateContent, Entity);

                return CreateFileHtmlByTemp(result, path);
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 擷取頁面的Html代碼
        /// </summary>
        /// <param name="url">模闆頁面路徑</param>
        /// <param name="encoding">頁面編碼</param>
        /// <returns></returns>
        public string GetHtml(string url, System.Text.Encoding encoding)
        {
            byte[] buf = new WebClient().DownloadData(url);
            if (encoding != null) return encoding.GetString(buf);
            string html = System.Text.Encoding.UTF8.GetString(buf);
            encoding = GetEncoding(html);
            if (encoding == null || encoding == System.Text.Encoding.UTF8) return html;
            return encoding.GetString(buf);
        }

        /// <summary>
        /// 擷取頁面的編碼
        /// </summary>
        /// <param name="html">Html源碼</param>
        /// <returns></returns>
        public System.Text.Encoding GetEncoding(string html)
        {
            string pattern = @"(?i)\bcharset=(?<charset>[-a-zA-Z_0-9]+)";
            string charset = Regex.Match(html, pattern).Groups["charset"].Value;
            try { return System.Text.Encoding.GetEncoding(charset); }
            catch (ArgumentException) { return null; }
        }

        /// <summary>
        /// 建立靜态檔案
        /// </summary>
        /// <param name="result">Html代碼</param>
        /// <param name="createpath">生成路徑</param>
        /// <returns></returns>
        public bool CreateFileHtmlByTemp(string result, string createpath)
        {
            if (!string.IsNullOrEmpty(result))
            {
                if (string.IsNullOrEmpty(createpath))
                {
                    createpath = "/default.html";
                }
                string filepath = createpath.Substring(createpath.LastIndexOf(@"\"));
                createpath = createpath.Substring(0, createpath.LastIndexOf(@"\"));
                if (!Directory.Exists(createpath))
                {
                    Directory.CreateDirectory(createpath);
                }
                createpath = createpath + filepath;
                try
                {
                    FileStream fs2 = new FileStream(createpath, FileMode.Create);
                    StreamWriter sw = new StreamWriter(fs2, new System.Text.UTF8Encoding(false));//去除UTF-8 BOM
                    sw.Write(result);
                    sw.Close();
                    fs2.Close();
                    fs2.Dispose();
                    return true;
                }
                catch { return false; }
            }
            return false;
        }
    }      

三、我們分别建立 文章管理、公司管理、欄目管理的接口和實作類 并且他們都內建基礎操作

   /// <summary>
    /// 文章管理
    /// </summary>
   public interface IArticleManage:IRepository<Domain.Articles>
    {
    }
    public class ArticleManage:RepositoryBase<Domain.Articles>,IArticleManage
    {
    }

  /// <summary>
    /// 公司管理
    /// </summary>
   public interface ICompanyManage:IRepository<Domain.Company>
    {
    }
  public class CompanyManage:RepositoryBase<Domain.Company>,ICompanyManage
    {
    }

  //欄目管理
   public interface IColumnManage:IRepository<Domain.Column>
    {
    }
  public class ColumnManage:RepositoryBase<Domain.Column>,IColumnManage
    {
    }      

四、注入Xml

<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
  <description>Spring注入Service,容器指向本層層封裝的接口</description>
  <object id="Service.ArticleManage" type="Service.ArticleManage,Service" singleton="false">
  </object>
  <object id="Service.ColumnManage" type="Service.ColumnManage,Service" singleton="false">
  </object>
  <object id="Service.CompanyManage" type="Service.CompanyManage,Service" singleton="false">
  </object>
</objects>      

五、我們分别初始化一個文章類、一個公司類(沒有管理資料表,它下面沒有文章清單 欄目模型我就不初始化了,怎麼輸出清單 大家可以參考下 欄目模闆)

public class HomeController : Controller
    {
        /// <summary>
        /// 聲明一下注入接口
        /// </summary>
        public IArticleManage ArticleManage = Spring.Context.Support.ContextRegistry.GetContext().GetObject("Service.ArticleManage") as IArticleManage;
        public ICompanyManage CompanyManage = Spring.Context.Support.ContextRegistry.GetContext().GetObject("Service.CompanyManage") as ICompanyManage;
        public IColumnManage ColumnManage = Spring.Context.Support.ContextRegistry.GetContext().GetObject("Service.ColumnManage") as IColumnManage;


        public ActionResult Index()
        {
            //初始化一個文章資料模型
            var entityArticle = new Domain.Articles() { Id = 1, Title = "這裡是文章标題", Content = "<span style=\"color:red;\">這裡是文章内容</span>", Author = "張三", CreateDate = DateTime.Now };

            //初始化一個公司資料模型
            var entityCompany = new Domain.Company() { Id = 1, CompanyName = "這裡是公司名稱", CompanyTel = "公司電話", ContectUser = "張三", CreateDate = DateTime.Now };

            //調用方法生成靜态頁面
            ArticleManage.CreateStaticPage(Server.MapPath("/Templates/Temp_article.html"), Server.MapPath("/Pages/news/" + DateTime.Now.ToString("yyyyMMddHHmmss") + "1.html"), entityArticle);
            CompanyManage.CreateStaticPage(Server.MapPath("/Templates/Temp_company.html"), Server.MapPath("/Pages/news/" + DateTime.Now.ToString("yyyyMMddHHmmss") + "2.html"), entityCompany);

            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
       
    }      

六、這是測試的簡單的文章模闆、公司模闆和欄目模闆

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>@Model.Title</title>
</head>
<body>
    <h1>@Model.Title</h1>
    <p>作者:@Model.Author - 釋出時間:@Model.CreateDate</p>
    <p>@Raw(Model.Content)</p>
</body>
</html>      
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <p>公司名稱:@Model.CompanyName</p>
    <p>公司電話:@Model.CompanyTel</p>
    <p>聯系人:@Model.ContectUser</p>
    <p>建立時間:@Model.CreateDate</p>
</body>
</html>      
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <p>欄目标題: @Model.Title</p>
    <p>
        文章清單
        <ul>
            @foreach(var item in @Model.Articles)
            {
            <li>
                <a href="">
                    <span>@item.Title</span>
                    <span>@item.Author</span>
                    <span>@item.CreateDate</span>
                </a>
            </li>
            }
        </ul>
    </p>
</body>
</html>      

我們運作一下,大功告成~~~

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)
ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)
ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)

怎麼排序?怎麼擷取前幾條?怎麼格式化日期時間?怎麼分頁?

這可是Razor啊,這都不需要再多講了吧,簡單一說,如果你傳入資料前沒有事先排序或者擷取前幾條,這些操作要做模闆裡操作 那跟在.cshtml裡基本是一樣的 

@foreach(var item in @Model.ListColumn)
{

  <div >
@if (@item.LinkUrl==null)
    {
       <ul>
@foreach(var article in @item.COM_ARTICLE.Take(15).OrderByDescending(p=>p.UpDateDate))
{

<li>
            <a href="@article.LinkUrl" class="gd-a">
                <div>@article.Title</div></a>
            </li>
}
  </ul>
     }
    else
     {

    }
</div>
}      

應用還是很廣泛的,而且解析代碼相對于标簽替換來說十分簡潔、高效。有時間可以多研究研究,改天有空寫一個模闆替換标簽的供大家參考一下。有人會說那我還得教前台制作Razor文法,這種說法我們沒法去置評,标簽替換你仍然要教他如何使用标簽啊,是以是不是複雜并不是探究的主題,想要前台制作人員更友善的制作一套模闆文法并不是主要因素,比如我們可以做一套友善的模闆制作,使用者點選一下就生成代碼,或者直接做成可視化的,這可能讓我們的程式員要耗費更多的精力,但是一勞永逸,标簽替換方式你仍然要給前台制作人員一套标簽規範和文法,況且背景解析異常的龐大和複雜。

ASP.NET MVC 解析模闆生成靜态頁一(RazorEngine)

還是那句老話,這篇文章僅僅是個人的一些了解和實作,可能中間會出現一些不合理的地方或是錯誤,請大家指正,我們共同學習研究。

Demo是用VS 2013寫的 

下載下傳:第一部分百度網盤   第二部分百度網盤

 原創文章 轉載請尊重勞動成果 http://yuangang.cnblogs.com