Renderizar Razor Ver a cadena en ASP.NET Core

Uso RazorEngine para analizar plantillas en mi proyecto MVC 6 de esta manera:

Engine.Razor.RunCompile(File.ReadAllText(fullTemplateFilePath), templateName, null, model); 

Funciona bien para la versión beta 6. No funciona después de actualizar a beta 7 con el error:

MissingMethodException: Método no encontrado: “Void Microsoft.AspNet.Razor.CodeGenerators.GeneratedClassContext.set_ResolveUrlMethodName (System.String)”. en RazorEngine.Comstacktion.CompilerServiceBase.CreateHost (Tipo templateType, Type modelType, String className)

Esto es global.json:

 { "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-beta7", "runtime": "clr", "architecture": "x64" } } 

Esto es 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": { } }, ... 

Mi plantilla es:

 @model dynamic @{ Layout = null; }     Registration   

Hello, @Model

¿Alguien tiene problemas similares? ¿Hay otra forma de analizar las plantillas en MVC 6?

ACTUALIZACIÓN julio de 2016

Funciona bien en las siguientes versiones 1.0.0 , RC2


¿Quién se está orientando a aspnetcore RC2? Este fragmento puede ayudarlo a:

  • Cree un servicio separado, para que pueda usarlo si no está en un contexto de controlador, por ejemplo, desde una línea de comando o en un corredor de cola, etc.
  • Registre este servicio en su contenedor IoC en la clase Startup

https://gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09

Uso

 // using a Model string html = view.Render("Emails/Test", new Product("Apple")); // using a Dictionary var viewData = new Dictionary(); viewData["Name"] = "123456"; string html = view.Render("Emails/Test", viewData); 

Notas

Los enlaces en Razor se representan como URL relativa , por lo que no funcionarán en vistas externas (como correos electrónicos, etc.).

Por ahora estoy generando el enlace en el controlador y lo paso a la vista a través del ViewModel.

Crédito

La fuente se extrae de (Gracias a @pholly): https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs )

En el pasado, utilicé RazorEngine dentro de una Biblioteca de clases porque mi objective era representar plantillas desde esta Biblioteca de clases.

Según tengo entendido, pareces estar dentro de un proyecto de MVC 6.0, ¿por qué no utilizar un método RenderPartialViewToString() sin tener que agregar la dependencia de RazorEngine ?

Tenga en cuenta, solo pregunto porque tengo curiosidad.

Por ejemplo, desde VS2015, creé una nueva aplicación web ASP.NET y seleccioné la plantilla de la aplicación web de las plantillas de vista previa de ASP.NET 5.

Dentro de la carpeta ViewModels , creé un 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); } } } 

Luego creé un nuevo BaseController y agregué un método RenderPartialViewToString() :

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

El crédito va a @DavidG por su método

Dentro de las Views-->Shared Carpeta Views-->Shared , creé una nueva carpeta de Plantillas en la que agregué una vista simple RegistrationTemplate.cshtml fuertemente PersonViewModel a mi PersonViewModel manera:

 @model MyWebProject.ViewModels.PersonViewModel     Registration   

Hello, @Model.FullName

El último paso es hacer que mi Controller herede de mi BaseController

 public class MyController : BaseController 

Y crea algo como:

 public IActionResult Index() { var model = new PersonViewModel(); model.FirstName = "Frank"; model.LastName = "Underwood"; var emailbody = base.RenderPartialViewToString("Templates/RegistrationTemplate", model); return View(); } 

Por supuesto, el ejemplo anterior es inútil ya que no hago nada con la variable emailbody pero la idea es mostrar cómo se usa.

En este punto, podría (por ejemplo) invocar un servicio de EmailService y pasar el emailbody :

 _emailService.SendEmailAsync("test@test.com", "registration", emailbody); 

No estoy seguro si esta es una alternativa adecuada para su tarea actual.

Encontré este hilo que lo discute: https://github.com/aspnet/Mvc/issues/3091

Alguien en el hilo creó un servicio de muestra aquí: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

Después de la prueba y error, pude recortar el servicio para que solo necesite un HttpContext y un ViewEngine y agregué una sobrecarga que no requiere un modelo. Las vistas son relativas a la raíz de la aplicación (no es necesario que vivan en una carpeta Views ).

Deberá registrar el servicio en Startup.cs y también registrar HttpContextAccessor :

 //Startup.cs ConfigureServices() services.AddTransient(); services.AddSingleton(); 
 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(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(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model }; viewContext.Writer = output; view.RenderAsync(viewContext).GetAwaiter().GetResult(); return output.ToString(); } } } } 

Ejemplo de uso:

 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 _logger; ViewRenderService _viewRender; public ValuesController(ILogger 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; } } } 

Hoy he terminado con mi biblioteca que puede resolver su problema. Puedes utilizarlo fuera de ASP.NET ya que no tiene dependencias en él

Ejemplo:

 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 

Más: https://github.com/toddams/RazorLight

ResolveUrlMethodName fue eliminado. Por lo tanto, en tu CreateHost aquí intentas establecer una propiedad que no existe :).

Decidimos mover ~/ manejo desde Razor central a un TagHelper implementado en el ensamblado Microsoft.AspNet.Mvc.Razor . Aquí está la confirmación de los bits que eliminaron el método.

Espero que esto ayude.

Para mejorar la respuesta de @vlince (eso no funcionaba de manera automática), esto es lo que hice:

1- Crea un controlador base que tu otro controlador heredará

 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- Heredar el controlador base y llamar al método

 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; namespace YourNameSpace { public class YourController : BaseController { public TutorialController(ICompositeViewEngine viewEngine) : base(viewEngine) { } public string Index(int? id) { var model = new MyModel { Name = "My Name" }; return RenderViewAsString(model); } } } 

Método de extensión para convertir vistas parciales a respuesta de cadena.

 public static class PartialViewToString { public static async Task ToString(this PartialViewResult partialView, ActionContext actionContext) { using(var writer = new StringWriter()) { var services = actionContext.HttpContext.RequestServices; var executor = services.GetRequiredService(); 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(); } } } 

Uso en sus acciones de controlador.

 public async Task Index() { return await PartialView().ToString(ControllerContext) } 

Una solución alternativa que utiliza solo ASP.NET Core, sin bibliotecas externas y sin reflexión se puede encontrar aquí: https://weblogs.asp.net/ricardoperes/getting-html-for-a-viewresult-in-asp-net- núcleo . Solo requiere un ViewResult y un HttpContext.