Шаблоны e-mail сообщений в ASP.NET

16.11.2009 17:19 / Артём Волк / 1159 просмотров / ...

Задача подготовки е-мейл сообщений на основе шаблонов в ASP.NET оказывается нетривиальной. Среди вариантов решения: использование стороннего шаблонизатора, использование XSLT. Но, если ASP.NET — тоже шаблонизатор, пусть и весьма специфический, почему бы не поручить работу по подготовке е-мейлов ему?

Пример будет рассматривать генерацию e-mail сообщений в формате HTML, но вполне подойдёт и для plain-text сообщений.

Идея состоит в следующем: в качестве шаблона использовать Web User Control, который затем будет рендерится в строку. Для реализации необходимы следующие компоненты:

User Control

Контрол содержит шаблон сообщения, внутри можно использовать обычные контролы, например, Repeater'ы, а в code-behind — привычные методы доступа к данным.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="OrderConfirmation.ascx.cs" Inherits="MailTemplates_OrderConfirmation" %>
<style>
* 
	{
	font-family: Arial, Sans-Serif;
	}
</style>

<p>Добрый день!</p>

<p>Спасибо за ваш заказ на сайте <a href="http://example.com">example.com</a>. Номер вашего заказа: <%=OrderId %></p>

<asp:Repeater runat="server" ID="rCart">
<HeaderTemplate>
		<table cellpadding="5" cellspacing="0" border="1" summary="Товары">  
			<tr>
				<td>
					<strong>Наименование</strong>
				</td>
				<td>
					<strong>Количество</strong>
				</td>
				<td>
					<strong>Цена,&nbsp;грн</strong>
				</td>
				<td>
					<strong>Сумма,&nbsp;грн</strong>
				</td>
			</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
	<td>
		<%# ((GoodCustom)Container.DataItem).Producer.ProducerName %>
		<%# ((GoodCustom)Container.DataItem).GoodName  %></td>
	<td align="center">
			<%# Cart.GetOne(((GoodCustom)Container.DataItem).NGood).Quantity %>
	</td>
	<td align="right">
		<%# Cart.GetOne(((GoodCustom)Container.DataItem).NGood).Price.ToString("N") %>
	</td>
	<td align="right">
		<%# (Cart.GetOne(((GoodCustom)Container.DataItem).NGood).Quantity * Cart.GetOne(((GoodCustom)Container.DataItem).NGood).Price).ToString("N") %>
	</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>

<p>Итого: <strong><asp:Literal ID="ltTotal" runat="server"></asp:Literal></strong>&nbsp;грн</p>

<hr />
<p>
ПОЖАЛУЙСТА, НЕ ОТВЕЧАЙТЕ НА ЭТО ПИСЬМО, ОНО СГЕНЕРИРОВАНО АВТОМАТИЧЕСКИ
</p>

Метод, отвечающий за рендеринг

Идея подсмотрена в одном хорошем блоге и состоит в создании фиктивного класса страницы.

public static string ProcessControl(string path, Dictionary<string, object> parameters)
{
	Page page = new Page();
	Control myControl = page.LoadControl(path);
	StringBuilder sb = new StringBuilder();
	Type type = myControl.GetType();

	PropertyInfo[] properties = type.GetProperties();

	foreach (KeyValuePair<string, object> kvp in parameters)
	{
		foreach (PropertyInfo property in properties)
		{
			if (kvp.Key == property.Name)
			{
				property.SetValue(myControl, kvp.Value, null);
			}
		}
	}

	page.Controls.Add(myControl);

	using (StringWriter sw = new StringWriter(sb))
	{
		page.Server.Execute(page, sw, false);
	}

	return sb.ToString();
}

Генерация е-мейла

string emailBody = UtilsTemplate.ProcessControl("~/MailTemplates/OrderConfirmation.ascx", new Dictionary<string, object> { { "OrderId", orderId } });

Недостатком способа является то, что параметры контрола передаются в виде нетипизированного Dictionary.