Render Razor view to string in ASP.NET 5
Asked Answered
N

2

17

In previous versions of ASP.NET it was possible, although not very simple, to render Razor views as strings. The methods I've seem are to use a fake controller, or also to use some external engine like RazorEngine.

Now, many things are changed with ASP.NET 5 and I was wondering whether or not this is now simpler than before. So in the new version of the framework is there one straightforward way to render Razor views as strings or we still need to use the methods from the previous versions?

Northrup answered 20/5, 2015 at 23:39 Comment(4)
Do you want to insert the HTML content of an action to a string variable? Or simply render an ActionResult inside another View?Fairlead
@FabioLuz I want to render the view and put the rendered HTML code inside a string variable.Northrup
If you want to do this while you are under MVC 6, it should be fairly straight forward. It's just a matter of getting the right dependency to rander the razor view. However, if you are outside of it, here is an example: github.com/tugberkugurlu/RazorOnConsole/blob/… dig in to see how Templater.Run is implemented.Jacobsen
@Jacobsen that link is great, thanks! I'm not the OP, but your repo was incredibly helpful.Timothee
T
20

I use the following types injected from the IServiceProvider:

ICompositeViewEngine viewEngine;
ITempDataProvider tempDataProvider;
IHttpContextAccessor httpContextAccessor;

I render the content using the following method:

private async Task<string> RenderView(string path, ViewDataDictionary viewDataDictionary, ActionContext actionContext)
{
    using (var sw = new System.IO.StringWriter())
    {
        var viewResult = viewEngine.FindView(actionContext, path);

        var viewContext = new ViewContext(actionContext, viewResult.View, viewDataDictionary, new TempDataDictionary(httpContextAccessor, tempDataProvider), sw);

        await viewResult.View.RenderAsync(viewContext);
        sw.Flush();

        if (viewContext.ViewData != viewDataDictionary)
        {
            var keys = viewContext.ViewData.Keys.ToArray();
            foreach (var key in keys)
            {
                viewDataDictionary[key] = viewContext.ViewData[key];
            }
        }

        return sw.ToString();
    }
}

I call it like this:

var path = "~/Views/Home/Index.cshtml";
var viewDataDictionary = new ViewDataDictionary(new Microsoft.AspNet.Mvc.ModelBinding.EmptyModelMetadataProvider(), new Microsoft.AspNet.Mvc.ModelBinding.ModelStateDictionary());
var actionContext = new ActionContext(httpContextAccessor.HttpContext, new Microsoft.AspNet.Routing.RouteData(), new ActionDescriptor());
viewDataDictionary.Model = null;
var text = await RenderView(path, viewDataDictionary, actionContext);

Of course, my viewDataDictionary and actionContext variables are set by another method for encapsulation. A modification to the new ViewDataDictionary line can result in a typed Model being bound to your View if you choose.

This code uses heavy usings, I think I've listed them below. Otherwise, VS2015 is pretty good about finding them.

using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;

This was written under beta-3; it still builds, but some things may change. I'll try to come back here to update if it does.

Threecornered answered 21/5, 2015 at 11:45 Comment(3)
Glad to help! And thanks for noting it still works - I hadn't had to go back and change it, so hadn't added that note to my answer.Threecornered
Not seeing how you get those injected objects and also the signature of the method is incoherent with the way you call it...Saucy
@Saucy - just in case anyone wonders about this, it's just dependency injection (DI). The service dependencies are generally parameters to the constructor of the class and the DI container works out where to get them. With regards to the signature inconsistency, Matt probably wrote the original method inside a controller that has an ActionContext property so you can just change to a capital A and start testing the code from within a controller.Longobard
F
4

There is a solution that I've used an year ago. I'm not sure if there is a new/better way to do, but it solves the problem.

public string RenderViewToString(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();
     }
}
Fairlead answered 21/5, 2015 at 0:40 Comment(2)
Unfortunately, the ViewEngines class also does not exist with asp.net-5.Threecornered
@MattDeKrey But it does in v4 (Not the OP request, I know)! Aaand this is absolutely the way to go for that version! Nice one, saved my day, no issues with any of the html helpers used with razor. THANK YOUSaucy

© 2022 - 2024 — McMap. All rights reserved.