Return View as String in .NET Core Return View as String in .NET Core asp.net asp.net

Return View as String in .NET Core


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);


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;}    } }


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);    }