多年下來,寫程式發 Email 通知的需求做過 N 回,其中寄給客戶的通知為求美觀常需採用 HTML 格式,而客戶姓名、通知內容等要隨客戶動態改變,嚴格來說也是一種套表。過去我慣用一套自己發明的「特別註記+Replace」做法,例如:

var tmpl = "<span>[$Name$]</span> 您好,您的等侯順位為<span>[$SeqNo$]</span>";
var dict = new Dictionary<string, string>()
{
    ["Name"] = "Jeffrey",
    ["SeqNo"] = "007"
};
var res = System.Text.RegularExpressions.Regex.Replace(
    tmpl, @"\[\$(?<n>.+?)\$\]", m =>
    {
        var n = m.Groups["n"].Value;
        return dict.ContainsKey(n) ? dict[n] : "<" + n + ">";
    });
Console.WriteLine(res);
Console.Read();

土砲做法雖然簡陋,不能 IF ELSE 也沒法跑迴圈,倒也淺顯易懂,就這麼一用十幾年 XD

最近專案又有類似需求打算重操舊業,轉念一想,一帖老方子從 VB6 寫到 C# 6 未免太不長進,該想想有沒有更好的方法。接著馬上有個念頭浮現腦海 - 寫了那麼多 ASP.NET MVC 見識過 CSHTML 的威力,既然信件內文也是 HTML,為什麼不用 Razor 來套版呢?

爬文很快找到順手的兵刃 – RazorEngine,一個允許在任何 .NET 專案(不必是 ASP.NET MVC)使用 Razor 的程式庫:

來個實例,假設我有個中獎通知函要動態改變中獎者姓名、頭銜、獎項內容與日期,若用 Razor 來寫會像這樣(MailTemplate.cshtml):

@model RazorMailTmpl.Models.MailData
 
<!DOCTYPE html>
 
<html>
<head>
    <meta charset="utf-8" />
    <title>Razor Mail Template Demo</title>
    <style>
        li { color: #0000ff; }
        .due { color: orangered; }
    </style>
</head>
<body>
<p>親愛的 @Model.WinnerName @Model.Title,</p>
<p>感謝您參加部落格讀者2017年終摸彩,在此恭喜您獲得以下獎項:</p>
<ul>
    @foreach (var prize in Model.Prizes)
    {
        <li>@prize</li>
    }
</ul>
<p>
    請在 
    <span class="due">@Model.DueDate.ToString("yyyy/MM/dd")</span>
    前連絡謎之聲領取獎項。
</p>
<p>再次恭喜您幸運中獎!</p>
<p>Regards,<br/>黑暗執行緒部落格抽獎小組</p>
</body>
</html>

接著宣告一個 MailData 資料型別,這樣在編輯 cshtml 時才能享受強型別與 Inetllisense 提示:

using System;
 
namespace RazorMailTmpl.Models
{
    public class MailData
    {
        public string WinnerName { get; set; }
        public string Title { get; set; }
        public string[] Prizes { get; set; }
        public DateTime DueDate { get; set; }
 
    }
}

要用 RazorEngine 套表很簡單,先建好 MailData 物件,使用 Engin.Razor.AddTemplate() 載入範本,引擎內建 Cache 機制,接著呼叫 Run 或 Compile 以 Cache Key 取出範本進行編譯運算,很快就能得到套表後的 HTML 字串結果:

using RazorEngine;
using RazorEngine.Templating;
using RazorMailTmpl.Models;
using System;
 
namespace RazorMailTmpl
{
    class Program
    {
        static void Main(string[] args)
        {
            var mailData = new MailData()
            {
                WinnerName = "Jeffrey",
                DueDate = new DateTime(2018, 2, 14),
                Title = "老司機",
                Prizes = new string[]
                {
                    "32G USB 行動碟一支",
                    "Visual Studio 2017 紀念貼紙一組",
                    "法拉帝(Ferretti) 660 豪華遊艇(20米)一艘"
                }
            };
            //將Template存入Cache以利重複使用
            Engine.Razor.AddTemplate(
                "MailBody", // Cache Key
                System.IO.File.ReadAllText("MailTemplate.cshtml"));
            //傳入Cache Key、Model物件型別、Model物件取得套表結果
            var result = 
                Engine.Razor.RunCompile("MailBody", typeof(MailData), mailData);
 
            //除了RunCompile,也可Compile一次,Run多次以提高效能
            Engine.Razor.Compile("MailBody", typeof(MailData));
            Engine.Razor.Run("MailBody", typeof(MailData), mailData);
 
            System.IO.File.WriteAllText("Result.html", result);
        }
    }
}

薑!薑!薑!薑~ 完成。

官方文件的說明挺詳細,相信有 MVC cshtml 經驗的同學很快就能上手,祝大家套表愉快。


Comments

# by 阿翰

驚呆了 一直在想T4模板可以使用razor語法的方法 或許這個方式可以,來嘗試看看

# by Peter

這種方式是不是不支援 @Html.DisplayName 這種 HtmlHelper ?

# by Jeffrey

to Peter, Html 屬性源自 WebViewPage,其型別為 HtmlHelper 又跟 ViewContext、IViewDataContainer 緊密綑綁,硬要在 RazorEngine 使用並非不可能,但得模擬很多 MVC 機制的細節,複雜遠高於其所帶來的便利性,不划算。

# by Alex Lee

這東西 除了 EMail 套版之外 還可以 拿來套用 call 第三方 SOAP 服務時所需的 XML

# by ALEX

此 package 已被標註為 "vulnerabilities detected" https://github.com/advisories/GHSA-ph3v-2hq5-5qfq 針對此漏洞 作者之一的 Matthias Dittrich 有做出說明 (refer : https://github.com/Antaris/RazorEngine/issues/585 ) 此漏洞僅針對以下狀況會受影響 : 1. 使用 IsolatedRazorEngineService 以及 CAS (code access security api) 來控制樣板權限 2. 使用者可以由外部來控制樣板內容

Post a comment