Return View as String in .NET Core

C#asp.netRazorasp.net Core.Net Core

C# Problem Overview


I found some article how to return view to string in ASP.NET, but could not covert any to be able to run it with .NET Core

public static string RenderViewToString(this Controller controller, string viewName, object model)
{
    var context = controller.ControllerContext;
    if (string.IsNullOrEmpty(viewName))
        viewName = context.RouteData.GetRequiredString("action");

    var viewData = new ViewDataDictionary(model);

    using (var sw = new StringWriter())
    {
        var viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
        var viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
        viewResult.View.Render(viewContext, sw);

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

which assumed to be able to call from a Controller using:

var strView = this.RenderViewToString("YourViewName", yourModel);

When I try to run the above into .NET Core I get lots of compilation errors.

I tried to convert it to work with .NET Core, but failed, can anyone help with mentioning the required using .. and the required "dependencies": { "Microsoft.AspNetCore.Mvc": "1.1.0", ... }, to be used in the project.json.

some other sample codes are here and here and here

NOTE I need the solution to get the view converted to string in .NET Core, regardless same code got converted, or another way that can do it.

C# Solutions


Solution 1 - C#

If like me you have a number of controllers that need this, like in a reporting site, it's not really ideal to repeat this code, and even injecting or calling another service doesn't really seem right.

So I've made my own version of the above with the following differences:

  • model strong-typing

  • error checking when finding a view

  • ability to render views as partials or pages

  • asynchronus

  • implemented as a controller extension

  • no DI needed

     using Microsoft.AspNetCore.Mvc;
     using Microsoft.AspNetCore.Mvc.Rendering;
     using Microsoft.AspNetCore.Mvc.ViewEngines;
     using Microsoft.AspNetCore.Mvc.ViewFeatures;
     using System.IO;
     using System.Threading.Tasks;
    
     namespace CC.Web.Helpers
     {
         public static class ControllerExtensions
         {
             public static async Task<string> RenderViewAsync<TModel>(this Controller controller, string viewName, TModel model, bool partial = false)
             {
                 if (string.IsNullOrEmpty(viewName))
                 {
                     viewName = controller.ControllerContext.ActionDescriptor.ActionName;
                 }
    
                 controller.ViewData.Model = model;
    
                 using (var writer = new StringWriter())
                 {
                     IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
                     ViewEngineResult viewResult = viewEngine.FindView(controller.ControllerContext, viewName, !partial);
    
                     if (viewResult.Success == false)
                     {
                         return $"A view with the name {viewName} could not be found";
                     }
    
                     ViewContext viewContext = new ViewContext(
                         controller.ControllerContext,
                         viewResult.View,
                         controller.ViewData,
                         controller.TempData,
                         writer,
                         new HtmlHelperOptions()
                     );
    
                     await viewResult.View.RenderAsync(viewContext);
    
                     return writer.GetStringBuilder().ToString();
                 }
             }
         }
     }
    

Then just implement with:

viewHtml = await this.RenderViewAsync("Report", model);

Or this for a PartialView:

partialViewHtml = await this.RenderViewAsync("Report", model, true);

Solution 2 - C#

Thanks to Paris Polyzos and his article.

I'm re-posting his code here, just in case the original post got removed for any reason.

Create Service in file viewToString.cs as below code:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
     
namespace WebApplication.Services
{
        public interface IViewRenderService
        {
            Task<string> RenderToStringAsync(string viewName, object model);
        }
     
        public class ViewRenderService : IViewRenderService
        {
            private readonly IRazorViewEngine _razorViewEngine;
            private readonly ITempDataProvider _tempDataProvider;
            private readonly IServiceProvider _serviceProvider;
     
            public ViewRenderService(IRazorViewEngine razorViewEngine,
                ITempDataProvider tempDataProvider,
                IServiceProvider serviceProvider)
            {
                _razorViewEngine = razorViewEngine;
                _tempDataProvider = tempDataProvider;
                _serviceProvider = serviceProvider;
            }
     
            public async Task<string> RenderToStringAsync(string viewName, object model)
            {
                var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
                var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
     
                using (var sw = new StringWriter())
                {
                    var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
     
                    if (viewResult.View == null)
                    {
                        throw new ArgumentNullException($"{viewName} does not match any available view");
                    }
     
                    var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                    {
                        Model = model
                    };
     
                    var viewContext = new ViewContext(
                        actionContext,
                        viewResult.View,
                        viewDictionary,
                        new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                        sw,
                        new HtmlHelperOptions()
                    );
     
                    await viewResult.View.RenderAsync(viewContext);
                    return sw.ToString();
                }
            }
        }
}

2. Add the service to the Startup.cs file, as:

using WebApplication.Services;

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IViewRenderService, ViewRenderService>();
}

3. Add "preserveCompilationContext": true to the buildOptions in the project.json, so the file looks like:

{
    "version": "1.0.0-*",
    "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true,
    "preserveCompilationContext": true
    },
    "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.AspNetCore.Mvc": "1.0.1"
    },
    "frameworks": {
    "netcoreapp1.0": {
        "dependencies": {
        "Microsoft.NETCore.App": {
            "type": "platform",
            "version": "1.0.1"
        }
        },
        "imports": "dnxcore50"
    }
    }
}

