97
votes

How to generate a HTML of a given partial view on ASP.NET view engine is known.

But if this functionality is used on razor partial view it does not work, as exception says the partial view does not derive from "UserControl".

How to fix the rendering to support razor partial view?

I need this because I generate emails form this partial views ...

UPDATE:

Code that fails (@mcl):

public string RenderPartialToString(string controlName, object viewData)
    {
        ViewPage viewPage = new ViewPage() { ViewContext = new ViewContext() };
        viewPage.Url = this.GetUrlHelper();

        string fullControlName = "~/Views/Email/" + controlName + ".ascx";

        viewPage.ViewData = new ViewDataDictionary(viewData);
        viewPage.Controls.Add(viewPage.LoadControl(fullControlName));

        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        {
            using (HtmlTextWriter tw = new HtmlTextWriter(sw))
            {
                viewPage.RenderControl(tw);
            }
        }
        return sb.ToString();
    }
6
Can you show the code you have so far that generates the exception?mlibby

6 Answers

153
votes
@Html.Partial("nameOfPartial", Model)

Update

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");

    ViewData.Model = model;

    using (StringWriter sw = new StringWriter()) {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
    }
}
7
votes

Although adequate answers have already been given, I'd like to propose a less verbose solution, that can be used without the helper methods available in an MVC controller class. Using a third party library called "RazorEngine" you can use .Net file IO to get the contents of the razor file and call

string html = Razor.Parse(razorViewContentString, modelObject);

Get the third party library here.

4
votes

You could also use the RenderView Controller extension from here (source)

and use it like this:

public ActionResult Do() {
var html = this.RenderView("index", theModel);
...
}

it works for razor and web-forms viewengines

1
votes

I saw that someone was wondering how to do it for another controller.

In my case I had all of my email templates in the Views/Email folder, but you could modify this to pass in the controller in which you have views associated for.

public static string RenderViewToString(Controller controller, string viewName, object model)
    {
        var oldController = controller.RouteData.Values["controller"].ToString();

        if (controller.GetType() != typeof(EmailController))
            controller.RouteData.Values["controller"] = "Email";

        var oldModel = controller.ViewData.Model;
        controller.ViewData.Model = model;
        try
        {
            using (var sw = new StringWriter())
            {
                var viewResult = ViewEngines.Engines.FindView(controller.ControllerContext, viewName,
                                                                           null);

                var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
                viewResult.View.Render(viewContext, sw);

                //Cleanup
                controller.ViewData.Model = oldModel;
                controller.RouteData.Values["controller"] = oldController;

                return sw.GetStringBuilder().ToString();
            }
        }
        catch (Exception ex)
        {
            Elmah.ErrorSignal.FromCurrentContext().Raise(ex);

            throw ex;
        }
    }

Essentially what this does is take a controller, such as AccountController and modify it to think it's an EmailController so that the code will look in the Views/Email folder. It's necessary to do this because the FindView method doesn't take a straight up path as a parameter, it wants a ControllerContext.

Once done rendering the string, it returns the AccountController back to its initial state to be used by the Response object.

1
votes

great code; little hint: if you sometimes have to bypass more data and not only the viewmodel ..

 if (model is ViewDataDictionary)
 {
     controller.ViewData = model as ViewDataDictionary;
 } else {
     controller.ViewData.Model = model;
 }
0
votes

Borrowing @jgauffin answer as an HtmlHelper extension:

public static class HtmlHelperExtensions
{
    public static MvcHtmlString RenderPartialViewToString(
        this HtmlHelper html, 
        ControllerContext controllerContext, 
        ViewDataDictionary viewData,
        TempDataDictionary tempData,
        string viewName, 
        object model)
    {
        viewData.Model = model;
        string result = String.Empty;

        using (StringWriter sw = new StringWriter())
        {
            ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controllerContext, viewName);
            ViewContext viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, sw);
            viewResult.View.Render(viewContext, sw);

            result = sw.GetStringBuilder().ToString();
        }

        return MvcHtmlString.Create(result);
    }
}

Usage in a razor view:

Html.RenderPartialViewToString(ViewContext, ViewData, TempData, "Search", Model)