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
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)){ }