2 min read

The Razor View Engine is very powerful, easy to use, and just overall great for binding data to HTML. But sometimes you need to render HTML for use in Emails, download pages, generate PDF’s, or other purposes, so it would be neat if you could use the Razor View Engine to generate those for you, and just give you the HTML to do with it as you wish. Luckily we have this somewhat built-in, and here’s a wrapper for it.

public string RenderRazorViewToString(string viewName, object model)
{
  ViewData.Model = model;
  using (var sw = new StringWriter())
  {
    var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
    var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
    viewResult.View.Render(viewContext, sw);
    viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
    return sw.GetStringBuilder().ToString();
  }
}

You can now use it as in the following example

var htmlPage = RenderRazorViewToString("~/Views/Home/myview.cshtml", siteModel);

You can of course extend the method above, and pass it your own ViewData, TempData, RouteData, change the View Engine, enable or disable ClientValidation and UnobtrusiveJavasript, and so on.

.NET Core / .NET 5 Razor View to String

In .net Core we use the RazorViewEngine which we can inject. We can find the view via the ViewEngine, and then use it to locate the view. Once we have the view we can render it, but we’ll also need to create an Action Context it expects, and provide it a TempData dictionary. We’ll also want to create an interface for it, so we can inject it anywhere we need.

public interface IViewRender
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="name"></param>
    /// <param name="model"></param>
    /// <returns></returns>
    string RenderPartialViewToString<TModel>(string name, TModel model);
}
public class ViewRender : IViewRender
{
    private IRazorViewEngine _viewEngine;
    private ITempDataProvider _tempDataProvider;
    private IServiceProvider _serviceProvider;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="viewEngine"></param>
    /// <param name="tempDataProvider"></param>
    /// <param name="serviceProvider"></param>
    public ViewRender(
        IRazorViewEngine viewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _viewEngine = viewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="name"></param>
    /// <param name="model"></param>
    /// <returns></returns>
    public string RenderPartialViewToString<TModel>(string name, TModel model)
    {
        var actionContext = GetActionContext();

        var viewEngineResult = _viewEngine.FindView(actionContext, $"EmailTemplate/{name}", false);

        if (!viewEngineResult.Success)
        {
            throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name));
        }

        var view = viewEngineResult.View;

        using (var output = new StringWriter())
        {
            var viewContext = new ViewContext(
                actionContext,
                view,
                new ViewDataDictionary<TModel>(
                    metadataProvider: new EmptyModelMetadataProvider(),
                    modelState: new ModelStateDictionary())
                {
                    Model = model
                },
                new TempDataDictionary(
                    actionContext.HttpContext,
                    _tempDataProvider),
                output,
                new HtmlHelperOptions());

            view.RenderAsync(viewContext).GetAwaiter().GetResult();

            return output.ToString();
        }
    }

    private ActionContext GetActionContext()
    {
        var httpContext = new DefaultHttpContext
        {
            RequestServices = _serviceProvider
        };
        return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
    }
}

Next, we’ll register it in our services collection as scoped

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IViewRender, ViewRender>();
    ...
}

Then, we just inject our IViewRender class anywhere we need it and can call it easily

var bodyTxt = await _viewRender.RenderPartialViewToString("QuestionAnswered", emailMessage);

More context:

Return View as String in .NET Core

Thanks to Paris Polyzos and his article. I’m re-posting his code here, just in case the original post got removed for any reason.

Was this post helpful?