E-Mails must often be customized - and to do it well, we need to be able to place both values as well as complex logic (like tables with sums) into the e-mail. Let's not be DAFT - this can be done using Razor templates!
The Basics of Razor Mail Templates
Basically your code will
- instantiate a razor-engine - which will compile prepare your razor for your code
- run the razor together with the data you provide it - typically dynamic objects, dictionaries or something - and return a string with the result
- send the resulting string as an e-mail
Templating HTML Message
To make templating easy, it's best to use Razor with HTML, just like you would create 2sxc-views or MVC-views. This is best done using a helper, which is like a function, but you can also write HTML. Here's an example from the Mobius Forms App:
Example of Razor Helper Generating Mail
@helper Message(Dictionary<string,object> request, dynamic context)
{
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body { font-family: Helvetica, sans-serif; }
</style>
</head>
<body>
<h1>Website contact form request</h1>
<table width="100%">
@foreach (var item in request)
{
<tr>
<td width="10%"><b>@item.Key</b></td>
<td>@item.Value</td>
</tr>
}
</table>
</body>
</html>
}
You can get the code from the app-download, github or read the blog.
Templating the Subject
Most subjects are just a standard piece of text (not needing any code), but it's still nice to run it through code, just in case you want to add more logic. Since we don't need any HTML or line breaks, we should use a function and not a helper. Here's the example from the Mobius Forms App:
Example of Trivial Subject Function
@functions {
public string Subject(dynamic request, dynamic context) {
return context.Content.OwnerMailSubject.ToString();
}
}
Connecting the Dots
So let's see the full recommendation. Here's one of the mail templates in the the Mobius Forms App. This example expects the caller (the WebApi) to pass the main data-object request and a context object helpers so the template can do it's job. Here's _Email customized.html:
Full _Mail to owner.cshtml
@helper Message(Dictionary<string,object> request, dynamic helpers)
{
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body { font-family: Helvetica, sans-serif; }
</style>
</head>
<body>
<h1>Website contact form request</h1>
<table width="100%">
@foreach (var item in request)
{
<tr>
<td width="10%"><b>@item.Key</b></td>
<td>@item.Value</td>
</tr>
}
</table>
</body>
</html>
}
@functions {
public string Subject(dynamic request, dynamic helpers) {
// create custom code to generate the subject here...
// or just return the setting configured in the form
return !String.IsNullOrWhiteSpace(helpers.Content.OwnerMailSubject)
? helpers.Content.OwnerMailSubject.ToString()
: helpers.App.Settings.OwnerMailSubject.ToString();
}
}
And here's the code exerpt from the api/FormController.cs WebApi, which calls up the rendering code:
Extract from the api/FormController.cs
using ...;
public class FormController : SxcApiController
{
[HttpPost]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.Anonymous)]
[ValidateAntiForgeryToken]
public void ProcessForm([FromBody]Dictionary<string,object> contactFormRequest)
{
// ...
// 3. Send Mail to owner
// uses the DNN command: http://www.dnnsoftware.com/dnn-api/html/886d0ac8-45e8-6472-455a-a7adced60ada.htm
var ownerMailEngine = TemplateInstance(config.OwnerMailTemplate);
var ownerBody = ownerMailEngine.Message(valuesWithMailLabels, this).ToString();
var ownerSubj = ownerMailEngine.Subject(valuesWithMailLabels, this);
// ...
}
private dynamic TemplateInstance(string fileName)
{
var compiledType = BuildManager.GetCompiledType(System.IO.Path.Combine("~", App.Path, "email-templates", fileName));
object objectValue = null;
if (compiledType != null)
{
objectValue = RuntimeHelpers.GetObjectValue(Activator.CreateInstance(compiledType));
return ((dynamic)objectValue);
}
throw new Exception("Error while creating mail template instance.");
}
}
TL;DR
In the name of Don't be DAFT we really believe that standard technologies should be used by techies to solve advanced problems, and that this is better than trying to provide an abstraction which will always be missing just-that-one-feature.
We hope you enjoy it!
Daniel (iJungleboy)
PS: you can always get the full code example from the Mobius Forms App (download, check Github or read blog).