4. Define you model, for example:

public class InviteViewModel {
    public string	UserId {get; set;}
    public string	UserName {get; set;}
    public string	ReferralCode {get; set;}
    public int	Credits {get; set;}
}

5. Create your Invite.cshtml for example:

@{
    ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId

6. In the Controller:

a. Define the below at the beginning:

private readonly IViewRenderService _viewRenderService;

public RenderController(IViewRenderService viewRenderService)
{
    _viewRenderService = viewRenderService;
}

b. Call and return the view with model as below:

var result = await _viewRenderService.RenderToStringAsync("Email/Invite", viewModel);
return Content(result);

c. The FULL controller example, could be like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebApplication.Services;
    
namespace WebApplication.Controllers
{
    [Route("render")]
    public class RenderController : Controller
    {
        private readonly IViewRenderService _viewRenderService;

        public RenderController(IViewRenderService viewRenderService)
        {
            _viewRenderService = viewRenderService;
        }

    [Route("invite")]
    public async Task<IActionResult> RenderInviteView()
    {
        ViewData["Message"] = "Your application description page.";
    	var viewModel = new InviteViewModel
    	{
    		UserId = "cdb86aea-e3d6-4fdd-9b7f-55e12b710f78",
    		UserName = "Hasan",
    		ReferralCode = "55e12b710f78",
    		Credits = 10
    	};
     
    	var result = await _viewRenderService.RenderToStringAsync("Email/Invite", viewModel);
    	return Content(result);
    }

    public class InviteViewModel {
        public string	UserId {get; set;}
    	public string	UserName {get; set;}
    	public string	ReferralCode {get; set;}
    	public int	Credits {get; set;}
    } 
}

Solution 3 - C#

ASP.NET Core 3.1

I know there are a lot of good answers here, I thought I share mine as well:

This is pulled from the source code of asp.net core on GitHub I usually use it to render HTML emails with Razor as well as returning HTML of partial views via Ajax or SignalR.

Add as transient service and inject with DI in controllers

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Abstractions;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.Mvc.Razor;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc.ViewEngines;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using Microsoft.AspNetCore.Routing;
    using System;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;

    public sealed class RazorViewToStringRenderer : IRazorViewToStringRenderer
    {
        private readonly IRazorViewEngine _viewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;

        public RazorViewToStringRenderer(
            IRazorViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            _viewEngine = viewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        }

        public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
        {
           //If you wish to use the route data in the generated view (e.g. use 
           //the Url helper to construct dynamic links)
           //inject the IHttpContextAccessor then use: var actionContext = new ActionContext(_contextAccessor.HttpContext, _contextAccessor.HttpContext.GetRouteData(), new ActionDescriptor());
          //instead of the line below

            var actionContext = GetActionContext();
            var view = FindView(actionContext, viewName);

            using (var output = new StringWriter())
            {
                var viewContext = new ViewContext(
                    actionContext,
                    view,
                    new ViewDataDictionary<TModel>(
                        metadataProvider: new EmptyModelMetadataProvider(),
                        modelState: new ModelStateDictionary())
                    {
                        Model = model
                    },
                    new TempDataDictionary(
                        actionContext.HttpContext,
                        _tempDataProvider),
                    output,
                    new HtmlHelperOptions());

                await view.RenderAsync(viewContext);

                return output.ToString();
            }
        }

        private IView FindView(ActionContext actionContext, string viewName)
        {
            var getViewResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true);
            if (getViewResult.Success)
            {
                return getViewResult.View;
            }

            var findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: true);
            if (findViewResult.Success)
            {
                return findViewResult.View;
            }

            var searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations);
            var errorMessage = string.Join(
                Environment.NewLine,
                new[] { $"Unable to find view '{viewName}'. The following locations were searched:" }.Concat(searchedLocations)); ;

            throw new InvalidOperationException(errorMessage);
        }

        private ActionContext GetActionContext()
        {
            var httpContext = new DefaultHttpContext();
            httpContext.RequestServices = _serviceProvider;
            return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        }
    }

    public interface IRazorViewToStringRenderer
    {
        Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model);
    }

