Razor View Page as Email Template

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:

  1. Use different resource files for different cultures.
  2. Use different cshtml file for different cultures.

Optoin 1 - Use different resource files for different cultures

Follow these steps:

  1. In API project, register IStringLocalizerFactory and IStringLocalizer<>:

     services.AddSingleton<IStringLocalizerFactory, ResourceManagerStringLocalizerFactory>();
    services.AddScoped(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
    services.AddScoped<IRegisterAccountService, RegisterAccountService>();
    services.AddScoped<IRazorViewToStringRenderer, RazorViewToStringRenderer>();
  2. Create a Resources.Resx file in Razor View Library and set its custom tool to PublicResXFileCodeGenerator. Then for each language, create the resource file like Resources.fa-IR.Resx and clear the custom tool to not generate code for the language files. Then add resource name and value, for example for fa-IR:

     Name        Value            Comment
    =========================================
    Welcome خوش آمدید
  3. 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.


  1. Use SR["resource key in resource file"] whenever you want to show a string from resource file:

     @SR["Welcome"]
  2. Add culture as parameter to RenderViewToStringAsync of IRazorViewToStringRenderer:

     Task<string> RenderViewToStringAsync<TModel>
    (string viewName, TModel model, string culture);
  3. Add culture to implementation of RenderViewToStringAsync in RazorViewToStringRenderer:

     public async Task<string> RenderViewToStringAsync<TModel>
    (string viewName, TModel model, string culture)
    {
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
    Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
    ...
  4. 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



Leave a reply



Submit