MVC 5 Render View to String

2019-01-22 08:28发布

问题:

It seems, most code for rendering view into string doesn't work in MVC 5.

I have latest MVC 5.1.2 templates and I am trying to render view into string.

    public static String RenderViewToString(ControllerContext context, String viewPath, object model = null)
    {
        context.Controller.ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindView(context, viewPath, null);
            var viewContext = new ViewContext(context, viewResult.View, context.Controller.ViewData, context.Controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);
            viewResult.ViewEngine.ReleaseView(context, viewResult.View);
            return sw.GetStringBuilder().ToString();
        }
    }

Well, it's working but its output contains lots of $ marks instead tags. I read something about it was fixed in RC version, but that's old news.

Problem looks like this

<$A$><h1></h1> 
<table</$A$><$B$> class=""</$B$><$C$>> <tbody</$C$><$D$></$D$><$E$>></tbody>
</table></$E$>

I would like to ask, how do you render views into string in latest MVC 5 template ? Thanks.

回答1:

Ok, seems I found a solution. Author of the idea is Yakir Manor.

class FakeController : ControllerBase
{
    protected override void ExecuteCore() { }
    public static string RenderViewToString(string controllerName, string viewName, object viewData)
    {
        using (var writer = new StringWriter())
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", controllerName);
            var fakeControllerContext = new ControllerContext(new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null), new HttpResponse(null))), routeData, new FakeController());
            var razorViewEngine = new RazorViewEngine();
            var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);

            var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
            razorViewResult.View.Render(viewContext, writer);
            return writer.ToString();

        }
    }
}

It's a trick with fake context and response.

Example:

String renderedHTML = RenderViewToString("Email", "MyHTMLView", myModel );

My file MyHTMLView.cstml is stored in Views/Email/MyHTMLView.cshtml. Email is a fake controller name.



回答2:

following is the solution that works with session and areas on MVC5.

public class FakeController : ControllerBase
{
    protected override void ExecuteCore() { }
    public static string RenderViewToString(string controllerName, string viewName,string areaName, object viewData,RequestContext rctx)
    {
        try
        {
            using (var writer = new StringWriter())
            {
                var routeData = new RouteData();
                routeData.Values.Add("controller", controllerName);
                routeData.Values.Add("Area", areaName);
                routeData.DataTokens["area"] = areaName;

                var fakeControllerContext = new ControllerContext(rctx, new FakeController());
                //new ControllerContext(new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null), new HttpResponse(null))), routeData, new FakeController());
                fakeControllerContext.RouteData = routeData;

                var razorViewEngine = new RazorViewEngine();

                var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);

                var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);

                razorViewResult.View.Render(viewContext, writer);
                return writer.GetStringBuilder().ToString();
                //use example
                //String renderedHTML = RenderViewToString("Email", "MyHTMLView", myModel );
                //where file MyHTMLView.cstml is stored in Views/Email/MyHTMLView.cshtml. Email is a fake controller name.
            }
        }
        catch (Exception ex)
        {
            //do your exception handling here
        }
    }
}

here is how you call this from another controller

var modal = getModal(params);
return FakeController.RenderViewToString(controllerName, viewName, areaName, modal, this.Request.RequestContext);

using requestcontext we can easily pass current session in fakecontroller and render razor string.



回答3:

I had an immediate need to return 6 partial views as strings in a JSON object. Instead of creating a static method, and passing all the unneeded parameters, I decided to add protected methods to our ControllerBase class that derives from Controller, and is used as the base class for all of our controllers.

Here is a fully functional ControllerBase class that provides this functionality, and works very similar to the PartialView() and View() methods that are in the Controller class. It includes the additions from @Alok.

public abstract class ControllerBase : Controller
{
    #region PartialViewToString

    protected string PartialViewToString(string partialViewName, object model = null)
    {
        ControllerContext controllerContext = new ControllerContext(Request.RequestContext, this);

        return ViewToString(
            controllerContext,
            ViewEngines.Engines.FindPartialView(controllerContext, partialViewName) ?? throw new FileNotFoundException("Partial view cannot be found."),
            model
        );
    }

    #endregion

    #region ViewToString

    protected string ViewToString(string viewName, object model = null)
    {
        ControllerContext controllerContext = new ControllerContext(Request.RequestContext, this);

        return ViewToString(
            controllerContext,
            ViewEngines.Engines.FindView(controllerContext, viewName, null) ?? throw new FileNotFoundException("View cannot be found."),
            model
        );
    }

    protected string ViewToString(string viewName, string controllerName, string areaName, object model = null)
    {
        RouteData routeData = new RouteData();
        routeData.Values.Add("controller", controllerName);

        if (areaName != null)
        {
            routeData.Values.Add("Area", areaName);
            routeData.DataTokens["area"] = areaName;
        }

        ControllerContext controllerContext = new ControllerContext(HttpContext, routeData, this);

        return ViewToString(
            controllerContext,
            ViewEngines.Engines.FindView(controllerContext, viewName, null) ?? throw new FileNotFoundException("View cannot be found."),
            model
        );
    }

    #endregion

    #region Private Methods

    private string ViewToString(ControllerContext controllerContext, ViewEngineResult viewEngineResult, object model)
    {
        using (StringWriter writer = new StringWriter())
        {
            ViewContext viewContext = new ViewContext(
                ControllerContext,
                viewEngineResult.View,
                new ViewDataDictionary(model),
                new TempDataDictionary(),
                writer
            );

            viewEngineResult.View.Render(viewContext, writer);

            return writer.ToString();
        }
    }

    #endregion
}


回答4:

@wh1sp3r answer works but for partial views I had to do the following minor changes:

 string html = FakeController.RenderViewToString("**Controllername**", "~/views/**Controllername**/_AsdfPartialPage.cshtml", fem);


public static string RenderViewToString(string controllerName, string viewName, object viewData)
    {
        using (var writer = new StringWriter())
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", controllerName);
            var fakeControllerContext = new ControllerContext(new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://localhost", null), new HttpResponse(null))), routeData, new FakeController());
            var razorViewEngine = new RazorViewEngine();
            //var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);

            var razorViewResult = razorViewEngine.FindPartialView(fakeControllerContext, viewName,  false);

            var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
            razorViewResult.View.Render(viewContext, writer);
            return writer.ToString();

        }
    }