Return View as String in .NET Core
up vote
24
down vote
favorite
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": { to be used in the
"Microsoft.AspNetCore.Mvc": "1.1.0",
...
},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# asp.net razor asp.net-core .net-core
add a comment |
up vote
24
down vote
favorite
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": { to be used in the
"Microsoft.AspNetCore.Mvc": "1.1.0",
...
},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# asp.net razor asp.net-core .net-core
add a comment |
up vote
24
down vote
favorite
up vote
24
down vote
favorite
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": { to be used in the
"Microsoft.AspNetCore.Mvc": "1.1.0",
...
},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# asp.net razor asp.net-core .net-core
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": { to be used in the
"Microsoft.AspNetCore.Mvc": "1.1.0",
...
},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# asp.net razor asp.net-core .net-core
c# asp.net razor asp.net-core .net-core
edited Sep 4 at 4:23
sbhomra
1,83111738
1,83111738
asked Dec 1 '16 at 13:49
Hasan A Yousef
5,10133064
5,10133064
add a comment |
add a comment |
6 Answers
6
active
oldest
votes
up vote
27
down vote
accepted
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();
}
}
}
}
Add the service to the
Startup.csfile, as:
using WebApplication.Services;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IViewRenderService, ViewRenderService>();
}
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"
}
}
}
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;}
}
Create your
Invite.cshtmlfor example:
@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId
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;}
}
}
9
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.
– philw
Nov 1 '17 at 17:50
2
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.
– martonx
Feb 25 at 22:56
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
add a comment |
up vote
21
down vote
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);
4
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
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!
– LentoMan
May 18 at 14:25
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
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); }`
– LentoMan
Jun 4 at 10:47
If that code does what you need, you could just directly callcontroller.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.
– Red
Jun 4 at 14:08
|
show 3 more comments
up vote
1
down vote
The link below tackles pretty much the same issue:
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();
}
}
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!
– ppumkin
Nov 16 at 14:44
1
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
add a comment |
up vote
1
down vote
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();
}
}
}
1
Note: on azure, I needed to add the following to the Startupservices.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();Weirdly worked fine locally without this ...
– Dave Glassborow
Jan 12 at 11:55
add a comment |
up vote
1
down vote
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.

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>
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.
– gbade_
Nov 27 at 11:33
add a comment |
up vote
-1
down vote
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
1
I goterror CS0117: 'ViewResult' doesn't contain a definition for 'Content'
– Hasan A Yousef
Dec 2 '16 at 10:48
add a comment |
6 Answers
6
active
oldest
votes
6 Answers
6
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
27
down vote
accepted
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();
}
}
}
}
Add the service to the
Startup.csfile, as:
using WebApplication.Services;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IViewRenderService, ViewRenderService>();
}
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"
}
}
}
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;}
}
Create your
Invite.cshtmlfor example:
@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId
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;}
}
}
9
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.
– philw
Nov 1 '17 at 17:50
2
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.
– martonx
Feb 25 at 22:56
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
add a comment |
up vote
27
down vote
accepted
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();
}
}
}
}
Add the service to the
Startup.csfile, as:
using WebApplication.Services;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IViewRenderService, ViewRenderService>();
}
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"
}
}
}
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;}
}
Create your
Invite.cshtmlfor example:
@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId
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;}
}
}
9
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.
– philw
Nov 1 '17 at 17:50
2
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.
– martonx
Feb 25 at 22:56
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
add a comment |
up vote
27
down vote
accepted
up vote
27
down vote
accepted
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();
}
}
}
}
Add the service to the
Startup.csfile, as:
using WebApplication.Services;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IViewRenderService, ViewRenderService>();
}
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"
}
}
}
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;}
}
Create your
Invite.cshtmlfor example:
@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId
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;}
}
}
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();
}
}
}
}
Add the service to the
Startup.csfile, as:
using WebApplication.Services;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IViewRenderService, ViewRenderService>();
}
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"
}
}
}
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;}
}
Create your
Invite.cshtmlfor example:
@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId
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;}
}
}
edited May 23 at 9:48
Glorfindel
16.4k114869
16.4k114869
answered Dec 2 '16 at 13:07
Hasan A Yousef
5,10133064
5,10133064
9
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.
– philw
Nov 1 '17 at 17:50
2
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.
– martonx
Feb 25 at 22:56
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
add a comment |
9
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.
– philw
Nov 1 '17 at 17:50
2
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.
– martonx
Feb 25 at 22:56
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
9
9
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.
– philw
Nov 1 '17 at 17:50
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.
– philw
Nov 1 '17 at 17:50
2
2
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.
– martonx
Feb 25 at 22:56
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.
– martonx
Feb 25 at 22:56
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
check my answer in the post. I pasted a sample code of cshtml.
– Chan
Nov 8 at 5:02
add a comment |
up vote
21
down vote
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);
4
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
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!
– LentoMan
May 18 at 14:25
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
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); }`
– LentoMan
Jun 4 at 10:47
If that code does what you need, you could just directly callcontroller.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.
– Red
Jun 4 at 14:08
|
show 3 more comments
up vote
21
down vote
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);
4
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
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!
– LentoMan
May 18 at 14:25
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
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); }`
– LentoMan
Jun 4 at 10:47
If that code does what you need, you could just directly callcontroller.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.
– Red
Jun 4 at 14:08
|
show 3 more comments
up vote
21
down vote
up vote
21
down vote
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);
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);
edited Jun 4 at 14:09
answered Apr 25 at 13:57
Red
665523
665523
4
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
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!
– LentoMan
May 18 at 14:25
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
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); }`
– LentoMan
Jun 4 at 10:47
If that code does what you need, you could just directly callcontroller.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.
– Red
Jun 4 at 14:08
|
show 3 more comments
4
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
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!
– LentoMan
May 18 at 14:25
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
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); }`
– LentoMan
Jun 4 at 10:47
If that code does what you need, you could just directly callcontroller.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.
– Red
Jun 4 at 14:08
4
4
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
This one should be way more upthere. It looks way more elegant and it worked like a charm for me.
– DGaspar
May 11 at 10:40
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!
– LentoMan
May 18 at 14:25
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!
– LentoMan
May 18 at 14:25
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
@LentoMan could you post an example of the wrapper extension that you mentioned?
– Mike Moore
Jun 2 at 22:18
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); }`
– LentoMan
Jun 4 at 10:47
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); }`
– LentoMan
Jun 4 at 10:47
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.– Red
Jun 4 at 14:08
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.– Red
Jun 4 at 14:08
|
show 3 more comments
up vote
1
down vote
The link below tackles pretty much the same issue:
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();
}
}
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!
– ppumkin
Nov 16 at 14:44
1
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
add a comment |
up vote
1
down vote
The link below tackles pretty much the same issue:
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();
}
}
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!
– ppumkin
Nov 16 at 14:44
1
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
add a comment |
up vote
1
down vote
up vote
1
down vote
The link below tackles pretty much the same issue:
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();
}
}
The link below tackles pretty much the same issue:
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();
}
}
edited Jun 28 '17 at 14:27
answered Jun 28 '17 at 14:17
Richard Mneyan
436168
436168
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!
– ppumkin
Nov 16 at 14:44
1
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
add a comment |
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!
– ppumkin
Nov 16 at 14:44
1
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
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!
– ppumkin
Nov 16 at 14:44
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!
– ppumkin
Nov 16 at 14:44
1
1
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
Your welcome @ppumkin, back then I spent long time figuring this out, and I needed this badly.
– Richard Mneyan
Nov 16 at 15:26
add a comment |
up vote
1
down vote
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();
}
}
}
1
Note: on azure, I needed to add the following to the Startupservices.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();Weirdly worked fine locally without this ...
– Dave Glassborow
Jan 12 at 11:55
add a comment |
up vote
1
down vote
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();
}
}
}
1
Note: on azure, I needed to add the following to the Startupservices.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();Weirdly worked fine locally without this ...
– Dave Glassborow
Jan 12 at 11:55
add a comment |
up vote
1
down vote
up vote
1
down vote
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();
}
}
}
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();
}
}
}
answered Jan 4 at 15:37
Dave Glassborow
1,4141619
1,4141619
1
Note: on azure, I needed to add the following to the Startupservices.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();Weirdly worked fine locally without this ...
– Dave Glassborow
Jan 12 at 11:55
add a comment |
1
Note: on azure, I needed to add the following to the Startupservices.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();Weirdly worked fine locally without this ...
– Dave Glassborow
Jan 12 at 11:55
1
1
Note: on azure, I needed to add the following to the Startup
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); Weirdly worked fine locally without this ...– Dave Glassborow
Jan 12 at 11:55
Note: on azure, I needed to add the following to the Startup
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); Weirdly worked fine locally without this ...– Dave Glassborow
Jan 12 at 11:55
add a comment |
up vote
1
down vote
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.

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>
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.
– gbade_
Nov 27 at 11:33
add a comment |
up vote
1
down vote
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.

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>
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.
– gbade_
Nov 27 at 11:33
add a comment |
up vote
1
down vote
up vote
1
down vote
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.

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>
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.

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>
answered Nov 8 at 4:59
Chan
686621
686621
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.
– gbade_
Nov 27 at 11:33
add a comment |
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.
– gbade_
Nov 27 at 11:33
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.
– gbade_
Nov 27 at 11:33
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.
– gbade_
Nov 27 at 11:33
add a comment |
up vote
-1
down vote
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
1
I goterror CS0117: 'ViewResult' doesn't contain a definition for 'Content'
– Hasan A Yousef
Dec 2 '16 at 10:48
add a comment |
up vote
-1
down vote
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
1
I goterror CS0117: 'ViewResult' doesn't contain a definition for 'Content'
– Hasan A Yousef
Dec 2 '16 at 10:48
add a comment |
up vote
-1
down vote
up vote
-1
down vote
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
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
answered Dec 1 '16 at 20:04
John Davidson
416311
416311
1
I goterror CS0117: 'ViewResult' doesn't contain a definition for 'Content'
– Hasan A Yousef
Dec 2 '16 at 10:48
add a comment |
1
I goterror CS0117: 'ViewResult' doesn't contain a definition for 'Content'
– Hasan A Yousef
Dec 2 '16 at 10:48
1
1
I got
error CS0117: 'ViewResult' doesn't contain a definition for 'Content'– Hasan A Yousef
Dec 2 '16 at 10:48
I got
error CS0117: 'ViewResult' doesn't contain a definition for 'Content'– Hasan A Yousef
Dec 2 '16 at 10:48
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f40912375%2freturn-view-as-string-in-net-core%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown