Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 5 Render View to String

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.

like image 740
wh1sp3r Avatar asked May 06 '14 12:05

wh1sp3r


People also ask

Can I render MVC Razor views directly to string?

Typically, Razor rendering in ASP.NET MVC is reserved purely for view rendering and generation of HTML output as part of an MVC request. But, as I describe in this article, it's also possible to render MVC Razor views directly to string in order to capture the output and to render those views outside of the direct context of an MVC Web request.

How do I render a view to a StringBuilder?

You must pass in the original StringWriter you are using to write to the StringBuilder, not a new instance or the output of the view will be lost. This article describes how to render a View to a string in different scenarios: The solution/code is provided as a class called ViewRenderer. It is part of Rick Stahl's WestwindToolkit at GitHub.

Is there a renderpartial extension in MVC?

In ASP.NET MVC release 1.0 there are a couple of RenderPartial extension methods. The one I'm using in particular is System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial (this HtmlHelper, string, object). I'm unaware whether the method has been added in the latest revisions of MVC and wasn't present in earlier ones. Thanks.

What is MVC Razor and how does it work?

The ASP.NET MVC Razor implementation is closely tied to ASP.NET and MVC and the View template (WebViewPage class) includes a few MVC-specific features as well as back references to the controller. In other words MVC Razor is designed with MVC in mind. Not surprisingly, it's easiest to render MVC Razor views out of MVC applications.


3 Answers

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.cshtml is stored in Views/Email/MyHTMLView.cshtml. Email is a fake controller name.

like image 143
wh1sp3r Avatar answered Oct 13 '22 09:10

wh1sp3r


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.

like image 31
Alok Avatar answered Oct 13 '22 09:10

Alok


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
}
like image 3
William Bosacker Avatar answered Oct 13 '22 07:10

William Bosacker