views:

3300

answers:

6

I'd like to use my Action in asp.net mvc, as template engine, that results in form of string, that I could send in email.

Pseudo-Code:

public ActionResult Register()
{
    SendEmail(View().ToString());

    return new EmptyResult();
}
+1  A: 

You cannot see the view itself inside of the controller. The way to do what you ask would be to write a custom ActionResult subclass, and a custom ViewEngine to handle it. Override ExecuteResult to actually send the email. Look at the open-source Spark, NHAML, etc., view engines for MVC for examples.

Craig Stuntz
+10  A: 

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....

Will
Somewhy doesn't work for me. Html is empty at the end. :/
Arnis L.
Usually has to do with the stream. You write the html at the stream, then attempt to give that stream to somebody else, they probably won't Seek back to the beginning. Seek 0 and ye shall find your HTML.
Will
Holy crap.... MVC 1 completely ignores the stream and just writes to the response. God DAMN it. http://ayende.com/Blog/archive/2008/11/11/another-asp.net-mvc-bug-rendering-views-to-different-output-source.aspx I wonder if they have fixed this in 2?
Will
+4  A: 

Here's how to get the view as a string.

Tim Scott
this is a really muddled question! i think for most people ending up here this is what they're actaully wanting! this question is actually even specificcaly about getting the view FOR an email inside another controller
Simon_Weaver
+3  A: 

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

Jonathan
A: 

Hey Will, from what I am reading (and my own attempt to implement your solution) the View.Render method completely ignores the writer paramter. The content of my writer is always empty and instead the rendered view gets blurted out in to the response stream. So it seems impossible (or rather convoluted at least) to get your use you views like that. I think in your test case you would see the rendered view twice, and your email would be blank.

I found some discussion on this here: http://ayende.com/Blog/archive/2008/11/11/another-asp.net-mvc-bug-rendering-views-to-different-output-source.aspx

+1  A: 

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();
}
Hightechrider