Solution 4 - C#

Red's answer got me 99% of the way there, but it doesn't work if your views are in an unexpected location. Here's my fix for that.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.IO;
using System.Threading.Tasks;

namespace Example
{
    public static class ControllerExtensions
    {
        public static async Task<string> RenderViewAsync<TModel>(this Controller controller, string viewName, TModel model, bool isPartial = false)
        {
            if (string.IsNullOrEmpty(viewName))
            {
                viewName = controller.ControllerContext.ActionDescriptor.ActionName;
            }

            controller.ViewData.Model = model;

            using (var writer = new StringWriter())
            {
                IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
                ViewEngineResult viewResult = GetViewEngineResult(controller, viewName, isPartial, viewEngine);

                if (viewResult.Success == false)
                {
                    throw new System.Exception($"A view with the name {viewName} could not be found");
                }

                ViewContext viewContext = new ViewContext(
                    controller.ControllerContext,
                    viewResult.View,
                    controller.ViewData,
                    controller.TempData,
                    writer,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

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

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

            }
        }
    }
}

This allows you to use it as as below:

var emailBody = await this.RenderViewAsync("~/My/Different/View.cshtml", myModel);

Solution 5 - C#

The answers above are fine, but need to tweaking to get any tag helpers to work (we need to use the actually http context). Also you will need to explicitly set the layout in the view to get a layout rendered.

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly IHostingEnvironment _env;
    private readonly HttpContext _http;

    public ViewRenderService(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider, IHostingEnvironment env, IHttpContextAccessor ctx)
    {
        _razorViewEngine = razorViewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; _env = env; _http = ctx.HttpContext;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        var actionContext = new ActionContext(_http, new RouteData(), new ActionDescriptor());

        using (var sw = new StringWriter())
        {
            var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
            //var viewResult = _razorViewEngine.GetView(_env.WebRootPath, viewName, false); // For views outside the usual Views folder
            if (viewResult.View == null)
            {
                throw new ArgumentNullException($"{viewName} does not match any available view");
            }
            var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
            {
                Model = model
            };
            var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary, new TempDataDictionary(_http, _tempDataProvider), sw, new HtmlHelperOptions());
            viewContext.RouteData = _http.GetRouteData();
            await viewResult.View.RenderAsync(viewContext);
            return sw.ToString();
        }
    }
}

Solution 6 - C#

I'm probably late to the party, but I've managed to find a solution which works without instantiating a new viewengine (RazorViewEngine), but to actually reuse the view engine already available in each of the Controllers. Also, with this approach, we also get a help from the IntelliSense, when typing the view name, which is really helpful when trying to determine the exact view path.

So, with this approach, your code would look like this:

