5

I am building an application which will send out custom emails to end users.

I have constructed an HTML template from which the emails will be built.

I currently have the template filled with tags as placeholders for the custom content...

|Text-Placeholder|

I have been returning the html file as a string to my CreateEMail Method:

string html = System.IO.File.ReadAllText(Server.MapPath("~/EmailTemplates/emailTemplate.html"));

And then using String.Replace to substitute in the custom text/content

html = html.Replace("|Name-Placeholder|", username);

I am curious if there is a method which would allow me to construct the template as a RazorView strongly typed as a ViewModel which will model the custom text/content, and return the view as an HTML file or directly as a string to pass into the body property of my SMTPClient instance for sending to the user?

Has anyone accomplished something like this or similar?

stephen776
  • 9,134
  • 15
  • 74
  • 123
  • possible duplicate of [Render a view as a string](http://stackoverflow.com/questions/483091/render-a-view-as-a-string) – jrummell Jan 13 '12 at 15:44
  • 1
    Comments in suggested duplicate refer to that method not working in MVC 3 and "Better ways to accompish this now" – stephen776 Jan 13 '12 at 15:51

4 Answers4

10

Have a look at these libraries:

There is ActionMailer. Inspired by Ruby's ActionMailer.

ActionMailer.Net aims to be an easy, and relatively painless way to send email from your ASP.NET MVC application. The concept is pretty simple. We render HTML by utilizing some pretty snazzy view engines, so why can't we do the same thing for email?

http://nuget.org/packages/ActionMailer

Supports many view engines I think, Razor included of course. And allows you to pass a model into the view. See: https://bitbucket.org/swaj/actionmailer.net/wiki/Home

The code :

public class MailController : MailerBase
{
    public EmailResult VerificationEmail(User model)
    {
        To.Add(model.EmailAddress);
        From = "no-reply@mycoolsite.com";
        Subject = "Welcome to My Cool Site!";
        return Email("VerificationEmail", model);
    }
}

The view:

@using ActionMailer.Net
@model User
@{
    Layout = null;
}
Welcome to My Cool Site, @Model.FirstName. We need you to verify your email.
Click this nifty link to get verified!

There is also another library:

MvcMailer lets you use your MVC Views to produce stunning emails

http://nuget.org/packages/MvcMailer
https://github.com/smsohan/MvcMailer

gideon
  • 19,329
  • 11
  • 72
  • 113
0

the problem with all the solutions above are that they are not thread safe!.

what if you want to render the view from a different thread? (for e mail)
in that case the ControllerContext is no longer relevant nor the this of the Controller

for those cases i suggest the following solution:

ThreadPool.QueueUserWorkItem(_ =>
{
    var model = new TestMailModel() { Sender = "yakirmanor", Recipient = "yakirmanor@notrealmail.com", Description = "some desc" };
    string test1 = ControllerExtensions.RenderView("Mail", "TestMail", model);
    string test2 = this.RenderView("TestMail", model);
    string test3 = this.RenderView(model);
    // now send the mail
    //MailHelper.SendEmail("yakirmanor@notrealmail.com", "subject", test1, "username", "password");
});

here you can see me calling the TestMail method in the Mail controller and also the TestMail in the current controller.
to create the extansions

public static class ControllerExtensions
{
    public static string RenderView(this Controller controller, object model)
    {
        string viewName = controller.ControllerContext.RouteData.Values["action"].ToString();
        string controllerName = controller.ControllerContext.RouteData.Values["controller"].ToString();
        return RenderView(controllerName, viewName, model);
    }

    public static string RenderView(this Controller controller, string viewName, object model)
    {
        string controllerName = controller.ControllerContext.RouteData.Values["controller"].ToString();
        return RenderView(controllerName, viewName, model);
    }

    public static string RenderView(string controllerName, string viewName, object viewData)
    {
        //Create memory writer
        var writer = new StringWriter();

        var routeData = new RouteData();
        routeData.Values.Add("controller", controllerName);

        //Create fake http context to render the view
        var fakeRequest = new HttpRequest(null, "http://tempuri.org", null);
        var fakeResponse = new HttpResponse(null);
        var fakeContext = new HttpContext(fakeRequest, fakeResponse);
        var fakeControllerContext = new ControllerContext(new HttpContextWrapper(fakeContext), routeData, new FakeController());

        var razorViewEngine = new RazorViewEngine();
        var razorViewResult = razorViewEngine.FindView(fakeControllerContext,viewName,"",false);

        var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
        razorViewResult.View.Render(viewContext, writer);

        return writer.ToString();
    }
}

to use the code you need to add your model and the fake controller

public class TestMailModel
{
    public string Sender { get; set; }
    public string Recipient { get; set; }
    public string Description { get; set; }
}

class FakeController : ControllerBase
{
    protected override void ExecuteCore() { }
}

the view might look like so:

@model Phytech.PhyToWeb.Controllers.TestMailModel
@{
Layout = null;
}
<table cellpadding="8" cellspacing="0" style="width: 100%!important; background: #ffffff; margin: 0; padding: 0" border="0">
    <tbody><tr><td valign="top">
    Hi @Model.Recipient youve got mail from @Model.Sender about @Model.Description
    </td></tr></tbody>
</table>
Yakir Manor
  • 4,687
  • 1
  • 32
  • 25
0

as a small suggestion, instead of placeholders, use resources. you create a resource file, say in your ~/Content directory called EMail.resx and add your tokens to it. in the view you can refer to them like this @EMail.Name and @EMail.Address. the benefit is that if tomorrow you need to send the mail in French, you create an EMail.fr.resx with all the same tokens and you're done

ekkis
  • 9,804
  • 13
  • 55
  • 105
0

you can use this helper in order to strip out the Html from a View. The method accept also a model, the View is programmatically rendered and its output returned as a string. More ore less the same concept of the LoadControl in Asp.net :

public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
{
    var viewEngineResult = ViewEngines.Engines.FindPartialView(context, partialViewName);

    if (viewEngineResult.View != null)
    {
        var stringBuilder = new StringBuilder();
        using (var stringWriter = new StringWriter(stringBuilder))
        {
            using (var output = new HtmlTextWriter(stringWriter))
            {
                ViewContext viewContext = new ViewContext(context, viewEngineResult.View, viewData, tempData, output);
                viewEngineResult.View.Render(viewContext, output);
            }
        }

        return stringBuilder.ToString();
    }

    //return string.Empty;
    throw new FileNotFoundException("The view cannot be found", partialViewName);
}

Here is where the content of the view is retrieved

//The welcome email is in the Shared/Email folder
string partialViewHtml = EmailHelpers.RenderPartialToString(this.ControllerContext, "Email/Welcome", new ViewDataDictionary(modelForPartialView), new TempDataDictionary());
Giorgio Minardi
  • 2,765
  • 1
  • 15
  • 11