Sending email using resulted razor view in MailKit

2019-05-10 20:02发布

问题:

Hope I can explain my request in a simple way:

In .Net Core, we can display the .cshtml view, after sending the model data using View(FileName, Model).

Is there a way to send the Model to the .cshtml file, so that instead of displaying the resulting view I get it emailed a attachment using Mailkit

回答1:

Thanks to Paris Polyzos and his article.

I found the solution, so liked to share it, may some one get a benefit from it, or improve it.

Firs: We need to create service to convert the Rasor into String, the code Razor2String.cs is below:

using System
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
 
namespace Project.Utilities
{
    public interface IViewRenderService
    {
        Task<string> RenderToStringAsync(string viewName, object model);
    }
 
    public class ViewRenderService : IViewRenderService
    {
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;
         public ViewRenderService(IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            _razorViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        }
 
        public async Task<string> RenderToStringAsync(string viewName, object model)
        {
            var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
            using (var sw = new StringWriter())
            {
                var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
                if (viewResult.View == null)
                {
                    throw new ArgumentNullException($"{viewName} does not match any available view");
                }
 
                var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };
 
                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );
 
                await viewResult.View.RenderAsync(viewContext);
                return sw.ToString();
            }
        }
    }
}

Second: We need to add the "preserveCompilationContext": true to the buildOptions, so the project.json become:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },
  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.AspNetCore.Mvc": "1.0.1",
    "MailKit":"1.10.0"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.1"
        }
      },
      "imports": "dnxcore50"
    }
  }
}

Third: We need to add the service to the ConfigureServices in the Startup class, so that the Startup.cs file become:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Project.Utilities;

public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();
             // Add application services.
            services.AddScoped<IViewRenderService, ViewRenderService>();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    "Hello World of the last resort. The Time is: " +
                    DateTime.Now.ToString("hh:mm:ss tt"));
            });
        }
    }

Fourth: Define your Model, something like users.cs:

namespace myApp
{
     public class users {
        public string   UserId {get; set;}
        public string   UserName {get; set;}
    } 
}

Fifth: Create the View template, in the Views folder, something like Views/Razor2String.cshtml:

Hello
<hr/>

@{
    ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>user id: @Model.UserId</h3>

Sixth: The Program.cs is simply as:

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;

namespace myApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseUrls("http://localhost:50000")
                .UseStartup<Startup>()
                .Build();
            host.Run();
        }
    }
}

Seventh: The main part, 'Email.cs':

using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;

using Project.Utilities;
using System.Threading.Tasks;

using MailKit.Net.Smtp;   // for SmtpClient
using MimeKit;  // for MimeMessage, MailboxAddress and MailboxAddress

namespace myApp
{
    [Route("api")]
    public class RenderController : Controller
    {
        private readonly IViewRenderService _viewRenderService;
        public RenderController(IViewRenderService viewRenderService)
        {
            _viewRenderService = viewRenderService;
        }
     
      [Route("sendEmail")]
      public async Task sendEmail()
        {
            var viewModel = new users
            {
                UserId = "cdb86aea-e3d6-4fdd-9b7f-55e12b710f78",
                UserName = "iggy",
            };
     
            // Get the generated Razor view as String
            var result = await _viewRenderService.RenderToStringAsync("Razor2String", viewModel);

            MemoryStream stream = new MemoryStream ();
            StreamWriter writer = new StreamWriter(stream);
            writer.Write((String)result);
            writer.Flush();
            stream.Position = 0;

            var message = new MimeMessage();
            message.From.Add(new MailboxAddress("Hasan Yousef", "mySelf@gmail.com"));
            message.To.Add(new MailboxAddress("Personal", "myPersonalEmail@gmail.com"));
            message.Subject = "Email Test";
            var bodyBuilder = new BodyBuilder();

            bodyBuilder.HtmlBody = @"<div>HTML email body</Div>";

            bodyBuilder.Attachments.Add ("msg.html", stream);

            message.Body = bodyBuilder.ToMessageBody();

            using (var client = new SmtpClient())
                {
                    client.Connect("smtp.gmail.com", 587);
                    client.AuthenticationMechanisms.Remove("XOAUTH2");  // due to enabling less secure apps access
                Console.WriteLine("Prepairing the Email");
                    try{
                        client.Authenticate("mySelf@gmail.com", "myPSWD");
                        Console.WriteLine("Auth Completed");
                    }
                    catch (Exception e){
                    Console.WriteLine("ERROR Auth"); 
                    }
                    try{
                        client.Send(message);
                        Console.WriteLine("Email had been sent");
                    }
                    catch (Exception e){
                        Console.WriteLine("ERROR");
                    }
                    client.Disconnect(true);
                }
        }
    }
}

If you need the string to be sent back for the browser, you can use:

  [Route("returnView")]
  public async Task<IActionResult> returnView()
    {
        var viewModel = new users
        {
            UserId = "cdb86aea-e3d6-4fdd-9b7f-55e12b710f78",
            UserName = "iggy",
        };
 
        // Get the generated Razor view as String
        var result = await _viewRenderService.RenderToStringAsync("Razor2String", viewModel);
        return Content(result);
    }

If you need to send the result to AJAX request, you can use something like:

public async Task<String> RenderInviteView()
{
.
.
.
return result;
}

If you want to have separate method to convert the string into stream like GenerateStreamFromString(result), then you can call it using using (Stream stream = GenerateStreamFromString(result)){ }