4

My application is split into three tiers:

  1. View (MVC)
  2. Business logic (Services)
  3. Data (LINQ / SQL)

Throughout my Services layer, I have calls to an EmailSender service that currently sends plaintext emails via SmtpClient and MailMessage. Pretty standard stuff as far as I know.

I'm now trying to format the body of these emails by using a Razor template that accepts a MailMessage as a model, and generates some HTML that I can stick into the body of the message.

Right now this is what I have:

public void SendAsyncEmail(MailMessage message)
        {
            message.IsBodyHtml = true;

            message.body = {generate html string from body here using razor?}

            // Async code here
        }

Say I define a razor view like this:

@model System.Net.Mail.MailMessage

<div>
    <h1>@Model.Subject</h1>
    <p>@Model.Body</p>
</div>

How can I pass the MailMessage into this view from my service method and generate a string from it for the body of the email?

RobVious
  • 12,685
  • 25
  • 99
  • 181

1 Answers1

6

To render a view you'll need to use a view engine and a view context. The view engine is responsible for finding and rendering a view. The view context contains information about the view and its state.

public ActionResult Index()
{
    StringWriter sw = new StringWriter();
    ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, "Index", "_Layout");
    ViewContext context = new ViewContext(ControllerContext, result.View, ViewData, TempData, sw);
    result.View.Render(context, sw);
    result.ViewEngine.ReleaseView(ControllerContext, result.View);

    string viewString = sw.ToString();

    return Content(viewString);
}

To pass a model use ViewData (i.e. property available from the controller).

ViewData.Model = new MailMessage();

I'd also like to highly recommend Postal and MvcMailer as alternatives to your current approach. Both of them work on the same principle of using views (such as razor) to generate emails. The setup is pretty simple and the rewards are worth it.

If your application uses a lot of emails then I'd suggest you try them.

Rowan Freeman
  • 15,724
  • 11
  • 69
  • 100
  • I'm having a bit of trouble with FindView - I tried adding a blank Layout page with RenderBody() but it's not resolving. While I dig into this - I don't like the idea of using Postal or MVCMailer because it's yet-another-external-dependency that might not get maintained past MVC4. That's why I'm trying this handcrafted approach. Thoughts? – RobVious Oct 20 '13 at 22:15
  • I wouldn't worry, personally. Both are mature enough that they can stand on their own for some time and they've been maintained well so far. They're open source, too; if they ever need to be taken over then they probably will. – Rowan Freeman Oct 20 '13 at 22:18
  • Got it, thanks. I'm having a lot of trouble with the suggested code snippet above (ControllerContext not being valid, view names not being recognized, etc); I'm going to poke around MVCMailer as it seems more mature. Can I use it solely to generate the message so I can leave my async code in place? – RobVious Oct 20 '13 at 22:21
  • Shouldn't be a problem with async. Make sure you've got the views in the right place. The view should be in the right view folder and the _Layout (or similar) should be in the Shared view folder. – Rowan Freeman Oct 20 '13 at 22:52
  • Postal has been "unlisted" by the owner and MvcMailer does not support VS2017 and was last updated on 3/17/2013. I have a message into the owner of this to see if he plans to update it. – TechSavvySam Sep 12 '17 at 16:33