I am using the Razor engine https://github.com/Antaris/RazorEngine to parse the body of my email templates. Is it possible to define a layout and include other .cshtml files? for example a common header and a footer.
I got common templates and a layout working, with the help of these two posts:
RazorEngine string layouts and sections?
This is my solution:
Solution 1: Layout
Used by setting _Layout
@{
_Layout = "Layout.cshtml";
ViewBag.Title = Model.Title;
}
Footer
@section Footer
{
@RenderPart("Footer.cshtml")
}
Layout.cshtml
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>
<head>
</head>
<body>
<div id="content">
@RenderBody()
</div>
@if (IsSectionDefined("Footer"))
{
<div id="footer">
@RenderSection("Footer")
</div>
}
</body>
</html>
TemplateBaseExtensions
Extend TemplateBase with a RenderPart Method
public abstract class TemplateBaseExtensions<T> : TemplateBase<T>
{
public string RenderPart(string templateName, object model = null)
{
string path = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates", templateName);
return Razor.Parse(File.ReadAllText(path), model);
}
}
Razor Config
Set BaseTemplateType to your TemplateBaseExtensions class
TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration
{
BaseTemplateType = typeof(TemplateBaseExtensions<>)
};
Razor.SetTemplateService(new TemplateService(templateConfig));
Edit Solution 2:
If you are using a TemplateResolver. RenderPart isn't needed use the @Include instead
Footer
@section Footer
{
@Include("Footer.cshtml")
}
Resolver
public class TemplateResolver : ITemplateResolver
{
public string Resolve(string name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
string path = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates", name);
return File.ReadAllText(path, System.Text.Encoding.Default);
}
}
Config
TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration
{
Resolver = new TemplateResolver()
};
Razor.SetTemplateService(new TemplateService(templateConfig));
Update by The Muffin Man Specify a template and render a string
var templateResolver = Razor.Resolve("Registration.cshtml");
return templateResolver.Run(new ExecuteContext());
Also I, along with others at this link https://github.com/Antaris/RazorEngine/issues/61 had issues with using _Layout
whereas Layout
worked.
'_Layout' is the old syntax. It was updated to 'Layout' in a future release.
ITemplateResolver
was the ticket. Couldn't get anything to go otherwise. Ended up writing a custom pathing handler to level out any quirks when searching for views. –
Bryson You can easily do a lot of stuff with Razor; however, that particular project seems to abstract away a lot of the Razor engine stuff you could do (which is both good and bad). In your situation, it sounds like you would be much better off implementing your own Razor solution (it's actually not that bad) and then you can have your templates throw exceptions or pull in other content pretty easily.
For example; rolling your own solution allows you to make a base class for your razor templates which can expose the capacity to pull in "partial views" by invoking other templates. In addition, you can do model checking and throw exceptions if certain properties are null.
The easiest way to implement a layout with RazorEngine is by replacing what your template return in the @RenderBody() of the layout:
var finalHtml = layout.Replace(@"@RenderBody()", templateHtml);
E.g.:
Your _Layout.cshtml with the typical @RenderBody()
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>
<head>
</head>
<body>
<div>
@RenderBody()
</div>
</body>
</html>
Your RazorEngine template MyTemplate.cshtml
@using RazorEngine.Templating
@inherits TemplateBase<myviewmodel>
<h1>Hello People</h1>
<p>@Model</p>
And wherever you call the RazorEngine template:
var TemplateFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates");
var template = File.ReadAllText(Path.Combine(TemplateFolderPath,"MyTemplate.cshtml"));
var layout = File.ReadAllText(Path.Combine(TemplateFolderPath, "_Layout.cshtml"));
var templateService = new TemplateService();
var templateHtml = templateService.Parse(template, myModel, null, null);
var finalHtml = layout.Replace(@"@RenderBody()", templateHtml);
Fully custom solution of implementing Mail functionality.
Add nuget packet of RazorEngine
Create _Layout Template(.cshtml) :
<html>
<body style="margin: 0; padding: 0;">
<div style="width:100%; display:block; float:left; height:100%;">
<table cellpadding="0" cellspacing="0" border="0" align="center" width="100%">
<tr>
<td width="37" style="background-color: #ffffff;"></td>
<td width="200" style="background-color: #ffffff">
<a href="@Url("")">Send Mail Logo</a>
</td>
<td style="background-color: #ffffff;">
</td>
<td width="126" style="background-color: #ffffff;">
<img src="@Url("Images/mail/social-media.png")" alt="" width="126" height="73" border="0" usemap="#Map" />
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" align="center" width="100%">
<tr height="7">
<td style="background-color: #ffffff;" colspan="3"></td>
</tr>
<tr height="54">
<td colspan="3"></td>
</tr>
<tr>
<td width="37"> </td>
<td style="font-family: Myriad, 'Helvetica Neue',Arial,Helvetica,sans-serif; font-size: 11pt; color: #6b6c6f; line-height: 24px;">
{{BODY}}
</td>
<td width="37"> </td>
</tr>
<tr height="55">
<td style="line-height: 0;" colspan="3"> </td>
</tr>
<tr height="11">
<td background="@Url("/Images/mail/dotted-line.png")" colspan="3" style="line-height: 0;"> </td>
</tr>
</table>
</div>
<map name="Map" id="Map">
<area shape="rect" coords="28,29,51,51" href="#" alt="Twitter" />
<area shape="rect" coords="56,28,78,52" href="#" alt="Google+" />
<area shape="rect" coords="84,28,104,51" href="#" alt="LinkedIn" />
</map>
</body>
</html>
Create ConfirmEmail Template (.cshtml) :
@using yourProjectnamespace.LanguageResources.Mail
@model ConfirmEmail
@MailTemplateResource.YouHaveLoggedIn
<a href="@Url(string.Format("/User/Confirmemail?EmailId={0}", Model.UserId))">@MailTemplateResource.ClickHere</a>
Create CustomTemplateBase class :
public class CustomTemplateBase<T> : TemplateBase<T>
{
public string Url(string url)
{
return MailConfiguration.BaseUrl + url.TrimStart('/');
}
}
Create EmbeddedTemplateManager class :
internal class EmbeddedTemplateManager : ITemplateManager { private readonly string ns;
public EmbeddedTemplateManager(string @namespace)
{
ns = @namespace;
}
public ITemplateSource Resolve(ITemplateKey key)
{
var resourceName = $"{ns}.{key.Name}.cshtml";
string content;
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
using (var streamReader = new StreamReader(stream))
{
content = streamReader.ReadToEnd();
}
return new LoadedTemplateSource(content);
}
public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)
{
return new NameOnlyTemplateKey(name, resolveType, context);
}
public void AddDynamic(ITemplateKey key, ITemplateSource source)
{
throw new NotImplementedException("");
}
}
Create Mail Class :
public class Mail
{
private static readonly IRazorEngineService RazorEngine;
static Mail()
{
var config = new TemplateServiceConfiguration
{
BaseTemplateType = typeof(CustomTemplateBase<>),
TemplateManager = new EmbeddedTemplateManager(typeof(Mail).Namespace + ".Templates"),
Namespaces = { "Add CurrentProjectName", "Add CurrentProjectName .Models" },
CachingProvider = new DefaultCachingProvider()
};
RazorEngine = RazorEngineService.Create(config);
}
public Mail(string templateName)
{
TemplateName = templateName;
ViewBag = new DynamicViewBag();
}
public string TemplateName { get; set; }
public object Model { get; set; }
public DynamicViewBag ViewBag { get; set; }
public string GenerateBody()
{
var layout = RazorEngine.RunCompile("_Layout", model: null);
var body = RazorEngine.RunCompile(TemplateName, Model.GetType(), Model);
return layout.Replace("{{BODY}}", body);
}
public MailMessage Send(Guid key, string to, string subject, string cc = null)
{
var email = new MailMessage()
{
From = MailConfiguration.From,
Body = GenerateBody(),
IsBodyHtml = true,
Subject = subject,
BodyEncoding = Encoding.UTF8
};
email.Headers.Add("X-MC-Metadata", "{ \"key\": \"" + key.ToString("N") + "\" }");
foreach (var sendTo in to.Split(' ', ',', ';'))
{
email.To.Add(sendTo);
}
if (cc != null)
{
foreach (var sendCC in cc.Split(' ', ',', ';'))
{
email.CC.Add(sendCC);
}
}
var smtp = new MailClient().SmtpClient;
smtp.EnableSsl = true;
smtp.Send(email);
return email;
}
}
public class Mail<TModel> : Mail where TModel : class
{
public Mail(string templateName, TModel mailModel) : base(templateName)
{
Model = mailModel;
}
}
Create MailClient Class :
public class MailClient
{
public MailClient()
{
SmtpClient = new SmtpClient(MailConfiguration.Host)
{
Port = MailConfiguration.Port,
Credentials = new NetworkCredential
{
UserName = MailConfiguration.UserName,
Password = MailConfiguration.Password
}
};
}
public SmtpClient SmtpClient { get; }
}
Create MailConfiguration Class :
public class MailConfiguration
{
private static string GetAppSetting(string key)
{
var element = ConfigurationManager.AppSettings["Mail:" + key];
return element ?? string.Empty;
}
public static string BaseUrl => GetAppSetting("BaseUrl");
public static string Host => GetAppSetting("Host");
public static int Port => Int32.Parse(GetAppSetting("Port"));
public static string UserName => GetAppSetting("Username");
public static string Password => GetAppSetting("Password");
public static MailAddress From => new MailAddress(GetAppSetting("From"));
}
MailSender Class :
Implement your method in MailSerder class and call MailSerder method in your repository or Controller.
Create public class MailSender : IMailSender
{
public MailSender()
{
}
public void SendConfirmEmail(string emailId, Guid userId)
{
var confirmEmail = new ConfirmEmail
{
UserId = userId
};
ConfirmEmail(emailId, MailResource.YourRegistration, confirmEmail);
}
private void ConfirmEmail(string recipient,string subject,ConfirmEmail model)
{
var key = Guid.NewGuid();
var mail = new Mail<ConfirmEmail>("ConfirmEmail", model);
mail.ViewBag.AddValue("Recipient", recipient);
var sentMail = mail.Send(key, recipient, subject);
}
}
© 2022 - 2024 — McMap. All rights reserved.