public override async Task<IActionResult> SignUp()
{
	...
	// send an email notification
	var emailView = View("Emails/SignupNotification"); // simply get the email view
	var emailBody = await RenderToStringAsync(emailView, _serviceProvider); // render it as a string

	SendEmail(emailBody);
	...

	return View();
}

The RenderToStringAsync method, used in this example, looks like this:

private static async Task<string> RenderToStringAsync(ViewResult viewResult, IServiceProvider serviceProvider)
{
	if (viewResult == null) throw new ArgumentNullException(nameof(viewResult));

	var httpContext = new DefaultHttpContext
	{
		RequestServices = serviceProvider
	};

	var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

	using (var stream = new MemoryStream())
	{
		httpContext.Response.Body = stream; // inject a convenient memory stream
		await viewResult.ExecuteResultAsync(actionContext); // execute view result on that stream

		httpContext.Response.Body.Position = 0;
		return new StreamReader(httpContext.Response.Body).ReadToEnd(); // collect the content of the stream
	}
}

If you implement the method as an extension method, your usage can become:

public override async Task<IActionResult> SignUp()
{
	...
	var emailBody = View("Emails/SignupNotification")
		.RenderToStringAsync(_serviceProvider);
	...

	return View();
}

Solution 7 - C#

I've written a clean library Razor.Templating.Core that works with .NET Core 3.0, 3.1 on both web and console app. It's available as NuGet package. After installing, you can call like

var htmlString = await RazorTemplateEngine
                      .RenderAsync("/Views/ExampleView.cshtml", model, viewData);

Note: Above snippet won't work straight away. Please refer the below working guidance on how to apply it.

Complete Working Guidance: https://medium.com/@soundaranbu/render-razor-view-cshtml-to-string-in-net-core-7d125f32c79

Sample Projects: https://github.com/soundaranbu/RazorTemplating/tree/master/examples

Solution 8 - C#

I tried the solution which answered by @Hasan A Yousef in Dotnet Core 2.1, but the csthml do not work well to me. It always throws a NullReferenceException, see screenshot. enter image description here

To solve it, I assign the Html.ViewData.Model to a new object. Here is my code.

@page
@model InviteViewModel 
@{
    var inviteViewModel = Html.ViewData.Model;
}

<p>
    <strong>User Id:</strong> <code>@inviteViewModel.UserId </code>
</p>

Solution 9 - C#

ASP .NET 5

Lots of great answers already been posted here. Basically I had the similar issue where I wanted to read the email template from a razor (.cshtml) page as string and I came across this question and tried all of the answers. I was facing some issues in .NET 5 so I'm posting my slightly tweaked solution here. Thanks.

Startup.cs file

public void ConfigureServices(IServiceCollection services)
{
	...
	services.AddScoped<CustomViewRendererService>();
	...
}

CustomViewRendererService.cs file

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.IO;
using System.Threading.Tasks;

namespace Services
{
    public class CustomViewRendererService
    {
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly ITempDataProvider _tempDataProvider;

        public CustomViewRendererService(
            IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider)
        {
            _razorViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
        }
        
        public async Task<string> RenderViewToStringAsync(ControllerContext actionContext, string viewPath, object model)
        {
            var viewEngineResult = _razorViewEngine.GetView(viewPath, viewPath, false);

            if (viewEngineResult.View == null || (!viewEngineResult.Success))
            {
                throw new ArgumentNullException($"Unable to find view '{viewPath}'");
            }

            var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), actionContext.ModelState);
            viewDictionary.Model = model;

            var view = viewEngineResult.View;
            var tempData = new TempDataDictionary(actionContext.HttpContext, _tempDataProvider);

            using var sw = new StringWriter();
            var viewContext = new ViewContext(actionContext, view, viewDictionary, tempData, sw, new HtmlHelperOptions());
            await view.RenderAsync(viewContext);
            return sw.ToString();
        }
    }
}

In my controller

public class TestController : ControllerBase
{
	private readonly CustomViewRendererService _viewService;

