Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Email

Is their a solution to generate an email template using an ASP.NET MVC View without having to jump through hoops.

Let me elaborate jumping through hoops.

var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse);
var oldContext = HttpContext.Current;
HttpContext.Current = fakeContext;
var html = new HtmlHelper(new ViewContext(fakeControllerContext,
  new FakeView(), viewDataDictionary, new TempDataDictionary()),
  new ViewPage());
html.RenderPartial(viewName, viewData, viewDataDictionary);
HttpContext.Current = oldContext;

The above code is using the current HttpContext to fake a new Context and render the page with RenderPartial, we shouldn't have to do this.

Another very detailed solution using ControllerContext and .Render: (IEmailTemplateService, Headers/Postback WorkAround) but pretty much doing the same thing with a lot more code.

I on the other hand, am looking for something that would just render a View without having to POST/GET and generates me a simple string that I can send off through my Email code. Something that doesn't run into errors such as posting headers twice or faking some piece of data.

EX:

//code which does not fire Render, RenderPartial... etc
var email = emailFramework.Create(viewData, view); 

See my solution bellow or follow this link:

My Solution using spark: (12/30/2009) ASP.NET MVC Email Template Solution

like image 525
Andrew Avatar asked Nov 13 '09 15:11

Andrew


4 Answers

This is what I wanted the ASP.NET MVC ViewEngine to do, but it's in Spark, just follow the latest link right bellow,

Update (12/30/2009) Cleaner Version: ASP.NET MVC Email Template Solution


(11/16/2009) Or, Louis DeJardin Console Application Version:

using System;
using Spark;
using Spark.FileSystem;

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public abstract class EmailView : AbstractSparkView
{
    public User user { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // following are one-time steps

        // create engine
        var settings = new SparkSettings()
            .SetPageBaseType(typeof(EmailView));

        var templates = new InMemoryViewFolder();
        var engine = new SparkViewEngine(settings)
                     {
                         ViewFolder = templates
                     };

        // add templates
        templates.Add("sample.spark", @"Dear ${user.Name}, This is an email.Sincerely, Spark View Engine http://constanto.org/unsubscribe/${user.Id}");

        // following are per-render steps

        // render template
        var descriptor = new SparkViewDescriptor()
            .AddTemplate("sample.spark");

        var view = (EmailView)engine.CreateInstance(descriptor);
        view.user = new User { Id = 655321, Name = "Alex" };
        view.RenderView(Console.Out);
        Console.ReadLine();
    }
}

I decided to use this method because it seems to be the one that does everything right, it:

  • Does not use any HttpContext/ControllerContext or mess with routing data!
  • It can implement Header/Footer to allow templates!
  • You can use loops, conditionals, etc...
  • It's clean, light weight especially if you plan to move entirely to spark view engine!

Please, make sure to read these posts. All credit to Louis DeJardin see his tutorials :): Using Spark as a general purpose template engine!, Email Templates Revisited

like image 113
Andrew Avatar answered Nov 04 '22 22:11

Andrew


Why do you need to create the email from a view? Why not use a plain old template file? I do this all the time - I make a template and use the NVelocity engine from the castle project (not to be confused with an nvelocity VIEW engine) to render the template.

Example:

var nvEngine = new NVelocityEngine();
nvEngine.Context.Add("FullName", fullName);
nvEngine.Context.Add("MallName", voucher.Mall.Name);
nvEngine.Context.Add("ConfirmationCode", voucher.ConfirmationCode);
nvEngine.Context.Add("BasePath", basePath);
nvEngine.Context.Add("TermsLink", termsLink);
nvEngine.Context.Add("LogoFilename", voucher.Mall.LogoFilename);

var htmlTemplate = System.IO.File.ReadAllText(
    Request.MapPath("~/App_Data/Templates/Voucher.html"));

var email = nvEngine.Render(htmlTemplate);

The NVelocityEngine class is a wrapper I wrote around the NVelocity port provided by the Castle project as shown below:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NVelocity;
using NVelocity.App;

namespace MyProgram
{
    /// <summary>
    /// A wrapper for the NVelocity template processor
    /// </summary>
    public class NVelocityEngine : VelocityEngine
    {
        Hashtable context = new Hashtable();

