Page 1 of 2 12 LastLast
Results 1 to 10 of 14
  1. #1
    Join Date
    Feb 2014
    Posts
    277

    Mvc Mailer replacement

    Since we now have Visual Studio 2015 it's probably worth looking for a replacement for the Mvc-mailer nuget package which is used to send e-mails in the mmo website videos.

    I have been looking at a nuget package called Fluent Email. Here are 3 links to his websites and the nuget website.
    https://www.nuget.org/packages/fluent-email
    https://github.com/lukencode/FluentEmail
    http://lukencode.com/2011/04/30/flue...for-templates/

    Anyone any comments or can recommend a different product?

  2. #2
    Join Date
    Aug 2010
    Posts
    158
    I was going to say we could just use the System.Net.Mail built in mailer. The project you linked above is just a wrapper build around that anyways.

    It is very easy to render a CSHTML file with a model passed in to an HTML string. We should be able to take that and set it to the body of the message to have email templates without the use of another project that is just as complicated.

    Maybe tonight I will hammer out a quick proof of concept to see if it is valid.

  3. #3
    Join Date
    Aug 2010
    Posts
    158
    So I just threw this together really quickly. It is an aggregation of a few other peoples work with some small edits. I can send templated emails with master layouts using MVC Razer views with models without using a separate library with only 2 real methods.

    This could be easily extended to use a custom ViewDataDictionary instead of a model, but I like strongly typed ViewModels for views instead of a ViewBag or ViewDataDictionary

    To be a drop in replacement for our project it would need to be slightly wrapped and have a fake controller context to render the view with.

    Either way this is was just hacked together in 5 minutes to show a proof of concept. I don't think we need to use another library or nuget package with how simple it is to do this.


    These are the two methods and the action that is calling them. This will only work on a controller as we need a controller context. There are ways to fake this by dynamically creating a controller context but this is just super simple.
    Code:
    // CONTROLLER ACTION THAT IS CALLING THE 2 METHODS BELOW
    public async Task<ActionResult> Contact()
            {
                ViewBag.Message = "Your contact page.";
    
                // Create an instance of an email
                var email = new Email
                {
                    FromEmail = "DoNotReply@*****.com",
                    FromName = "BuzzMMO Website",
    
                    ToEmail = "user@gmail.com",
    
                    Subject = "This is a test",
                    Message = "This message is just a test"
                };
                
                // Set the email message (body) to the HTML string of the view
                email.Message = RenderViewToString(ControllerContext, "TestEmail", email);
    
                // Send the email
                await SendEmailAsync(email);
    
                return View();
            }
    
    private async Task SendEmailAsync(Email email)
            {
                var message = new MailMessage();
                
                message.To.Add(new MailAddress(email.ToEmail));
                message.From = new MailAddress(email.FromEmail, email.FromName);
                message.Subject = email.Subject;
                message.Body = email.Message;
                message.IsBodyHtml = true;
    
                using (var smtp = new SmtpClient())
                {
                    var credential = new NetworkCredential
                    {
                        UserName = "DoNotReply@****.com",  // replace with valid value
                        Password = "********"  // replace with valid value
                    };
                    smtp.Credentials = credential;
                    smtp.Host = "smtp.gmail.com";
                    smtp.Port = 587;
                    smtp.EnableSsl = true;
                    await smtp.SendMailAsync(message);
                }
            }
    
            private string RenderViewToString(ControllerContext context, string viewPath, object model = null, bool partial = false)
            {
                // first find the ViewEngine for this view
                ViewEngineResult viewEngineResult = null;
                if (partial)
                    viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
                else
                    viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
    
                if (viewEngineResult == null)
                    throw new FileNotFoundException("View cannot be found.");
    
                // get the view and attach the model to view data
                var view = viewEngineResult.View;
                context.Controller.ViewData.Model = model;
    
                string result = null;
    
                using (var sw = new StringWriter())
                {
                    var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
                    view.Render(ctx, sw);
                    result = sw.ToString();
                }
    
                return result;
            }
    A view to render
    Code:
    @model EmailWithTemplateTest.Models.Email
    
    @{
        Layout = "~/Views/Shared/_EmailLayout.cshtml";
    }
    
    <h2>TestEmail</h2>
    
    <div>
        <h4>Email</h4>
        <hr />
        <dl class="dl-horizontal">
            <dt>
                @Html.DisplayNameFor(model => model.FromName)
            </dt>
            <dd>
                @Html.DisplayFor(model => model.FromName)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.FromEmail)
            </dt>
            <dd>
                @Html.DisplayFor(model => model.FromEmail)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.Subject)
            </dt>
            <dd>
                @Html.DisplayFor(model => model.Subject)
            </dd>
    
            <dt>
                @Html.DisplayNameFor(model => model.Message)
            </dt>
            <dd>
                @Html.DisplayFor(model => model.Message)
            </dd>
        </dl>
    </div>
    A Model to pass to the view
    Code:
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace EmailWithTemplateTest.Models
    {
        public class Email
        {
            [Required, Display(Name = "Sender's name")]
            public string FromName { get; set; }
    
            [Required, Display(Name = "Sender's email"), EmailAddress]
            public string FromEmail { get; set; }
    
            [Required, Display(Name = "Receiver's email"), EmailAddress]
            public string ToEmail { get; set; }
    
            [Required]
            public string Subject { get; set; }
    
            [Required]
            public string Message { get; set; }
        }
    }
    Last edited by am385; 08-27-2015 at 06:40 PM.

  4. #4
    Join Date
    Aug 2010
    Posts
    158
    Here is another quick hack using a fake controller context so that it can be leveraged outside of a controllers action. Realistically if this was purely for email I would probably have a SendEmailFromViewAsync method as well that internally creates the body of an email message from view

    also note that I am using the same model in the view as the email it self, but this would work with any Model/ViewModel pair

    Code:
    using EmailWithTemplateTest.Models;
    using System.Threading.Tasks;
    using System.Web.Mvc;
    
    namespace EmailWithTemplateTest.Controllers
    {
        public class HomeController : Controller
        {
            public async Task<ActionResult> Contact()
            {
                ViewBag.Message = "Your contact page.";
    
                var email = new Email
                {
                    FromEmail = "DoNotReply@*********.com",
                    FromName = "MMO Website",
    
                    ToEmail = "********@gmail.com",
    
                    Subject = "This is a test",
                    Message = "This message is just a test"
                };
    
                email.Message = EmailFactory.RenderViewToString("~/Views/Mailers/TestEmail.cshtml", email);
    
                await EmailFactory.SendEmailAsync(email);
    
                return View();
            }        
        }
    }


    Code:
    using EmailWithTemplateTest.Models;
    using System.IO;
    using System.Net;
    using System.Net.Mail;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Routing;
    
    namespace EmailWithTemplateTest
    {
        public static class EmailFactory
        {
            public static async Task SendEmailAsync(Email email)
            {
                var message = new MailMessage();
    
                message.To.Add(new MailAddress(email.ToEmail));
                message.From = new MailAddress(email.FromEmail, email.FromName);
                message.Subject = email.Subject;
                message.Body = email.Message;
                message.IsBodyHtml = true;
    
                using (var smtp = new SmtpClient())
                {
                    var credential = new NetworkCredential
                    {
                        UserName = "DoNotReply@*****com",  // replace with valid value
                        Password = "******"  // replace with valid value
                    };
                    smtp.Credentials = credential;
                    smtp.Host = "smtp.gmail.com";
                    smtp.Port = 587;
                    smtp.EnableSsl = true;
                    await smtp.SendMailAsync(message);
                }
            }
    
            public static string RenderViewToString(string viewPath, object model = null, bool partial = false, ViewDataDictionary viewDataDictionary = null)
            {
                // first find the ViewEngine for this view
                ViewEngineResult viewEngineResult = null;
                if (partial)
                    viewEngineResult = ViewEngines.Engines.FindPartialView(FakeControllerContext, viewPath);
                else
                    viewEngineResult = ViewEngines.Engines.FindView(FakeControllerContext, viewPath, null);
    
                if (viewEngineResult == null)
                    throw new FileNotFoundException("View cannot be found.");
    
                if (viewDataDictionary == null)
                    viewDataDictionary = new ViewDataDictionary();
    
                // get the view and attach the model to view data
                var view = viewEngineResult.View;
                viewDataDictionary.Model = model;
    
                using (var sw = new StringWriter())
                {
                    var ctx = new ViewContext(FakeControllerContext, view, viewDataDictionary, FakeControllerContext.Controller.TempData, sw);
                    view.Render(ctx, sw);
                    return sw.ToString();
                }
            }
    
            private static ControllerContext _fakeControllerContext;
            private static ControllerContext FakeControllerContext
            {
                get
                {
                    if (_fakeControllerContext == null)
                    {
                        var routeData = new RouteData();
                        routeData.Values.Add("controller", "Fake");
                        _fakeControllerContext = new ControllerContext(new HttpContextWrapper(HttpContext.Current), routeData, new FakeController());
                    }
    
                    return _fakeControllerContext;
                }
            }
            private class FakeController : ControllerBase { protected override void ExecuteCore() { } }
        }
    }
    Last edited by am385; 08-27-2015 at 07:16 PM.

  5. #5
    Join Date
    Feb 2014
    Posts
    277
    am385,
    Thanks.
    Seems you have done just about all the work including configuring smtp

    I wasn't expecting such a comprehensive response.
    That kinda means I can now upgrade to VS2015 with confidence when I rebuild the PC.

  6. #6
    Join Date
    Aug 2010
    Posts
    158
    No problem oldngrey. Basically that is just a hacked together set of like 3 peoples work. One for rendering view as HTML string, one for settings up fake controllers, and one for sending emails with System.Net.Mail so i don't deserve the credit. I just plumbed them together and converted them to a basic system. Please note that even though I called it EmailFactory that this does not follow factory patterns. I shouldn't have called it that.

    You can tell that the nuget package you linked is just a basic system like this wrapped in a set of fluent methods.

    Also, we should really make the TO, CC, & BCC parts of the email model and set them up as lists or at least ";" separated strings to be parsed out later. Or even a list of MailAddress types.

    Just a wanted to throw out a quick proof of concept that we could do this incredibly easily without the need for nuget packages that don't really offer a lot of code simplicity.

  7. #7
    Join Date
    Feb 2014
    Posts
    277
    I am having some fun getting MVCMailer to work in VS2015 just to remain 100% compatible with Nelson code.

    If you are interested, try installing
    * T4Scaffolding.VS2015
    then install
    * MvcMailer-vs2013 (find it with Include Pre-releases ticked and select Ignore Dependencies from the pulldown.)

    Funnily enough it seems to work so far. At least it does send e-mails.

    ** edit Well the next time I tried it, the "Scaffold Mailer.Razor UsersMailer VerifyEmail,ResetPasswor -NoInterface" command failed requiring a restart of VS. Not great and hardly recommended now.
    Last edited by oldngrey; 10-12-2015 at 08:01 PM.

  8. #8
    Join Date
    Aug 2010
    Posts
    158
    I didn't have to install any other packages personally. It just started working. I think the underlying scaffolding tech is now supported.

  9. #9
    Join Date
    Feb 2014
    Posts
    277
    This scaffolding stuff really is annoying to get working. T4Scaffolding.VS2015 is not seen as an upgrade or the same package by a lot of things that need scaffolding 1.0. As soon as something else brings in Scaffolding it all dies as only one scaffolding can work at a time.
    So, I am trying a few other things as a last ditch effort to keep using MVCMailer. We shall see.

    I hate to say just yet that am385's solution is the the only way out (it's far more elegant than anything I've tried so far), but VS2015 has so many issues that I can't be sure if it's .net 4.6, vs2015 or even windows10 causing my issues. I'd like to nail it down once and for all. I might see if MVCScaffolding 1.09 is the answer..... but things get so screwed up in VS2015 at times that I had to completely re-install it to get it to even create a new project. I really hate working with so many new things at once.
    Last edited by oldngrey; 10-15-2015 at 05:36 PM.

  10. #10
    Join Date
    Aug 2010
    Posts
    158
    I am still running nelsons solution. Just threw that together as a proof of concept. I am running Win 10 & VS 2015. I have not upgraded to 4.6 yet. I started a branch to do that, but there were issues. I'll bet that is the issue for you too.

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •