ASP.NET MVC 5 如何從 Razor 檢視產生 HTML 字串

ASP.NET MVC 框架將應用程式切割為 Model (模型)、View (檢視)以及 Controller (控制器)三個部分,Model 主要用來作資料的存取和業務邏輯的實作,View 的功能為使用者介面的呈現,Controller 則是用來處理 HTTP 的請求及回應。MVC 的核心精神為「關注點分離」(Separation of Concerns),這樣的架構能幫助我們降低程式碼的耦合,讓我們較不會寫出業務邏輯、使用者介面混在一起的義大利麵程式碼(Spaghetti code)。

ASP.NET MVC 使用 Razor 視圖引擎搭配檢視樣板檔案(.cshtml)來動態產生 HTML 網頁回應,Razor 視圖引擎的功能非常強大,可以在樣板中穿插靜態的 HTML 標籤或文字,以及以 Razor 語法、C# 程式語言,所撰寫的使用者介面相關程式邏輯,利如使用「@if」來作條件式渲染,或是使用「@for」、「@foreach」進行迴圈式渲染…等。

Razor 視圖引擎已經被完全地整合在 ASP.NET MVC 框架之中,一般情況下,我們都是在 Controller 的動作方法中,直接呼叫 View() 輔助方法建立並回傳 ViewResult 實體,就能以 Razor 視圖引擎處理樣板,並動態回應 HTML 結果。在這個過程中,我們並不能夠直接存取到 Razor 視圖引擎所動態組成的 HTML 字串。

但有時候我們會希望能夠取得動態組成的 HTML 字串,來作為其他用途,舉例來說,以 HTML 格式寄送 E-mail 時的信件內文。這個情況下,如果沒有樣板引擎的幫助,我們就必須以串接字串的方法,來組成 HTML。

以下的工具函數 RenderViewToString() 能讓我們傳入 ControllerContext 物件、檢視名稱,以及檢視的資料模型物件後,取得 Razor 視圖引擎處理樣板後的 HTML 字串:

using System.IO;
using System.Web.Mvc;
namespace JZLib
{
public static class JZUtil
{
public static string RenderViewToString(ControllerContext controllerContext, string viewName, object viewData = null)
{
using (var writer = new StringWriter())
{
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(controllerContext, viewName, "", false);
var viewContext = new ViewContext(controllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
}
}
view raw JZUtil.cs hosted with ❤ by GitHub

讓我們來看一個簡單的例子,首先,在專案的 Models/ 目錄下,新增名稱為「Order.cs」的 C# 程式碼檔案,其中包含自訂的訂單類別「Order」、訂單明細類別「OrderDetail」,以及用來取得測試訂單資料的工具類別「OderUtil」,檔案內容如下,OrderUtil 的 GetTestOrder() 方法能夠取得測試用的訂單資料:

using System.Collections.Generic;
namespace Models
{
public class Order
{
public int order_id { get; set; }
public string customer_email { get; set; }
public IEnumerable<OrderDetail> details { get; set; }
}
public class OrderDetail
{
public string product_name { get; set; }
public int quantity { get; set; }
}
public static class OrderUtil
{
public static Order GetTestOrder()
{
return new Order
{
order_id = 1,
customer_email = "jason@gms.ndhu.edu.tw",
details = new List<OrderDetail> {
new OrderDetail{ product_name = "basketball", quantity = 100 },
new OrderDetail { product_name = "soccer", quantity = 50 }
}
};
}
}
}
view raw Order.cs hosted with ❤ by GitHub

接著在控制器中撰寫 SendOrderEmail() 動作方法,用來發送包含訂單資料的 E-mail 給訂購顧客,在沒有 Razor 引擎的協助下,必須以字串串接的方式,來動態組成信件內文的 HTML:

[HttpPost]
public ActionResult SendOrderEmail()
{
var order = Models.OrderUtil.GetTestOrder();
string subject = "測試主旨";
string body = $@"訂單編號:{order.order_id}<br>
顧客E-mail:{order.customer_email}<br>
訂單明細:";
body += @"<table border=""1"" cellpadding=""5"">
<tr>
<th>產品</th>
<th>數量</th>
</tr>";
foreach (var detail in order.details)
{
body += $@"<tr>
<td>{detail.product_name}</td>
<td>{detail.quantity}</td>
</tr>";
}
body += "</table>";
using (SmtpClient smtp = new SmtpClient("your.smtp.server"))
{
smtp.Send(new MailMessage("noreply@jzcorp.com", order.customer_email)
{
Subject = subject,
Body = body,
IsBodyHtml = true
});
}
return View("MailSent");
}

可以看得出來,為了結合訂單資料和 HTML 標籤,字串串接的方式讓程式碼略顯複雜,在串接的過程中也很容易出錯,而造成最終產出的 HTML 無法被正確解析。

我們可以改用 RenderViewToString() 工具函數來取得 HTML 字串,首先,在專案的 Views/Shared/ 目錄下,新增一個樣板檔案「_OrderEmailTemplate.cshtml」,並以 @model 指示詞指定檢視的資料模型為自訂的訂單類別「Order」,檔案內容如下:

@model Models.Order
@{
Layout = null;
}
訂單編號:@Model.order_id<br>
顧客E-mail:@Model.customer_email<br>
訂單明細<br>
<table border="1" cellpadding="5">
<tr>
<th>產品</th>
<th>數量</th>
</tr>
@foreach(var detail in Model.details)
{
<tr>
<td>@detail.product_name</td>
<td>@detail.quantity</td>
</tr>
}
</table>
@ViewContext.Controller.ViewBag.remark

Razor 檢視樣板可以輕鬆地結合動態資料以及靜態的 HTML 標籤及文字,除了強型別的資料模型外,也能使用並輸出 ViewBag 的資料,例如第 23 行的「@ViewContext.Controller.ViewBag.remark」,不過特別要注意的是,這裡不能使用一般的「@ViewBag.remark」,否則會取不到 ViewBag 裡的資料。

接著,將控制器中用來發送訂單 E-mail 的動作方法 SendOrderEmail() 修改如下即可,一樣需要注意的是,如果樣板中有用到 ViewBag 的話,必須在呼叫 RenderViewToString() 工具函數前指派 ViewBag 的值(第 8 行處):

[HttpPost]
public ActionResult SendOrderEmail()
{
var order = Models.OrderUtil.GetTestOrder();
string subject = "測試主旨";
ViewBag.remark = "寄送時間:" + DateTime.Now;
string body = JZLib.JZUtil.RenderViewToString(ControllerContext, "_OrderEmailTemplate", order);
using (SmtpClient smtp = new SmtpClient("your.smtp.server"))
{
smtp.Send(new MailMessage("noreply@jzcorp.com", order.customer_email)
{
Subject = subject,
Body = body,
IsBodyHtml = true
});
}
return View("MailSent");
}

使用 Razor 引擎搭配樣板檔案讓動態產生 HTML 字串的過程變得容易許多,去除掉字串串接的部分,也適度地提升了程式碼的可讀性,最重要的是,避免了字串處理時的錯誤,讓產出的 HTML 能夠正確地被解析。最後,範例中 HTML 格式 E-mail 的結果示意圖如下:

如果你的 ASP.NET MVC 專案也有結合資料動態產生 HTML,又難以處理字串串接問題的話,不妨試著使用 Razor 引擎搭配樣版檔案,來簡化產生 HTML 字串的過程!

下載 RenderViewToString() 工具函數範例程式碼

發表留言