前言:模板引擎渲染HTML的技術有很多,這里僅說明一下利用.net core下的mvc框架里的razor引擎怎么去做模板渲染功能。 在介紹之前,首先感謝磊哥的技術分享,得以讓這篇文章成型。
核心參考文章:
Walkthrough: Creating an HTML Email Template with Razor and Razor Class Libraries
https://github.com/scottsauber/RazorHtmlEmails
a-razor引擎渲染HTML核心代碼:

using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; using System; using System.IO; using System.Linq; using System.Threading.Tasks; namespace MeShop.CRM.Domain.Services.Vip.Helper { // Code from: https://github.com/aspnet/Entropy/blob/master/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs public class RazorViewToStringRenderer : IRazorViewToStringRenderer { private IRazorViewEngine _viewEngine; private ITempDataProvider _tempDataProvider; private IServiceProvider _serviceProvider; public RazorViewToStringRenderer( IRazorViewEngine viewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) { _viewEngine = viewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; } public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model) { var actionContext = GetActionContext(); var view = FindView(actionContext, viewName); using (var output = new StringWriter()) { var viewContext = new ViewContext( actionContext, view, new ViewDataDictionary<TModel>( metadataProvider: new EmptyModelMetadataProvider(), modelState: new ModelStateDictionary()) { Model = model }, new TempDataDictionary( actionContext.HttpContext, _tempDataProvider), output, new HtmlHelperOptions()); await view.RenderAsync(viewContext); return output.ToString(); } } private IView FindView(ActionContext actionContext, string viewName) { var getViewResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true); if (getViewResult.Success) { return getViewResult.View; } var findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: true); if (findViewResult.Success) { return findViewResult.View; } var searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations); var errorMessage = string.Join( Environment.NewLine, new[] { $"Unable to find view '{viewName}'. The following locations were searched:" }.Concat(searchedLocations)); ; throw new InvalidOperationException(errorMessage); } private ActionContext GetActionContext() { var httpContext = new DefaultHttpContext(); httpContext.RequestServices = _serviceProvider; return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); } } public interface IRazorViewToStringRenderer { Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model); } }
b-郵件模板模塊:
需要注意的是:郵件模板模塊,必須放在mvc項目的Views目錄下,主要是利用Razor尋找頁面元素位置的機制(或者可以自定義尋找視圖位置)
TemplateShopExpired實體示例:

namespace MeShop.Module.Model.CRM.EmailTemplate { /// <summary> /// 郵件模板類-店鋪過期 /// </summary> public class TemplateShopExpired { public string ShopHost { get; set; } public string ShopHostAdmin { get; set; } public string ShopSaveDays { get; set; } public string ShopCloseDate { get; set; } public string CdnHost { get; set; } } }
EmailTemplateShopExpired.cshtml文件示例:

@model MeShop.Module.Model.CRM.EmailTemplate.TemplateShopExpired @{ Layout = null; } <!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> <table width="650" border="0" align="center" cellpadding="0" cellspacing="0" style="border: 1px solid #ccc;"> <tbody> <tr> <td valign="top" width="335"> <span class="edit-emai-text" style="font-size:20px;color:#000;font-weight: bold;font-family: arial;display:block;padding:20px 0 20px 20px;"> <span class="edit-email-child">歡迎來到meshop</span> </span> </td> </tr> <tr> <td valign="top" width="335"> <span class="edit-emai-text" style="font-size:15px;color:#000;font-weight:bold;font-family: arial;display:block;padding:20px 20px 15px 20px;"> <span class="edit-email-child">您的店鋪<a href="@(Model.ShopHost)/admin/home">@(Model.ShopHostAdmin)</a>已經到期, 我們將為您保留@ShopSaveDays@天的店鋪配置數據,@(Model.ShopCloseDate)(UTC)后我們將清除店鋪的配置數據,只保留店鋪注冊信息。</span> </span> </td> </tr> <tr> <td valign="top" width="335"> <span class="edit-emai-text" style="font-size:16px;color:#000;font-weight:bold;font-family: arial;display:block;padding:20px 0 15px 20px;"> <a class="edit-emai-link" href="@(Model.ShopHost)/admin/home" title="" style="border:1px solid #ccc;font-size:14px;border-radius: 3px; padding:5px 3px; color:#000; text-decoration:none"><span class="edit-email-child"> 立即續費 </span></a> </span> </td> </tr> </tbody> </table> <TABLE style="PADDING-BOTTOM: 10px; FONT-FAMILY: Arial; MARGIN-BOTTOM: 10px; MARGIN-LEFT: auto; FONT-SIZE: 12px; MARGIN-RIGHT: auto; PADDING-TOP: 15px" border=0 cellSpacing=0 cellPadding=0 width=650 bgColor=#ffffff align=center> <TBODY> <TR> <TD vAlign=center width=650 align=middle> <SPAN class="edit-emai-text" style="PADDING-BOTTOM:15px; DISPLAY: inline-block;vertical-align: middle;"> <img src="@(Model.CdnHost)/admin/favicon.ico" style="display:inline-block;width:40px;height:auto; vertical-align: middle;"> <span class="edit-emai-text" style=" DISPLAY: inline-block;FONT-SIZE:20px;font-weight:bold;">meshop</span> </SPAN> </TD> </TR> </TBODY> </TABLE> </body> </html>
c-使用示例:

/// <summary> /// 3-發送店鋪過期郵件 /// </summary> /// <param name="shopID">店鋪ID</param> /// <param name="times">次數</param> public async Task SendShopExpiredEmail(long shopID, int times) { ShopInfo shopInfo = base.ShopInfoRepository.Get(shopID); if (shopInfo != null) { string shopSsoHost = PayCommonHelper.GetSsoHost(shopInfo.HostAdmin); //生成店鋪過期時間執行SouceValue(店鋪ID+過期時間+次數) string shopExpiredSourceValue = $"{shopID}_{shopInfo.ExpiredTime.ToString_yyyyMMddHHmmss()}_{times}"; TemplateShopExpired templateShopExpired = new TemplateShopExpired { ShopHostAdmin = shopInfo.HostAdmin, ShopHost = shopSsoHost, ShopSaveDays = (shopInfo.BufferTime - shopInfo.ExpiredTime).Days.ToString(), ShopCloseDate = shopInfo.BufferTime.ToString_yyyyMMddHHmmss(), CdnHost = PayCommonHelper.GetCdnHost(this._config) }; string emailTemplateContent = await this._razorViewToStringRenderer.RenderViewToStringAsync(this._emailTmplateShopExpiredRazorPath, templateShopExpired); } }