Reputation: 3439
I use RazorEngine for parsing of templates in my MVC 6 project like this:
Engine.Razor.RunCompile(File.ReadAllText(fullTemplateFilePath), templateName, null, model);
It works fine for the beta 6. It does not work after upgrading to beta 7 with the error:
MissingMethodException: Method not found: "Void Microsoft.AspNet.Razor.CodeGenerators.GeneratedClassContext.set_ResolveUrlMethodName(System.String)". in RazorEngine.Compilation.CompilerServiceBase.CreateHost(Type templateType, Type modelType, String className)
This is global.json:
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-beta7",
"runtime": "clr",
"architecture": "x64"
}
}
This is project.json:
...
"dependencies": {
"EntityFramework.SqlServer": "7.0.0-beta7",
"EntityFramework.Commands": "7.0.0-beta7",
"Microsoft.AspNet.Mvc": "6.0.0-beta7",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta7",
"Microsoft.AspNet.Authentication.Cookies": "1.0.0-beta7",
"Microsoft.AspNet.Authentication.Facebook": "1.0.0-beta7",
"Microsoft.AspNet.Authentication.Google": "1.0.0-beta7",
"Microsoft.AspNet.Authentication.MicrosoftAccount": "1.0.0-beta7",
"Microsoft.AspNet.Authentication.Twitter": "1.0.0-beta7",
"Microsoft.AspNet.Diagnostics": "1.0.0-beta7",
"Microsoft.AspNet.Diagnostics.Entity": "7.0.0-beta7",
"Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta7",
"Microsoft.AspNet.Server.IIS": "1.0.0-beta7",
"Microsoft.AspNet.Server.WebListener": "1.0.0-beta7",
"Microsoft.AspNet.StaticFiles": "1.0.0-beta7",
"Microsoft.AspNet.Tooling.Razor": "1.0.0-beta7",
"Microsoft.Framework.Configuration.Abstractions": "1.0.0-beta7",
"Microsoft.Framework.Configuration.Json": "1.0.0-beta7",
"Microsoft.Framework.Configuration.UserSecrets": "1.0.0-beta7",
"Microsoft.Framework.Logging": "1.0.0-beta7",
"Microsoft.Framework.Logging.Console": "1.0.0-beta7",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-beta7",
"RazorEngine": "4.2.2-beta1"
},
...
"frameworks": {
"dnx451": { }
},
...
My template is:
@model dynamic
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Registration</title>
</head>
<body>
<p>
Hello, @Model
</p>
</body>
</html>
Does anyone have similar problems? There is another way to parse templates in MVC 6?
Upvotes: 35
Views: 52294
Reputation: 14555
An alternative solution using just ASP.NET Core, no external libraries, and no reflection can be found here: https://weblogs.asp.net/ricardoperes/getting-html-for-a-viewresult-in-asp-net-core. It just requires a ViewResult
and an HttpContext
.
The idea is to pick up a ViewResult
and call some method, say, ToHtml
, to get the rendered output. This method can look like this:
public static class ViewResultExtensions {
public static string ToHtml(this ViewResult result, HttpContext httpContext) {
var feature = httpContext.Features.Get<IRoutingFeature>();
var routeData = feature.RouteData;
var viewName = result.ViewName ?? routeData.Values["action"] as string;
var actionContext = new ActionContext(httpContext, routeData, new ControllerActionDescriptor());
var options = httpContext.RequestServices.GetRequiredService<IOptions<MvcViewOptions>>();
var htmlHelperOptions = options.Value.HtmlHelperOptions;
var viewEngineResult = result.ViewEngine?.FindView(actionContext, viewName, true) ?? options.Value.ViewEngines.Select(x => x.FindView(actionContext, viewName, true)).FirstOrDefault(x => x != null);
var view = viewEngineResult.View;
var builder = new StringBuilder();
using (var output = new StringWriter(builder)) {
var viewContext = new ViewContext(actionContext, view, result.ViewData, result.TempData, output, htmlHelperOptions);
view
.RenderAsync(viewContext)
.GetAwaiter()
.GetResult();
}
return builder.ToString();
}
}
To use it, just do:
var view = this.View(“ViewName”);
var html = view.ToHtml();
Upvotes: 2
Reputation: 21
Extension method for converting partial views to string response.
public static class PartialViewToString
{
public static async Task<string> ToString(this PartialViewResult partialView, ActionContext actionContext)
{
using(var writer = new StringWriter())
{
var services = actionContext.HttpContext.RequestServices;
var executor = services.GetRequiredService<PartialViewResultExecutor>();
var view = executor.FindView(actionContext, partialView).View;
var viewContext = new ViewContext(actionContext, view, partialView.ViewData, partialView.TempData, writer, new HtmlHelperOptions());
await view.RenderAsync(viewContext);
return writer.ToString();
}
}
}
Usage in your controller actions.
public async Task<IActionResult> Index()
{
return await PartialView().ToString(ControllerContext)
}
.NET 5 Implementation
public static async Task<string> ViewToString(this PartialViewResult partialView, Controller controller)
{
using (var writer = new StringWriter())
{
var services = controller.ControllerContext.HttpContext.RequestServices;
var viewEngine = services.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
var viewName = partialView.ViewName ?? controller.ControllerContext.ActionDescriptor.ActionName;
var view = viewEngine.FindView(controller.ControllerContext, viewName, false).View;
var viewContext = new ViewContext(controller.ControllerContext, view, partialView.ViewData, partialView.TempData, writer, new HtmlHelperOptions());
await view.RenderAsync(viewContext);
return writer.ToString();
}
}
Upvotes: 2
Reputation: 4753
To improve on @vlince answer (that wasn't working out of the box for me), here is what I did :
1- Create a base controller that your other controller will inherit
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.IO;
namespace YourNameSpace
{
public class BaseController : Controller
{
protected ICompositeViewEngine viewEngine;
public BaseController(ICompositeViewEngine viewEngine)
{
this.viewEngine = viewEngine;
}
protected string RenderViewAsString(object model, string viewName = null)
{
viewName = viewName ?? ControllerContext.ActionDescriptor.ActionName;
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
IView view = viewEngine.FindView(ControllerContext, viewName, true).View;
ViewContext viewContext = new ViewContext(ControllerContext, view, ViewData, TempData, sw, new HtmlHelperOptions());
view.RenderAsync(viewContext).Wait();
return sw.GetStringBuilder().ToString();
}
}
}
}
2- Inherit the base controller and call the method
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
namespace YourNameSpace
{
public class YourController : BaseController
{
public YourController(ICompositeViewEngine viewEngine) : base(viewEngine) { }
public string Index(int? id)
{
var model = new MyModel { Name = "My Name" };
return RenderViewAsString(model);
}
}
}
Upvotes: 4
Reputation: 21512
Working fine on the following versions 1.0.0
, RC2
Who's targeting aspnetcore RC2, this snippet might help you:
Startup
class https://gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09
// using a Model
string html = view.Render("Emails/Test", new Product("Apple"));
// using a Dictionary<string, object>
var viewData = new Dictionary<string, object>();
viewData["Name"] = "123456";
string html = view.Render("Emails/Test", viewData);
Links in Razor are rendered as relative URL, so this will not work on external views (like emails, etc ...).
As for now am generating the link on the controller and pass it to the view through the ViewModel.
The source is extracted from (Thanks To @pholly): https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs)
Upvotes: 30
Reputation: 753
I found this thread which discusses it: https://github.com/aspnet/Mvc/issues/3091
Someone in the thread created a sample service here: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs
After trial and error I was able to trim the service down so it only needs a valid HttpContext
and a ViewEngine
and I added an overload that doesn't require a model. Views are relative to your application root (they don't have to live in a Views
folder).
You will need to register the service in Startup.cs
and also register HttpContextAccessor
:
//Startup.cs ConfigureServices()
services.AddTransient<ViewRenderService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.IO;
namespace LibraryApi.Services
{
public class ViewRenderService
{
IRazorViewEngine _viewEngine;
IHttpContextAccessor _httpContextAccessor;
public ViewRenderService(IRazorViewEngine viewEngine, IHttpContextAccessor httpContextAccessor)
{
_viewEngine = viewEngine;
_httpContextAccessor = httpContextAccessor;
}
public string Render(string viewPath)
{
return Render(viewPath, string.Empty);
}
public string Render<TModel>(string viewPath, TModel model)
{
var viewEngineResult = _viewEngine.GetView("~/", viewPath, false);
if (!viewEngineResult.Success)
{
throw new InvalidOperationException($"Couldn't find view {viewPath}");
}
var view = viewEngineResult.View;
using (var output = new StringWriter())
{
var viewContext = new ViewContext();
viewContext.HttpContext = _httpContextAccessor.HttpContext;
viewContext.ViewData = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{ Model = model };
viewContext.Writer = output;
view.RenderAsync(viewContext).GetAwaiter().GetResult();
return output.ToString();
}
}
}
}
Example usage:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using LibraryApi.Services;
using System.Dynamic;
namespace LibraryApi.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
ILogger<ValuesController> _logger;
ViewRenderService _viewRender;
public ValuesController(ILogger<ValuesController> logger, ViewRenderService viewRender)
{
_logger = logger;
_viewRender = viewRender;
}
// GET api/values
[HttpGet]
public string Get()
{
//ViewModel is of type dynamic - just for testing
dynamic x = new ExpandoObject();
x.Test = "Yes";
var viewWithViewModel = _viewRender.Render("eNotify/Confirm.cshtml", x);
var viewWithoutViewModel = _viewRender.Render("MyFeature/Test.cshtml");
return viewWithViewModel + viewWithoutViewModel;
}
}
}
Upvotes: 22
Reputation: 1499
Today I've finished with my library that can solve your problem. You can use it out of ASP.NET as it has no dependencies on it
Example:
string content = "Hello @Model.Name. Welcome to @Model.Title repository";
var model = new
{
Name = "John Doe",
Title = "RazorLight"
};
var engine = new RazorLightEngine();
string result = engine.ParseString(content, model);
//Output: Hello John Doe, Welcome to RazorLight repository
More: https://github.com/toddams/RazorLight
Upvotes: 13
Reputation: 6011
In the past, I’ve used the RazorEngine
inside a Class Library because my goal was to render templates from within this Class Library.
From my understanding, you seem to be inside an MVC 6.0 project so why not use a RenderPartialViewToString()
method without having to add the dependency on the RazorEngine
?
Keep in mind, I'm only asking because I'm curious.
For example purposes, from within VS2015, I created a new ASP.NET Web Application and selected the Web Application template from the ASP.NET 5 Preview Templates.
Inside the ViewModels
folder, I created a PersonViewModel
:
public class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", this.FirstName, this.LastName);
}
}
}
I then created a new BaseController
and added a RenderPartialViewToString()
method:
public string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ActionContext.ActionDescriptor.Name;
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
var engine = Resolver.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
ViewEngineResult viewResult = engine.FindPartialView(ActionContext, viewName);
ViewContext viewContext = new ViewContext(ActionContext, viewResult.View, ViewData, TempData, sw,new HtmlHelperOptions());
var t = viewResult.View.RenderAsync(viewContext);
t.Wait();
return sw.GetStringBuilder().ToString();
}
}
Credit goes to @DavidG for his method
Inside the Views-->Shared
folder, I created a new Templates folder in which I’ve added a simple RegistrationTemplate.cshtml
View strongly typed to my PersonViewModel
like so:
@model MyWebProject.ViewModels.PersonViewModel
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Registration</title>
</head>
<body>
<p>
Hello, @Model.FullName
</p>
</body>
</html>
The last step is to make my Controller
inherit from my BaseController
public class MyController : BaseController
And create something like:
public IActionResult Index()
{
var model = new PersonViewModel();
model.FirstName = "Frank";
model.LastName = "Underwood";
var emailbody = base.RenderPartialViewToString("Templates/RegistrationTemplate", model);
return View();
}
Of course, the example above is useless since I do nothing with the variable emailbody
but the idea is to show how it’s used.
At this point, I could've(for example), invoke an EmailService
and pass the emailbody
:
_emailService.SendEmailAsync("[email protected]", "registration", emailbody);
I'm not sure if this is suitable alternative for your current task.
Upvotes: 15
Reputation: 18301
ResolveUrlMethodName
was removed. Therefore in your CreateHost
here you're trying to set a property that doesn't exist :).
We decided to move ~/
handling from core Razor into a TagHelper
implemented in the Microsoft.AspNet.Mvc.Razor
assembly. Here's the commit to the bits that removed the method.
Hopefully this helps.
Upvotes: 3