Razor View Page as Email Template
Email messages only understand two formats: plain text and HTML. Since Razor is neither, it will need to be processed by some engine, so that it gives you back the generated HTML.
That's exactly what happens when you use Razor in ASP.NET MVC, behind the scenes. The Razor file is compiled into a internal C# class, that gets executed, and the result of the execution is the string content of the HTML, that gets sent to the client.
Your problem is that you want and need that processing to run, only to get the HTML back as a string, instead of being sent to the browser. After that you can do whatever you want with the HTML string, including sending it as an e-mail.
There are several packages that include this power, and I've used Westwind.RazorHosting successfully, but you can also use RazorEngine with similar results. I would prefer RazorHosting for standalone non-web applications, and RazorEngine for web applications
Here is a (sanitized) version of some of my code - I'm using Westwind.RazorHosting to send razor-formatted emails from a windows service, using a strongly typed view.
RazorFolderHostContainer host = = new RazorFolderHostContainer();
host.ReferencedAssemblies.Add("NotificationsManagement.dll");
host.TemplatePath = templatePath;
host.Start();
string output = host.RenderTemplate(template.Filename, model);
MailMessage mm = new MailMessage { Subject = subject, IsBodyHtml = true };
mm.Body = output;
mm.To.Add(email);
var smtpClient = new SmtpClient();
await smtpClient.SendMailAsync(mm);
Razor views as email templates
You can use http://razorengine.codeplex.com/ to achieve this. It allows you to use razor outside of mvc.
string Email = "Hello @Model.Name! Welcome to Razor!";
string EmailBody = Razor.Parse(Email, new { Name = "World" });
It's simple to implement and it's available on http://nuget.codeplex.com/ for easy integration into your projects.
Use localized Razor pages as e-mail templates
I see at least two options for you:
- Use different resource files for different cultures.
- Use different cshtml file for different cultures.
Optoin 1 - Use different resource files for different cultures
Follow these steps:
In API project, register
IStringLocalizerFactory
andIStringLocalizer<>
:services.AddSingleton<IStringLocalizerFactory, ResourceManagerStringLocalizerFactory>();
services.AddScoped(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
services.AddScoped<IRegisterAccountService, RegisterAccountService>();
services.AddScoped<IRazorViewToStringRenderer, RazorViewToStringRenderer>();Create a Resources.Resx file in Razor View Library and set its custom tool to
PublicResXFileCodeGenerator
. Then for each language, create the resource file likeResources.fa-IR.Resx
and clear the custom tool to not generate code for the language files. Then add resource name and value, for example forfa-IR
:Name Value Comment
=========================================
Welcome خوش آمدیدInject string localizer to the views that you want:
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<RazorHtmlEmails.RazorClassLib.SharedResources> SR
In above example, RazorHtmlEmails.RazorClassLib
is namespace of the resource.
Use
SR["resource key in resource file"]
whenever you want to show a string from resource file:@SR["Welcome"]
Add culture as parameter to
RenderViewToStringAsync
ofIRazorViewToStringRenderer
:Task<string> RenderViewToStringAsync<TModel>
(string viewName, TModel model, string culture);Add culture to implementation of
RenderViewToStringAsync
inRazorViewToStringRenderer
:public async Task<string> RenderViewToStringAsync<TModel>
(string viewName, TModel model, string culture)
{
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
...Use it:
string body = await _razorViewToStringRenderer.RenderViewToStringAsync(
"/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml",
confirmAccountModel, "fa-IR");
Option 2 - Use different cshtml file for different cultures
If you don't want to use resource files and you want to have different cshtml files for different cultures, just use naming convention. For example create a template.fa-IR.cshtml
for Persian language and then when rendering, use that view:
string body = await _razorViewToStringRenderer.RenderViewToStringAsync(
"/Views/Emails/ConfirmAccount/ConfirmAccountEmail.fa-IR.cshtml",
confirmAccountModel);
Use Razor views as email templates inside a Web Forms app
You need to get the full path to the file; relative paths will end up being relative to the wrong location.
Write
File.OpenText(Server.MapPath("~/Email/UserDetailsEmail.cshtml"))
How to use razor engine for email templating with image src
To see image everywhere you can use these options:
Absolute Url
You can simply use full absolute path of image for example "http://example.com/images/logo.png"
IMO It is the most simple option and recommended for your problem.
Attachment
As mentioned by Mason in comments You can attach image to mail and then put image tag and useContentId
of attachment:
//(Thanks to Mason for comment and Thanks to Bartosz Kosarzyck for sample code)
string subject = "Subject";
string body = @"<img src=""$CONTENTID$""/> <br/> Some Content";
MailMessage mail = new MailMessage();
mail.From = new MailAddress("from@example.com");
mail.To.Add(new MailAddress("to@example.com"));
mail.Subject = subject;
mail.Body = body;
mail.Priority = MailPriority.Normal;
string contentID = Guid.NewGuid().ToString().Replace("-", "");
body = body.Replace("$CONTENTID$", "cid:" + contentID);
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(body, null, "text/html");
//path of image or stream
LinkedResource imagelink = new LinkedResource(@"C:\Users\R.Aghaei\Desktop\outlook.png", "image/png");
imagelink.ContentId = contentID;
imagelink.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
htmlView.LinkedResources.Add(imagelink);
mail.AlternateViews.Add(htmlView);
SmtpClient client = new SmtpClient();
client.Host = "mail.example.com";
client.Credentials = new NetworkCredential("from@example.com", "password");
client.Send(mail);
Data Uri
you can use data uri (data:image/png;base64,....).
Not Recommended because of weak support in most of mail clients, I tested it with Outlook.com(web)
and OutlookWebAccess(web)
and Office Outlook(Windows)
and Outlook(windows 8.1)
and unfortunately it worked only on OutlookWebAccess(web)
.
MVC Razor: Preview HTML email template (master was not found)
First, try changing Layout to null instead of empty string:
@model string
@{
ViewBag.Title = "Preview Email Template";
Layout = null;
}
@Html.Raw(Model)
For previewing a page in a blank tab or window, you may add target="_blank"
attribute on your form or action link:
@using (Html.BeginForm("Controller", "Action", FormMethod.Post, new { target="_blank" })) { ... }
@Html.ActionLink("Text", "Action", new { controller = "Controller" }, new { target="_blank" })
Also, make sure you're returning proper view name and model contains HTML tags inside controller code.
public ActionResult Preview()
{
String model = "<!DOCTYPE html>....</html>";
// some code here
return View("viewname", model); // viewname is your cshtml file name
}
I assumed you're trying to return raw HTML string as view name, thus "master was not found" error thrown by view engine.
Sending emaills with template MVC using Razor
If you wish there is a helper that i use
public static class HtmlOutputHelper
{
public static string RenderViewToString(ControllerContext context,
string viewPath,
object model = null,
bool partial = false)
{
// first find the ViewEngine for this view
ViewEngineResult viewEngineResult = null;
if (partial)
viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
else
viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
if (viewEngineResult == null)
throw new FileNotFoundException("View cannot be found.");
// get the view and attach the model to view data
var view = viewEngineResult.View;
context.Controller.ViewData.Model = model;
string result = null;
using (var sw = new StringWriter())
{
var ctx = new ViewContext(context, view,
context.Controller.ViewData,
context.Controller.TempData,
sw);
view.Render(ctx, sw);
result = sw.ToString();
}
return result;
}
}
On your controller
var viewModel = new PurchaseVM
{
GuId = new Guid(guidValue),
Name = name,
Address = address,
Phone = phone,
Email = email,
Comments = comments,
Date = DateTime.Now,
CartList = cartList
};
var emailTemplate = "~/Views/Templates/OrderPlaced.cshtml";
var emailOutput = HtmlOutputHelper.RenderViewToString(ControllerContext, emailTemplate, emailModel, false);
Related Topics
Search Xdocument Using Linq Without Knowing the Namespace
Is the Destructor Called If the Constructor Throws an Exception
With C# Use Chrome to Covert HTML to Pdf
Are P/Invoke [In, Out] Attributes Optional for Marshaling Arrays
In C# Wpf, Why Is My Tabcontrol's Selectionchanged Event Firing Too Often
Is C# Really Slower Than Say C++
How to Unset a Specific Bit in an Integer
Load Different CSS File Based on Browser
Error While Using Executenonquery C#
How to Compare Two Objects in Unit Test
How to Get Float Value with SQLdatareader
Linq to Entities Generated SQL
SQL Injections with Replace Single-Quotation and Validate Integers