	public TestController(CustomViewRendererService viewService)
	{
		_viewService = viewService;
	}

	public async Task<IActionResult> SendTestEmail2Async()
	{
		var templatePath = "~/Views/Email/Test.cshtml";
		var msg = await _viewService.RenderViewToStringAsync(ControllerContext, templatePath, ("Foo", "Bar"));
		
		return Ok(msg);
	}
}

finally Test.cshtml file

@model (string arg1, string arg2)

<h1>Param: @Model.arg1</h1>
<h1>Param @Model.arg2</h1>

Solution 10 - C#

The link below tackles pretty much the same issue:

https://stackoverflow.com/questions/31905624/where-are-the-controllercontext-and-viewengines-properties-in-mvc-6-controller

In Hasan A Yousef's answer I had to make the same change as in the link above to make it work me:

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using System;
using System.IO;
using System.Threading.Tasks;

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly IHostingEnvironment _env;

    public ViewRenderService(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider, IHostingEnvironment env)
    {
        _razorViewEngine = razorViewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; _env = env;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        using (var sw = new StringWriter()) {
            //var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
            var viewResult = _razorViewEngine.GetView(_env.WebRootPath, viewName, false);
            if (viewResult.View == null) {
                throw new ArgumentNullException($"{viewName} does not match any available view");
            }
            var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) {
                Model = model
            };
            var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary, new TempDataDictionary(actionContext.HttpContext, _tempDataProvider), sw, new HtmlHelperOptions());
            await viewResult.View.RenderAsync(viewContext);
            return sw.ToString();
        }
    }

Solution 11 - C#

Here is another version that suited me better but is still very similar to other versions above. This is Core MVC 3.X.

Controller:

public IActionResult UserClientView(UserClientModel ucm)
{
        try
        {
            
            PartialViewResult pvr = PartialView("_AddEditUserPartial", ucm);

            string s = _helper.ViewToString(this.ControllerContext, pvr, _viewEngine);

            return Ok(s);

        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "UserClientView Error. userName: {userName}", new[] { ucm.UserLogonName });
            return new JsonResult(StatusCode(StatusCodes.Status500InternalServerError));
        }
    }

Helper:

public interface IHelper
{
    string ViewToString(ControllerContext controllerContext, PartialViewResult pvr, ICompositeViewEngine _viewEngine);
}

public class Helper : IHelper
{

    public string ViewToString(ControllerContext controllerContext, PartialViewResult pvr, ICompositeViewEngine _viewEngine)
    {

        using (var writer = new StringWriter())
        {
            ViewEngineResult viewResult = _viewEngine.FindView(controllerContext, pvr.ViewName, false);

            ViewContext viewContext = new ViewContext(
                controllerContext,
                viewResult.View,
                pvr.ViewData,
                pvr.TempData,
                writer,
                new HtmlHelperOptions()
            );

            viewResult.View.RenderAsync(viewContext);

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

}

StartUp:

services.AddSingleton<IHelper, Helper>();

Solution 12 - C#

Microsoft has an excellent article on Controller Testing at https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/testing

Once you have returned a ViewResult then you can get the string content by

var strResult = ViewResult.Content

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHasan A YousefView Question on Stackoverflow
Solution 1 - C#RedView Answer on Stackoverflow
Solution 2 - C#Hasan A YousefView Answer on Stackoverflow
Solution 3 - C#HMZView Answer on Stackoverflow
Solution 4 - C#PharylonView Answer on Stackoverflow
Solution 5 - C#Dave GlassborowView Answer on Stackoverflow
Solution 6 - C#Mladen B.View Answer on Stackoverflow
Solution 7 - C#Soundar AnbuView Answer on Stackoverflow
Solution 8 - C#ChanView Answer on Stackoverflow
Solution 9 - C#Mahabubul HasanView Answer on Stackoverflow
Solution 10 - C#Richard MneyanView Answer on Stackoverflow
Solution 11 - C#user2096582View Answer on Stackoverflow
Solution 12 - C#John DavidsonView Answer on Stackoverflow