        /// <summary>
        /// A list of values to be merged with the template
        /// </summary>
        public Hashtable Context
        {
            get { return context; }
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public NVelocityEngine()
        {
            base.Init();
        }

        /// <summary>
        /// Renders a template by merging it with the context items
        /// </summary>
        public string Render(string template)
        {
            VelocityContext nvContext;

            nvContext = new VelocityContext(context);
            using (StringWriter writer = new StringWriter())
            {
                this.Evaluate(nvContext, writer, "template", template);
                return writer.ToString();
            }
        }
    }
}

In this way, you don't have to meddle with the view engine at all, and you can theoretically chain this with the ASP.NET view engine if you wanted, like I have done in the following controller method:

public ActionResult ViewVoucher(string e)
{
    e = e.Replace(' ', '+');
    var decryptedEmail = CryptoHelper.Decrypt(e);
    var voucher = Voucher.FindByEmail(decryptedEmail);
    if (voucher == null) return View("Error", new Exception("Voucher not found."));

    var basePath = new Uri(Request.Url, Url.Content("~/")).ToString();
    var termsLink = new Uri(Request.Url, Url.Action("TermsGC", "Legal")).ToString();
    basePath = basePath.Substring(0, basePath.Length - 1);

    var fullName = voucher.FirstName;
    if (!string.IsNullOrEmpty(voucher.LastName))
        fullName += " " + voucher.LastName;

    var nvEngine = new NVelocityEngine();
    nvEngine.Context.Add("FullName", fullName);
    nvEngine.Context.Add("MallName", voucher.Mall.Name);
    nvEngine.Context.Add("ConfirmationCode", voucher.ConfirmationCode);
    nvEngine.Context.Add("BasePath", basePath);
    nvEngine.Context.Add("TermsLink", termsLink);
    nvEngine.Context.Add("LogoFilename", voucher.Mall.LogoFilename);

    var htmlTemplate = System.IO.File.ReadAllText(
        Request.MapPath("~/App_Data/Templates/Voucher.html"));

    return Content(nvEngine.Render(htmlTemplate));
}
like image 33
Chris Avatar answered Nov 04 '22 21:11

Chris


Try using spark view engine (http://www.sparkviewengine.com/). It is easy to use, nicer than standard engine and doesn't require to fake context.

You can also use function from this answer Render a view as a string , but it requires faking context. This is the way standard view engine works and you can do nothing about that.

This is my extension class that is used to generate views to string. First is for standard view engine, second for Spark:

public static class ControllerHelper
{
    /// <summary>Renders a view to string.</summary>
    public static string RenderViewToString(this Controller controller,
                                            string viewName, object viewData)
    {
        //Getting current response
        var response = HttpContext.Current.Response;
        //Flushing
        response.Flush();

        //Finding rendered view
        var view = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName).View;
        //Creating view context
        var viewContext = new ViewContext(controller.ControllerContext, view,
                                          controller.ViewData, controller.TempData);

        //Since RenderView goes straight to HttpContext.Current, we have to filter and cut out our view
        var oldFilter = response.Filter;
        Stream filter = new MemoryStream(); ;
        try
        {
            response.Filter = filter;
            viewContext.View.Render(viewContext, null);
            response.Flush();
            filter.Position = 0;
            var reader = new StreamReader(filter, response.ContentEncoding);
            return reader.ReadToEnd();
        }
        finally
        {
            filter.Dispose();
            response.Filter = oldFilter;
        } 
    }

    /// <summary>Renders a view to string.</summary>
    public static string RenderSparkToString(this Controller controller,
                                            string viewName, object viewData)
    {
        var view = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName).View;
        //Creating view context
        var viewContext = new ViewContext(controller.ControllerContext, view,
                                          controller.ViewData, controller.TempData);

        var sb = new StringBuilder();
        var writer = new StringWriter(sb);

        viewContext.View.Render(viewContext, writer);
        writer.Flush();
        return sb.ToString();
    }
}
like image 7
LukLed Avatar answered Nov 04 '22 21:11

LukLed


If you want simple text replacements, .NET has something for that:

        ListDictionary replacements = new ListDictionary();

        // Replace hard coded values with objects values
        replacements.Add("{USERNAME}", "NewUser");            
        replacements.Add("{SITE_URL}", "http://yourwebsite.com");
        replacements.Add("{SITE_NAME}", "My site's name");

        string FromEmail= "[email protected]";
        string ToEmail = "[email protected]";

        //Create MailDefinition
        MailDefinition md = new MailDefinition();

        //specify the location of template
        md.BodyFileName = "~/Templates/Email/Welcome.txt";
        md.IsBodyHtml = true;
        md.From = FromEmail;
        md.Subject = "Welcome to youwebsite.com ";

        System.Web.UI.Control ctrl = new System.Web.UI.Control { ID = "IDontKnowWhyThisIsRequiredButItWorks" };

        MailMessage message = md.CreateMailMessage(ToEmail , replacements, ctrl);

        //Send the message
        SmtpClient client = new SmtpClient();

        client.Send(message);

And the Welcome.txt file

    Welcome - {SITE_NAME}<br />
    <br />
    Thank you for registering at {SITE_NAME}<br />
    <br />
    Your account is activated and ready to go! <br />
    To login, visit <a href="{SITE_URL}">{SITE_NAME}</a> and use the following credentials:
    <br />
    username: <b>{USERNAME}</b><br />
    password: use the password you registered with
    <br />
    <br />

    - {SITE_NAME} Team

Again, this is only good for simple string replacements. If you plan emailing more data, you would need to format it properly then replace it.

like image 4
Omar Avatar answered Nov 04 '22 22:11

Omar