Here is how I did it.
How to always register a Service in Startup.cs
services.AddScoped<IViewRenderService, ViewRenderService>();
A service is defined as follows:
public interface IViewRenderService { Task<string> RenderToStringAsync<T>(string viewName, T model) where T : PageModel; } public class ViewRenderService : IViewRenderService { private readonly IRazorViewEngine _razorViewEngine; private readonly ITempDataProvider _tempDataProvider; private readonly IServiceProvider _serviceProvider; private readonly IHttpContextAccessor _httpContext; private readonly IActionContextAccessor _actionContext; private readonly IRazorPageActivator _activator; public ViewRenderService(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider, IHttpContextAccessor httpContext, IRazorPageActivator activator, IActionContextAccessor actionContext) { _razorViewEngine = razorViewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; _httpContext = httpContext; _actionContext = actionContext; _activator = activator; } public async Task<string> RenderToStringAsync<T>(string pageName, T model) where T : PageModel { var actionContext = new ActionContext( _httpContext.HttpContext, _httpContext.HttpContext.GetRouteData(), _actionContext.ActionContext.ActionDescriptor ); using (var sw = new StringWriter()) { var result = _razorViewEngine.FindPage(actionContext, pageName); if (result.Page == null) { throw new ArgumentNullException($"The page {pageName} cannot be found."); } var view = new RazorView(_razorViewEngine, _activator, new List<IRazorPage>(), result.Page, HtmlEncoder.Default, new DiagnosticListener("ViewRenderService")); var viewContext = new ViewContext( actionContext, view, new ViewDataDictionary<T>(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model }, new TempDataDictionary( _httpContext.HttpContext, _tempDataProvider ), sw, new HtmlHelperOptions() ); var page = ((Page)result.Page); page.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext { ViewData = viewContext.ViewData }; page.ViewContext = viewContext; _activator.Activate(page, viewContext); await page.ExecuteAsync(); return sw.ToString(); } } }
I call it that
emailView.Body = await this._viewRenderService.RenderToStringAsync("Email/ConfirmAccount", new Email.ConfirmAccountModel { EmailView = emailView, });
"Email / ConfirmAccount" is the path to my Razor page (under the pages). "ConfirmAccountModel" is my page for this page.
ViewData is null because the ViewData for the page is set when the PageContext parameter is set, so if this is not set, ViewData is null.
I also found that I needed to call
_activator.Activate(page, viewContext);
Work for all this. This has not yet been fully tested, so it may not work for all scenarios, but should help you get started.