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