Send asp.net mvc action result inside email

Solution 1:

First, you'll still probably want to return a view from your action, so returning an EmptyResult isn't the best; but you'll figure that out when you're dealing with the page flow in your registration process.

I'm assuming that you wish to create an HTML email using a View you have already created. That means you wish to take the result of something that looks like the following:

public ActionResult CreateEmailView(RegistrationInformation info)
{
  var userInformation = Membership.CreateNewUserLol(info);
  return View(userInformation)
}

and send that as the body of the email. You get to reuse your views and all that fun stuff.

You can take advantage of the framework by creating a custom ActionResult and using this to generate your text.

Here is some c#-like pseudocode that might actually compile and work. First, the custom ActionResult:

public class StringResult : ViewResult
{
    public string Html { get; set; }
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (string.IsNullOrEmpty(this.ViewName))
        {
            this.ViewName = 
                 context.RouteData.GetRequiredString("action");
        }
        ViewEngineResult result = null;
        if (this.View == null)
        {
            result = this.FindView(context);
            this.View = result.View;
        }
        ViewContext viewContext = new ViewContext(
                context, this.View, this.ViewData, this.TempData);
        using (var stream = new MemoryStream())
        using (var writer = new StreamWriter(stream))
        {
            // used to write to context.HttpContext.Response.Output
            this.View.Render(viewContext, writer);
            writer.Flush();
            Html = Encoding.UTF8.GetString(stream.ToArray());
        }
        if (result != null)
        {
            result.ViewEngine.ReleaseView(context, this.View);
        }
    }
}

This overrides the base method's ExecuteResult (this is the code from the base method I'm overriding; may have changed in RC1) to render to a stream that I control instead of the Output stream of the Response. So it pulls out the text exactly how it would be rendered to the client machine.

Next, how you would use this in a controller action:

public ActionResult CreateEmailView(RegistrationInformation info)
{
  var userInformation = Membership.CreateNewUserLol(info);
  // grab our normal view so we can get some info out of it
  var resultView = View(userInformation);

  // create our string result and configure it
  StringResult sr = new StringResult();
  sr.ViewName = resultView.ViewName;
  sr.MasterName = resultView.MasterName;
  sr.ViewData = userInformation;
  sr.TempData = resultView.TempData;
  // let them eat cake
  sr.ExecuteResult(this.ControllerContext);
  string emailHtml = sr.Html;
  // awesome utils package, dude
  Utils.SendEmailKThx(userInformation, emailHtml);

  return resultView;
}

I'm rendering the same view twice; the first time I render it to a stream and the second time I render it normally. It might be possible to sneak into the call chain of ViewResult somewhere else and change how Render operates, but a cursory glance at the code doesn't reveal anything. While the framework is pretty good, the call stack for parts of the process are just not fine grained enough to make it easy to change a single step in the process. If they broke ExecuteResult into a few different overridable methods, we could have changed it from rendering to the output stream to rendering to our stream without overriding the entire ExecuteResult method. Oh well....

Solution 2:

I don't have enough rep to comment but I thought I'd save people some time by pointing out that it seems like the accepted answer would work and it would be great if it did. However, it seems the WebForm View Engine just eats the TextWriter parameter. There is more info here: http://ayende.com/Blog/archive/2008/11/11/another-asp.net-mvc-bug-rendering-views-to-different-output-source.aspx

Solution 3:

http://www.brightmix.com/blog/renderpartial-to-string-in-asp-net-mvc/ has a good solution for rendering a View to a string so you can send it in email. He notes "rendering a partial view to string has, thankfully, become much, much easier."

/// Static Method to render string - put somewhere of your choosing
public static string RenderPartialToString(string controlName, object viewData)
{
     ViewDataDictionary vd = new ViewDataDictionary(viewData);
     ViewPage vp = new ViewPage { ViewData = vd };
     Control control = vp.LoadControl(controlName);

     vp.Controls.Add(control);

     StringBuilder sb = new StringBuilder();
     using (StringWriter sw = new StringWriter(sb))
     {
         using (HtmlTextWriter tw = new HtmlTextWriter(sw))
         {
             vp.RenderControl(tw);
         }
     }

     return sb.ToString();
}