JoakDA
JoakDA

Reputation: 305

Exception Rendering a partial view as string that contains another partial view

I am trying to render a partial view as a string to return inside a JSON object. I have this code to do it:

/// <summary>
/// Render razor view to a string.
/// </summary>
/// <param name="model">Model sent to view.</param>
/// <param name="filePath">Path to the view location in project hierarchy.</param>
/// <returns>String with the html code of returned view.</returns>
public static string Func_GetRazorViewAsString(object model, string filePath)
{
    var st = new StringWriter();
    var context = new HttpContextWrapper(HttpContext.Current);
    var routeData = new RouteData();
    var controllerContext = new ControllerContext(new RequestContext(context, routeData), new FakeController());
    var razor = new RazorView(controllerContext, filePath, null, false, null);
    razor.Render(new ViewContext(controllerContext, razor, new ViewDataDictionary(model), new TempDataDictionary(), st), st);
    return st.ToString();
}

It works great when the View does not contain a PartialView, but it throws an exception when the view to render call another Partial View. For example:

@using Anpr_Web_Manager.App_GlobalResources
@model IEnumerable<Anpr_Web_Manager.Models.DetectionCardViewModel>

@{
    int iCountElementsRow = 0;
    int iNumberElements = Model.Count();
    int iNumNavigationPages = (int)Math.Ceiling(iNumberElements / (double)3);
}
    <div class="col-12 bootstrap-carousel">
        <div id="carouselDetections" class="carousel slide" data-ride="carousel" data-interval="false">
            @if (iNumberElements != 0)
            {
                @:<ol class="carousel-indicators">
                    <li data-target="#carouselDetections" data-slide-to="0" class="active"></li>
                    for (int i = 1; i < iNumNavigationPages; i++)
                {
                    <li data-target="#carouselDetections" data-slide-to="@i"></li>
                    }
                @:</ol>
            }
            <div class="container carousel-inner">
                <div class="row row-equal row-slider carousel-item active">
                    @foreach (var item in Model)
                    {
                        if (iCountElementsRow != 0 && iCountElementsRow % 3 == 0)
                        {
                            //Insert new row
                            @:</div>
                            @:<div class="row row-equal row-slider carousel-item">
                        }
                        //TODO: RENDER ELEMENT
                        @Html.Partial("~/Views/Capturas/_DetectionElement.cshtml", item);
                        iCountElementsRow++;
                    }
                </div>
            </div>
        </div>
    </div>

The exception message is:

"The RouteData must contain an item named 'controller' with a non-empty string value."

Please, how can I solve it? I know I can copy and paste the code of the second Partial View but it is not my best solution because I use this Partial View in another places and I don't want to have the same code duplicated.

Thank you very much. Best regards,

Upvotes: 0

Views: 573

Answers (1)

RickL
RickL

Reputation: 3393

Using this method to render a View to a string:

public static string RenderViewToString(ControllerContext context, string viewPath, object viewModel = null, bool partial = false)
{
    // get the ViewEngine for this view
    ViewEngineResult viewEngineResult = null;
    if (partial)
        viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
    else
        viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);

    if (viewEngineResult == null)
        throw new FileNotFoundException("View cannot be found.");

    // get the view and attach the model to view data
    var view = viewEngineResult.View;
    context.Controller.ViewData.Model = viewModel;

    string result = null;

    using (var sw = new StringWriter())
    {
        var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
        view.Render(ctx, sw);
        result = sw.ToString();
    }

    return result;
}

works if you call:

@foreach (var item in Model.SomeIEnumerableModel)
{
    @Html.Partial("~/Views/SomePath/SomeViewTwo.cshtml", item);
}

in the "parent Partial View".

It can be called in the Controller using this (assuming you're returning json from an ajax request, and the RenderViewToString method is located in the calling Controller):

public ActionResult TestViewToString()
{
    var viewModel = new TestViewModel();
    // Populate ViewModel here ...

    string data = RenderViewToString(ControllerContext, "~/Views/SomePath/SomeViewOne.cshtml", viewModel, true);
    return Json(data, JsonRequestBehavior.AllowGet);
}

Upvotes: 1

Related Questions