ASP.NET Core Render a View with Partial Views inside to String

Viewed 1014

I am struggling to render a view that has a partial inside the view.

The View does get rendered to string, but the partial inside the view does not get rendered at all.

Here is my View to String Utility Code:

public static class EmailUtility
    {
        public static async Task<string> RenderPartialViewToString(this Controller controller, string viewName, object model)
        {
            controller.ViewData.Model = model;

            using (StringWriter sw = new StringWriter())
            {
                IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
                ViewEngineResult viewEngineResult = GetViewEngineResult(controller, viewName, false, viewEngine);

                if (!viewEngineResult.Success)
                {
                    return $"A view with the name {viewName} could not be found";
                }

                ViewContext viewContext = new ViewContext(
                    controller.ControllerContext,
                    viewEngineResult.View,
                    controller.ViewData,
                    controller.TempData,
                    sw,
                    new HtmlHelperOptions()
                );

                await viewEngineResult.View.RenderAsync(viewContext);

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

        private static ViewEngineResult GetViewEngineResult(Controller controller, string viewName, bool isPartial, IViewEngine viewEngine)
        {
            if (viewName.StartsWith("~/"))
            {
                var hostingEnv = controller.HttpContext.RequestServices.GetService(typeof(IWebHostEnvironment)) as IWebHostEnvironment;
                return viewEngine.GetView(hostingEnv.WebRootPath, viewName, !isPartial);
            }
            else
            {
                return viewEngine.FindView(controller.ControllerContext, viewName, !isPartial);
            }
        }
    }

This is how I call the method in the Controller:

string html = await this.RenderPartialViewToString("~/Views/Shared/MailerLayoutMaster.cshtml", model);

This is the MailerLayoutMaster.cshtml:

@model Model

@{ 
    Layout = null;
}

<!DOCTYPE html>
<html>
    <head>

    </head>
    <body>
        @{ 
            switch ((int) Model.EmailType)
            {
                case (int) EmailTypeEnum.ContactUsSubmitted:
                    await Html.PartialAsync("../Shared/MailTemplates/EmailPartial.cshtml", Model);
                    break;
                default:
                    
                    break;
            }
        }
    </body>
</html>

And this is my EmailPartial.cshtml

@model Model

<table>
    <tr>
        <td>Name: <b>@Model.Name</b></td>
    </tr>
    <tr>
        <td>Surname: <b>@Model.Surname</b></td>
    </tr>
    <tr>
        <td>Email: <b>@Model.Email</b></td>
    </tr>
    <tr>
        <td>Number: <b>@Model.Phone</b></td>
    </tr>
</table>

This is the string I get:

<!DOCTYPE html>
<html lang="en-ZA">
    <head>

    </head>
    <body>

    </body>
</html>

Here is my project structure if it helps:

Controllers
    Controller.cs
Utilities
    ViewToStringUtil.cs
Views
    Shared
        MailTemplates
            EmailPartial.cshtml
        MailerLayoutMaster.cshtml
2 Answers

I think it's happened because you didn't call partial view the right way.no need to use .cshtml extension for call partial view. update your code like below:-

string html = await this.RenderPartialViewToString("~/Views/Shared/MailerLayoutMaster", model);

And

 switch ((int) Model.EmailType)
            {
                case (int) EmailTypeEnum.ContactUsSubmitted:
                    await Html.PartialAsync("../Shared/MailTemplates/EmailPartial", Model);
                    break;

//clarify code

Also, you can try without full path as per your folder structure, just using a partial view name. You can check it works or not this process.

string html = await this.RenderPartialViewToString("PartialViewName", model);

The solution was really simple.

All I needed was a @ before

await Html.PartialAsync("../Shared/MailTemplates/EmailPartial.cshtml", Model);

It should have been:

@await Html.PartialAsync("../Shared/MailTemplates/EmailPartial.cshtml", Model);
Related