Render Razor View to string in ASP.NET Core
UPDATE July, 2016
Working fine on the following versions 1.0.0
, RC2
Who's targeting aspnetcore RC2, this snippet might help you:
- Create a separate Service, so you can use it either if you are not in a controller context, e.g. from a command line or on a queue runner, etc ...
- Register this service in your IoC container in the
Startup
class
https://gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09
Usage
// using a Model
string html = view.Render("Emails/Test", new Product("Apple"));
// using a Dictionary<string, object>
var viewData = new Dictionary<string, object>();
viewData["Name"] = "123456";
string html = view.Render("Emails/Test", viewData);
Notes
Links in Razor are rendered as relative URL, so this will not work on external views (like emails, etc ...).
As for now am generating the link on the controller and pass it to the view through the ViewModel.
Credit
The source is extracted from (Thanks To @pholly): https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs)
I found this thread which discusses it: https://github.com/aspnet/Mvc/issues/3091
Someone in the thread created a sample service here: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs
After trial and error I was able to trim the service down so it only needs a valid HttpContext
and a ViewEngine
and I added an overload that doesn't require a model. Views are relative to your application root (they don't have to live in a Views
folder).
You will need to register the service in Startup.cs
and also register HttpContextAccessor
:
//Startup.cs ConfigureServices()
services.AddTransient<ViewRenderService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.IO;
namespace LibraryApi.Services
{
public class ViewRenderService
{
IRazorViewEngine _viewEngine;
IHttpContextAccessor _httpContextAccessor;
public ViewRenderService(IRazorViewEngine viewEngine, IHttpContextAccessor httpContextAccessor)
{
_viewEngine = viewEngine;
_httpContextAccessor = httpContextAccessor;
}
public string Render(string viewPath)
{
return Render(viewPath, string.Empty);
}
public string Render<TModel>(string viewPath, TModel model)
{
var viewEngineResult = _viewEngine.GetView("~/", viewPath, false);
if (!viewEngineResult.Success)
{
throw new InvalidOperationException($"Couldn't find view {viewPath}");
}
var view = viewEngineResult.View;
using (var output = new StringWriter())
{
var viewContext = new ViewContext();
viewContext.HttpContext = _httpContextAccessor.HttpContext;
viewContext.ViewData = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{ Model = model };
viewContext.Writer = output;
view.RenderAsync(viewContext).GetAwaiter().GetResult();
return output.ToString();
}
}
}
}
Example usage:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using LibraryApi.Services;
using System.Dynamic;
namespace LibraryApi.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
ILogger<ValuesController> _logger;
ViewRenderService _viewRender;
public ValuesController(ILogger<ValuesController> logger, ViewRenderService viewRender)
{
_logger = logger;
_viewRender = viewRender;
}
// GET api/values
[HttpGet]
public string Get()
{
//ViewModel is of type dynamic - just for testing
dynamic x = new ExpandoObject();
x.Test = "Yes";
var viewWithViewModel = _viewRender.Render("eNotify/Confirm.cshtml", x);
var viewWithoutViewModel = _viewRender.Render("MyFeature/Test.cshtml");
return viewWithViewModel + viewWithoutViewModel;
}
}
}
In the past, I’ve used the RazorEngine
inside a Class Library because my goal was to render templates from within this Class Library.
From my understanding, you seem to be inside an MVC 6.0 project so why not use a RenderPartialViewToString()
method without having to add the dependency on the RazorEngine
?
Keep in mind, I'm only asking because I'm curious.
For example purposes, from within VS2015, I created a new ASP.NET Web Application and selected the Web Application template from the ASP.NET 5 Preview Templates.
Inside the ViewModels
folder, I created a PersonViewModel
:
public class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", this.FirstName, this.LastName);
}
}
}
I then created a new BaseController
and added a RenderPartialViewToString()
method:
public string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ActionContext.ActionDescriptor.Name;
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
var engine = Resolver.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
ViewEngineResult viewResult = engine.FindPartialView(ActionContext, viewName);
ViewContext viewContext = new ViewContext(ActionContext, viewResult.View, ViewData, TempData, sw,new HtmlHelperOptions());
var t = viewResult.View.RenderAsync(viewContext);
t.Wait();
return sw.GetStringBuilder().ToString();
}
}
Credit goes to @DavidG for his method
Inside the Views-->Shared
folder, I created a new Templates folder in which I’ve added a simple RegistrationTemplate.cshtml
View strongly typed to my PersonViewModel
like so:
@model MyWebProject.ViewModels.PersonViewModel
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Registration</title>
</head>
<body>
<p>
Hello, @Model.FullName
</p>
</body>
</html>
The last step is to make my Controller
inherit from my BaseController
public class MyController : BaseController
And create something like:
public IActionResult Index()
{
var model = new PersonViewModel();
model.FirstName = "Frank";
model.LastName = "Underwood";
var emailbody = base.RenderPartialViewToString("Templates/RegistrationTemplate", model);
return View();
}
Of course, the example above is useless since I do nothing with the variable emailbody
but the idea is to show how it’s used.
At this point, I could've(for example), invoke an EmailService
and pass the emailbody
:
_emailService.SendEmailAsync("[email protected]", "registration", emailbody);
I'm not sure if this is suitable alternative for your current task.
Today I've finished with my library that can solve your problem. You can use it out of ASP.NET as it has no dependencies on it
Example:
string content = "Hello @Model.Name. Welcome to @Model.Title repository";
var model = new
{
Name = "John Doe",
Title = "RazorLight"
};
var engine = new RazorLightEngine();
string result = engine.ParseString(content, model);
//Output: Hello John Doe, Welcome to RazorLight repository
More: https://github.com/toddams/RazorLight