Return View as String in .NET Core

72,153

Solution 1

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 2

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 3

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

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

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();
        }
    }
}
Share:
72,153
Hasan A Yousef
Author by

Hasan A Yousef

Industrial Engineer, with a slogan "If you can automate it, do not do it". Interested in all types of automation that facilitate life. Work officially as Supply Chain Manager, where Data is the main driver of availability. Combining my IT skills and interest with my work expertise, proud to deliver the best possible results for my organization.

Updated on July 08, 2022

Comments

  • Hasan A Yousef
    Hasan A Yousef almost 2 years

    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.

  • Hasan A Yousef
    Hasan A Yousef over 7 years
    I got error CS0117: 'ViewResult' doesn't contain a definition for 'Content'
  • philw
    philw over 6 years
    That pretty much works for Core 2.0, with two exceptions: (1) _razorViewEngine.FindView doesn't work on absolute paths, and I at least need those because the standard template apps don't use Views folders which it assumes. Tto use his is documented as "by design" on the Core 2.0 GitHub site, and the solution is to use _razorViewEngine.GetView, which supports absolute paths. (2) that preserveCompilationContext (not in the original article) isn't explained - why do you need it? It's not clear where to put it with COre 2.0, and it seems to work without it.
  • Dave Glassborow
    Dave Glassborow over 6 years
    Note: on azure, I needed to add the following to the Startup services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); Weirdly worked fine locally without this ...
  • martonx
    martonx about 6 years
    With this code ViewData["Message"] = "Your application description page."; will be null in the view. Why? Could anybody post a fixed version which contains correct handling of ViewData, not just view model.
  • DGaspar
    DGaspar almost 6 years
    This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
  • LentoMan
    LentoMan almost 6 years
    Thanks, excellent solution! The only thing I changed was adding an additional wrapped extension method for partial views. I actually ran into a bit of a problem when using the previously injected ViewRenderService since it could access partial views from the view tree of other controllers. Those views did render correctly, but would never automatically recompile during debug, moving them to Shared views solved the issue!
  • Mike Moore
    Mike Moore almost 6 years
    @LentoMan could you post an example of the wrapper extension that you mentioned?
  • LentoMan
    LentoMan almost 6 years
    Just add another method in the same extension class that Red suggested: ` public static async Task<string> RenderPartialViewAsync<TModel>(this Controller controller, string viewName, TModel model) { return await controller.RenderViewAsync(viewName, model, true); }`
  • Red
    Red almost 6 years
    If that code does what you need, you could just directly call controller.RenderViewAsync(viewName, model, true);. I don't see you're gaining anything, other than making it clearer that it's a Partial, but that could be important in your situation.
  • LentoMan
    LentoMan almost 6 years
    You are not wrong, one could also use named arguments to make it more readable. But in my case, I mainly use partials, so it is more about making it easier to use and matching the result methods already available on the controller.
  • Janis Veinbergs
    Janis Veinbergs over 5 years
    I would prefer throwing exception if view could not be found rather than getting surprise result: return $"A view with the name {viewName} could not be found";
  • Chan
    Chan over 5 years
    check my answer in the post. I pasted a sample code of cshtml.
  • Piotr Kula
    Piotr Kula over 5 years
    Thanks- I was looking for something like this to merge a model in HTML using Razor. Not sure why MS wont just make a simpler way of this doing. Although.. its much easier than in MVC 1. Great solution and thanks for sharing the GetView change!
  • Richard Mneyan
    Richard Mneyan over 5 years
    Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
  • gbade_
    gbade_ over 5 years
    I tried your method and now I'm getting this - Executed action Controllers.PortfolioController.PrintStatement in [ERR] An unhandled exception has occurred while executing the request System.NullReferenceException: Object reference not set to an instance of an object. at AspNetCore._Views_Portfolio_PrintStatement_cshtml. in PrintStatement.cshtml:line 248. --------------Prior to that, I was getting the nullreference error at line 0 in the cshtml file.
  • Pharylon
    Pharylon over 5 years
    This doesn't work if your views are in an unusual location. I posted a fix for that in my own answer.
  • Yogi
    Yogi over 5 years
    This does work, but the issue is the @page directive that marks it as a Razor Page. And Razor Pages work differently than Razor Views. See this other SO solution for a way to add the Model to a Razor Page: stackoverflow.com/a/49275145/943435
  • scgough
    scgough over 5 years
    This is nice. I'm having a small issue though. I want to get the HTML string for ControllerB.Action1 from ControllerA.Action1. The code seems to successfully render the view to a string but it doesn't seem to execute the ControllerB.Action1 method before hand. Is there a way to get this to happen? I am using a dynamic partial view, not a static shared partial view.
  • jonmeyer
    jonmeyer over 5 years
    @Red 3 changes i would recommend: 1. use GetRequiredService instead of GetService 2. Resolve generic iLogger<> to log when success is false to record the error message 3. Resolve IOptions<HtmlHelperOptions> instead of using new HtmlHelperOptions()
  • c-sharp
    c-sharp over 5 years
    How do you write a unit test for this service? I mean, how do we get the dependencies?
  • Felype
    Felype over 5 years
    This is great, though I had to change the interface from receiving an object model to Receiving the ViewDataDictionary ViewData instead, and then instead of initializing the var viewDictionary as you did, I simply use the ViewData. This way I get the Model, ViewBag, ViewData and everything else and that the controller context had, just as if I was calling View()
  • haugan
    haugan about 5 years
    I get this error, trying this in .NET Core 2.2 from a Web API controller: "Unable to resolve service for type 'Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine' while attempting to activate 'xxx.Api.ViewRenderService'." I think I've followed the instructions form Hasan's answer to the point. What could be wrong?
  • Faraz Ahmed
    Faraz Ahmed almost 5 years
    Previously I have coded with different technique it works in development but not in IIS, finally your code work in both dev and live.
  • Adrita Sharma
    Adrita Sharma almost 5 years
    What should be passed in controller in (this Controller controller, string viewName, TModel model, bool partial = false). I am getting compile time error.
  • Red
    Red almost 5 years
    @Adrita-Sharma - it's a controller extension, so you don't pass in your controller, it's a function added to all controllers. So whatever controller produces the view you want as a string, use the implementation code in the answer there.
  • Andrew
    Andrew over 4 years
    This works great for me. I changed this so that it gets the ActionContext via the IActionContextAccessor interface.
  • gtrak
    gtrak over 4 years
    This answer has an issue, since it mutates the controllers own model controller.ViewData.Model = model;, we have to undo any changes or it breaks the followup view rendering. I wrapped this mutation and rendering in a try-finally to fix it.
  • Watson
    Watson over 4 years
    How do you let javascript run to completion? Any view returned does not run js - for example: <div id="Test"></div> and document.getElementById("Test").innerHTML = "whatever". Returned content does not change inner div html.
  • Red
    Red over 4 years
    This is run on the server, so no client code will be executed. If you can't refactor to use the model data I think you're out of luck
  • rvnlord
    rvnlord over 4 years
    This is the correct answer @Dave Glassborow, because (at least in Core 3.0) if you don't inject HttpContext and create the default one, you'll get Invalid URI: The format of the URI could not be determined on executing RenderAsync(), at least if you got custom View/Pages paths. Thank you.
  • Lion
    Lion about 4 years
    Its important to set Layout = null; when rendering PartialViews (e.g. for Ajax calls) with this method. Then it works well!
  • DLeh
    DLeh almost 4 years
    If you are trying to render documents that reference partials with relative paths, set the actionContext.RouteData.Values["controller"] = controllerName; , where controllerName is the base of the doc you want to render. In this example it would be Email from the Email/Invite path.
  • beggarboy
    beggarboy almost 4 years
    This comment sadly, was only half the truth for me. While this code does the actual job of converting razor pages into an html string, the setup around has to fit, otherwise the compiler will complain. Make sure you add services.AddControllersWithViews(); and services.AddRazorPages();to your Startup.cs. Make sure you add the DI correctly aswell below: services.AddTransient<IRazorViewToStringRenderer, RazorViewToStringRenderer>(); AND make sure your project is on .Net Core 3.1. I have a feeling this has problems working on .net core 3. Correct me if I am wrong. Hopefully this helps some1
  • HMZ
    HMZ almost 4 years
    @beggarboy i have tried it on asp.net core 2.2 as well, and what you just said is pretty standard stuff except that you only need to add ControllersWithViews or RazorPages (according to your preference).
  • beggarboy
    beggarboy almost 4 years
    You're not wrong, it is pretty standard stuff, but it's draining enough trying to patch specific things like this together from multiple sources over the web desperately trying to get it to work over the multiple iterations of .net core and the breaking changes inbetween. I have tried 4 approaches before yours, and was always met with missing, unresolvable dependencies or implementation issues, so I thought it would be nice sparing somebody else the hour or two trying to figure out very simple things they might miss :)
  • HMZ
    HMZ almost 4 years
    @beggarboy I agree, it is a pain sometimes, and this feature could have been easily implemented in the framework since the engine is already here.
  • Jason Ayer
    Jason Ayer almost 4 years
    @DLeh - I'm attempting this in a ASP.NET Core 3.1 project and my views root directory is at ~/Views. In my example would I need to add actionContext.RouteData["controller"] = "Views";?
  • Kalpesh Rajai
    Kalpesh Rajai almost 4 years
    I used this solution,, Works fine.. But localization does not work. Any idea? what I am missing ?
  • NickG
    NickG over 3 years
    Why am I finding that controller.HttpContext is null when this code is clearly working for everyone else?
  • mohrtan
    mohrtan over 3 years
    Thanks, Hasan. This got me 98% of the way too. I had to use @philw's adjustment to "GetView" and then had 1 more issue. I couldn't use DefaultHttpContext, so I used the info from this answer to get the current HttpContext.
  • Hasan A Yousef
    Hasan A Yousef over 3 years
    Glad it helped though it is 4 years old, where .Net Core was at version 1 :)
  • Canada Wan
    Canada Wan over 3 years
    Works on Net Core 3.1. Thanks Red!
  • Auspex
    Auspex over 3 years
    Bonus points for including your using statements. So many examples assume we're all so familiar with this that it's unnecessary
  • Auspex
    Auspex over 3 years
    And I've seen ViewResults with Content but Content is null. I think it doesn't contain what you think it does.
  • Jack Miller
    Jack Miller over 3 years
    Works but IHostingEnvironment is obsolete. Just replace with IWebHostEnvironment.
  • JohnnyJaxs
    JohnnyJaxs over 3 years
    I like your extension solution. You save me time.
  • Dave B
    Dave B about 3 years
    Your code worked for me except for partial views. Your answer says "as well as returning HTML of partial views via Ajax" but your code is missing a boolean that can toggle between a full view and a partial view. You hardcoded isMainPage: true in the method private IView FindView. In the answer provided by @Red, an optional parameter bool partial = false can be added to the RenderViewToStringAsync method. The partial parameter is then passed to the private IView FindView method. In that method swap out isMainPage: true with isMainPage: !partial.
  • HMZ
    HMZ about 3 years
    @DaveB I suspect your partial view doesn't have a property Layout = null. You can add it to your "Partial view" and just render using the method above or do it the way you mentioned and add a partial view toggle.
  • Dave B
    Dave B about 3 years
    @HMZ You're right, my partial views do not have Layout = null so ASP.NET Core was grabbing a layout from the nearest ViewStart.cshtml file. I added the partial view toggle to avoid adding Layout = null to my partial views.
  • Auspex
    Auspex almost 3 years
    It's too bad you didn't show how _helper & _viewEngine were defined. I'm assuming _helper is from injection of IHelper but I'm at a complete loss to figure out your _viewEngine
  • user2096582
    user2096582 almost 3 years
    _viewEngine comes from the controller DI as "ICompositeViewEngine viewEngine" I then assign it to _viewEngine in the constructor. _helper you are correct it is in the DI constructor as well.
  • Ofer Zelig
    Ofer Zelig over 2 years
    Good project. Worth noting that as of now (.NET Core 5.0.400), for the class library that hosts the Razor templates, one needs to create a project using the 'Razor Class Library' template and tick 'Support pages and views' in the wizard, otherwise Razor files can't be created.
  • ooXei1sh
    ooXei1sh over 2 years
    @HMZ, This won't work with invalid ModelState if you're rendering partial forms with validation errors. Seems like passing in a ModelState is working tho. modelState: modelState ?? new ModelStateDictionary()
  • Matthew M.
    Matthew M. over 2 years
    Very elegant solution and helpful. Some minor changes to address nullability in C#9, but otherwise works out of the box.