¿Cómo representar una vista ASP.NET MVC como una cadena?

485

Quiero generar dos vistas diferentes (una como una cadena que se enviará como un correo electrónico) y la otra la página mostrada a un usuario.

¿Es esto posible en ASP.NET MVC beta?

He probado múltiples ejemplos:

1. RenderPartial a String en ASP.NET MVC Beta

Si uso este ejemplo, recibo el mensaje "No se puede redirigir después de que se hayan enviado los encabezados HTTP".

2. MVC Framework: capturando el resultado de una vista

Si uso esto, parece que no puedo hacer un redirectToAction, ya que intenta mostrar una vista que puede no existir. Si devuelvo la vista, está completamente desordenada y no se ve del todo bien.

¿Alguien tiene alguna idea / solución para estos problemas que tengo, o tiene alguna sugerencia para solucionarlos?

¡Muchas gracias!

A continuación se muestra un ejemplo. Lo que intento hacer es crear el método GetViewForEmail :

public ActionResult OrderResult(string ref)
{
    //Get the order
    Order order = OrderService.GetOrder(ref);

    //The email helper would do the meat and veg by getting the view as a string
    //Pass the control name (OrderResultEmail) and the model (order)
    string emailView = GetViewForEmail("OrderResultEmail", order);

    //Email the order out
    EmailHelper(order, emailView);
    return View("OrderResult", order);
}

Respuesta aceptada de Tim Scott (modificada y formateada un poco por mí):

public virtual string RenderViewToString(
    ControllerContext controllerContext,
    string viewPath,
    string masterPath,
    ViewDataDictionary viewData,
    TempDataDictionary tempData)
{
    Stream filter = null;
    ViewPage viewPage = new ViewPage();

    //Right, create our view
    viewPage.ViewContext = new ViewContext(controllerContext, new WebFormView(viewPath, masterPath), viewData, tempData);

    //Get the response context, flush it and get the response filter.
    var response = viewPage.ViewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;

    try
    {
        //Put a new filter into the response
        filter = new MemoryStream();
        response.Filter = filter;

        //Now render the view into the memorystream and flush the response
        viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
        response.Flush();

        //Now read the rendered view.
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        //Clean up.
        if (filter != null)
        {
            filter.Dispose();
        }

        //Now replace the response filter
        response.Filter = oldFilter;
    }
}

Ejemplo de uso

Asumiendo una llamada desde el controlador para obtener el correo electrónico de confirmación del pedido, pasando la ubicación Site.Master.

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
Dan Atkinson
fuente
2
¿Cómo puede usar esto con una vista, que está fuertemente tipada? Es decir. ¿Cómo puedo alimentar un modelo a la página?
Kjensen
No se puede usar esto y crear JsonResult después, porque el tipo de contenido no se puede establecer después de que se hayan enviado los encabezados (porque Flush los envía).
Arnis Lapsa
Porque no hay una única respuesta correcta, supongo. :) Creé una pregunta que era específica para mí, pero sabía que también sería una pregunta muy frecuente.
Dan Atkinson
2
La solución sugerida no funciona en MVC 3.
Kasper Holdum
1
@Qua: La solución sugerida tiene más de dos años. ¡Tampoco esperaría que funcione para MVC 3! Además, hay mejores formas de hacerlo ahora.
Dan Atkinson

Respuestas:

572

Esto es lo que se me ocurrió, y está funcionando para mí. Agregué los siguientes métodos a mi clase base de controlador. (Siempre puedo hacer estos métodos estáticos en otro lugar que acepten un controlador como parámetro, supongo)

Estilo MVC2 .ascx

protected string RenderViewToString<T>(string viewPath, T model) {
  ViewData.Model = model;
  using (var writer = new StringWriter()) {
    var view = new WebFormView(ControllerContext, viewPath);
    var vdd = new ViewDataDictionary<T>(model);
    var viewCxt = new ViewContext(ControllerContext, view, vdd,
                                new TempDataDictionary(), writer);
    viewCxt.View.Render(viewCxt, writer);
    return writer.ToString();
  }
}

Razor .cshtml style

public string RenderRazorViewToString(string viewName, object model)
{
  ViewData.Model = model;
  using (var sw = new StringWriter())
  {
    var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext,
                                                             viewName);
    var viewContext = new ViewContext(ControllerContext, viewResult.View,
                                 ViewData, TempData, sw);
    viewResult.View.Render(viewContext, sw);
    viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
    return sw.GetStringBuilder().ToString();
  }
}

Editar: código agregado de Razor.

Ben Lesh
fuente
31
Representar una vista en una cadena siempre es "inconsistente con todo el concepto de enrutamiento", ya que no tiene nada que ver con el enrutamiento. No estoy seguro de por qué una respuesta que funciona recibió un voto negativo.
Ben Lesh, el
44
Creo que es posible que deba eliminar la "estática" de la declaración del método de la versión Razor, de lo contrario no puede encontrar ControllerContext et al.
Mike
3
Tendrá que implementar su propio método de eliminación para esos espacios en blanco superfluos. La mejor forma en que puedo pensar en la parte superior de mi cabeza es cargar la cadena en un XmlDocument y luego volver a escribirla en una cadena con un XmlWriter, según el enlace que dejé en mi último comentario. Realmente espero que eso ayude.
Ben Lesh
3
Hmm, ¿cómo debo hacer esto usando un controlador WebApi? Cualquier sugerencia sería apreciada
Alexander
3
Hola a todos para usarlo con la palabra clave "Estática" para todos los controladores para que sea común que tenga que hacer una clase estática y dentro de ella debe poner este método con "esto" como parámetro para "ControllerContext". Puedes verlo aquí stackoverflow.com/a/18978036/2318354 .
Dilip0165
68

Esta respuesta no está en camino. Esto es originalmente de https://stackoverflow.com/a/2759898/2318354 pero aquí he mostrado la forma de usarlo con la palabra clave "estática" para que sea común para todos los controladores.

Para eso tienes que hacer la staticclase en el archivo de clase. (Suponga que su nombre de archivo de clase es Utils.cs)

Este ejemplo es para Razor.

Utils.cs

public static class RazorViewToString
{
    public static string RenderRazorViewToString(this Controller controller, string viewName, object model)
    {
        controller.ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
            var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);
            viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View);
            return sw.GetStringBuilder().ToString();
        }
    }
}

Ahora puede llamar a esta clase desde su controlador agregando NameSpace en su archivo de controlador de la siguiente manera pasando "esto" como parámetro al controlador.

string result = RazorViewToString.RenderRazorViewToString(this ,"ViewName", model);

Como sugerencia dada por @Sergey, este método de extensión también puede llamar desde cotroller como se indica a continuación

string result = this.RenderRazorViewToString("ViewName", model);

Espero que esto sea útil para que el código sea limpio y ordenado.

Dilip0165
fuente
1
Buena solución! Una cosa, RenderRazorViewToString es en realidad un método de extensión (porque pasa el parámetro del controlador con esta palabra clave), por lo que este método de extensión se puede llamar de esta manera: this.RenderRazorViewToString ("ViewName", modelo);
Sergey
@Sergey Hmmm ... Déjame comprobar de esa manera, si está bien, actualizaré mi respuesta. De todos modos, gracias por su sugerencia.
Dilip0165
Dilip0165, obtuve un error de referencia nulo en var viewResult = ViewEngines.Engines.FindPartialView (controller.ControllerContext, viewName) ;. ¿Tiene alguna idea?
CB4
@ CB4 Creo que podría ser el problema de "viewName" que está pasando a la función. Debe pasar "viewName" con la ruta completa según la estructura de su carpeta. Así que mira esto.
Dilip0165
1
@Sergey Gracias por su sugerencia, he actualizado mi respuesta según su sugerencia, que es totalmente correcta
Dilip0165
32

Esto funciona para mi:

public virtual string RenderView(ViewContext viewContext)
{
    var response = viewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;
    Stream filter = null;
    try
    {
        filter = new MemoryStream();
        response.Filter = filter;
        viewContext.View.Render(viewContext, viewContext.HttpContext.Response.Output);
        response.Flush();
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        if (filter != null)
        {
            filter.Dispose();
        }
        response.Filter = oldFilter;
    }
}
Tim Scott
fuente
Gracias por tu comentario, pero ¿no se usa eso para renderizar dentro de una vista? ¿Cómo podría usarlo en el contexto con el que actualicé la pregunta?
Dan Atkinson el
Lo siento, sigo pensando en Silverlight el año pasado cuyo primer rc fue 0. :) Hoy voy a intentarlo. (Tan pronto como resuelva el formato correcto de la ruta de visualización)
NikolaiDante
Esto todavía rompe las redirecciones en RC1
derrotado
derrotado: No, no lo hace. Si es así, entonces estás haciendo algo mal.
Dan Atkinson
Combiné esto con stackoverflow.com/questions/520863/… , agregué conocimiento de ViewEnginesCollection, traté de hacer estallar una vista parcial y obtuve este stackoverflow.com/questions/520863/… . : E
Arnis Lapsa
31

Encontré una nueva solución que representa una vista de cadena sin tener que meterse con el flujo de Respuesta del HttpContext actual (que no le permite cambiar el ContentType de la respuesta u otros encabezados).

Básicamente, todo lo que debe hacer es crear un HttpContext falso para que la vista se represente:

/// <summary>Renders a view to string.</summary>
public static string RenderViewToString(this Controller controller,
                                        string viewName, object viewData) {
    //Create memory writer
    var sb = new StringBuilder();
    var memWriter = new StringWriter(sb);

    //Create fake http context to render the view
    var fakeResponse = new HttpResponse(memWriter);
    var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse);
    var fakeControllerContext = new ControllerContext(
        new HttpContextWrapper(fakeContext),
        controller.ControllerContext.RouteData,
        controller.ControllerContext.Controller);

    var oldContext = HttpContext.Current;
    HttpContext.Current = fakeContext;

    //Use HtmlHelper to render partial view to fake context
    var html = new HtmlHelper(new ViewContext(fakeControllerContext,
        new FakeView(), new ViewDataDictionary(), new TempDataDictionary()),
        new ViewPage());
    html.RenderPartial(viewName, viewData);

    //Restore context
    HttpContext.Current = oldContext;    

    //Flush memory and return output
    memWriter.Flush();
    return sb.ToString();
}

/// <summary>Fake IView implementation used to instantiate an HtmlHelper.</summary>
public class FakeView : IView {
    #region IView Members

    public void Render(ViewContext viewContext, System.IO.TextWriter writer) {
        throw new NotImplementedException();
    }

    #endregion
}

Esto funciona en ASP.NET MVC 1.0, junto con ContentResult, JsonResult, etc. (cambiar los encabezados en el HttpResponse original no arroja la excepción "El servidor no puede establecer el tipo de contenido después de que se hayan enviado los encabezados HTTP ").

Actualización: en ASP.NET MVC 2.0 RC, el código cambia un poco porque tenemos que pasar el StringWriterusado para escribir la vista en ViewContext:

//...

//Use HtmlHelper to render partial view to fake context
var html = new HtmlHelper(
    new ViewContext(fakeControllerContext, new FakeView(),
        new ViewDataDictionary(), new TempDataDictionary(), memWriter),
    new ViewPage());
html.RenderPartial(viewName, viewData);

//...
LorenzCK
fuente
No existe un método RenderPartial en el objeto HtmlHelper. Esto no es posible: html.RenderPartial (viewName, viewData);
MartinF
1
En ASP.NET MVC versión 1.0 hay un par de métodos de extensión RenderPartial. El que estoy usando en particular es System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial (este HtmlHelper, cadena, objeto). No sé si el método se ha agregado en las últimas revisiones de MVC y no estuvo presente en las anteriores.
LorenzCK
Gracias. Solo necesitaba agregar el espacio de nombres System.Web.Mvc.Html a la declaración de uso (de lo contrario html.RenderPartial (..) por supuesto no será accesible :))
MartinF
¿Alguien tiene esto trabajando con el RC de MVC2? Agregaron un parámetro adicional Textwriter a ViewContext. Intenté agregar un nuevo StringWriter (), pero no funcionó.
beckelmw
1
@beckelmw: Actualicé la respuesta. Debe pasar el original StringWriterque está utilizando para escribir en el StringBuilder, no una nueva instancia o la salida de la vista se perderá.
LorenzCK
11

Este artículo describe cómo representar una vista en una cadena en diferentes escenarios:

  1. MVC Controller llamando a otro de sus propios métodos de acción
  2. Controlador MVC llamando a un Método de acción de otro Controlador MVC
  3. Controlador WebAPI que llama a un ActionMethod de un controlador MVC

La solución / código se proporciona como una clase llamada ViewRenderer . Es parte de WestwindToolkit de Rick Stahl en GitHub .

Uso (3. - Ejemplo de WebAPI):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
Jenny O'Reilly
fuente
3
También como paquete NuGet West Wind Web MVC Utilities ( nuget.org/packages/Westwind.Web.Mvc ). Como beneficio adicional, el renderizador de vistas no solo puede representar vistas parciales, sino también la vista completa, incluido el diseño. Artículo de blog con código: weblog.west-wind.com/posts/2012/May/30/…
Jeroen K
Sería genial si esto se dividiera en paquetes más pequeños. El paquete Nuget realiza muchos cambios en su web.config y agrega archivos js a su proyecto, que luego no se limpian cuando lo desinstala: /
Josh Noe
8

Si desea renunciar a MVC por completo, evitando así todo el desorden de HttpContext ...

using RazorEngine;
using RazorEngine.Templating; // For extension methods.

string razorText = System.IO.File.ReadAllText(razorTemplateFileLocation);
string emailBody = Engine.Razor.RunCompile(razorText, "templateKey", typeof(Model), model);

Esto utiliza el increíble Razor Engine de código abierto aquí: https://github.com/Antaris/RazorEngine

Josh Noe
fuente
¡Agradable! ¿Sabes si hay un motor de análisis similar para la sintaxis de WebForms? Todavía tengo algunas vistas antiguas de WebForms que aún no se pueden mover a Razor.
Dan Atkinson
Hola, tuve muchos problemas con el razorengine, y el informe de errores no es muy bueno. No creo que el ayudante de Url sea compatible
Layinka
@Layinka No estoy seguro de si esto ayuda, pero la mayor parte de la información de error pertenece a CompilerErrorsla excepción.
Josh Noe
5

obtienes la vista en cadena de esta manera

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");

    if (model != null)
        ViewData.Model = model;

    using (StringWriter sw = new StringWriter())
    {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
    }
}

Llamamos a este método de dos maneras.

string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", null)

O

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)
Jayesh Patel
fuente
4

Consejo adicional para ASP NET CORE:

Interfaz:

public interface IViewRenderer
{
  Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model);
}

Implementación:

public class ViewRenderer : IViewRenderer
{
  private readonly IRazorViewEngine viewEngine;

  public ViewRenderer(IRazorViewEngine viewEngine) => this.viewEngine = viewEngine;

  public async Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model)
  {
    ViewEngineResult viewEngineResult = this.viewEngine.FindView(controller.ControllerContext, name, false);

    if (!viewEngineResult.Success)
    {
      throw new InvalidOperationException(string.Format("Could not find view: {0}", name));
    }

    IView view = viewEngineResult.View;
    controller.ViewData.Model = model;

    await using var writer = new StringWriter();
    var viewContext = new ViewContext(
       controller.ControllerContext,
       view,
       controller.ViewData,
       controller.TempData,
       writer,
       new HtmlHelperOptions());

       await view.RenderAsync(viewContext);

       return writer.ToString();
  }
}

Registro en Startup.cs

...
 services.AddSingleton<IViewRenderer, ViewRenderer>();
...

Y uso en el controlador:

public MyController: Controller
{
  private readonly IViewRenderer renderer;
  public MyController(IViewRendere renderer) => this.renderer = renderer;
  public async Task<IActionResult> MyViewTest
  {
    var view = await this.renderer.RenderAsync(this, "MyView", model);
    return new OkObjectResult(view);
  }
}
Marcin
fuente
3

Estoy usando MVC 1.0 RTM y ninguna de las soluciones anteriores funcionó para mí. Pero este sí:

Public Function RenderView(ByVal viewContext As ViewContext) As String

    Dim html As String = ""

    Dim response As HttpResponse = HttpContext.Current.Response

    Using tempWriter As New System.IO.StringWriter()

        Dim privateMethod As MethodInfo = response.GetType().GetMethod("SwitchWriter", BindingFlags.NonPublic Or BindingFlags.Instance)

        Dim currentWriter As Object = privateMethod.Invoke(response, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, New Object() {tempWriter}, Nothing)

        Try
            viewContext.View.Render(viewContext, Nothing)
            html = tempWriter.ToString()
        Finally
            privateMethod.Invoke(response, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, New Object() {currentWriter}, Nothing)
        End Try

    End Using

    Return html

End Function
Jeremy Bell
fuente
2

Vi una implementación para MVC 3 y Razor desde otro sitio web, funcionó para mí:

    public static string RazorRender(Controller context, string DefaultAction)
    {
        string Cache = string.Empty;
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        System.IO.TextWriter tw = new System.IO.StringWriter(sb); 

        RazorView view_ = new RazorView(context.ControllerContext, DefaultAction, null, false, null);
        view_.Render(new ViewContext(context.ControllerContext, view_, new ViewDataDictionary(), new TempDataDictionary(), tw), tw);

        Cache = sb.ToString(); 

        return Cache;

    } 

    public static string RenderRazorViewToString(string viewName, object model)
    {

        ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
            var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
            viewResult.View.Render(viewContext, sw);
            return sw.GetStringBuilder().ToString();
        }
    } 

    public static class HtmlHelperExtensions
    {
        public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
        {
            ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName);

            if (result.View != null)
            {
                StringBuilder sb = new StringBuilder();
                using (StringWriter sw = new StringWriter(sb))
                {
                    using (HtmlTextWriter output = new HtmlTextWriter(sw))
                    {
                        ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output);
                        result.View.Render(viewContext, output);
                    }
                }
                return sb.ToString();
            } 

            return String.Empty;

        }

    }

Más sobre Razor render- MVC3 View Render to String

Adamy
fuente
Sí, esto es más o menos una copia de la respuesta aceptada. :)
Dan Atkinson
2

Para representar una vista de una cadena en la capa de servicio sin tener que pasar ControllerContext, hay un buen artículo de Rick Strahl aquí http://www.codemag.com/Article/1312081 que crea un controlador genérico. Resumen del código a continuación:

// Some Static Class
public static string RenderViewToString(ControllerContext context, string viewPath, object model = null, bool partial = false)
{
    // first find the ViewEngine for this view
    ViewEngineResult viewEngineResult = null;
    if (partial)
        viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
    else
        viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);

    if (viewEngineResult == null)
        throw new FileNotFoundException("View cannot be found.");

    // get the view and attach the model to view data
    var view = viewEngineResult.View;
    context.Controller.ViewData.Model = model;

    string result = null;

    using (var sw = new StringWriter())
    {
        var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
        view.Render(ctx, sw);
        result = sw.ToString();
    }

    return result;
}

// In the Service Class
public class GenericController : Controller
{ }

public static T CreateController<T>(RouteData routeData = null) where T : Controller, new()
{
    // create a disconnected controller instance
    T controller = new T();

    // get context wrapper from HttpContext if available
    HttpContextBase wrapper;
    if (System.Web.HttpContext.Current != null)
        wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
    else
        throw new InvalidOperationException("Cannot create Controller Context if no active HttpContext instance is available.");

    if (routeData == null)
        routeData = new RouteData();

    // add the controller routing if not existing
    if (!routeData.Values.ContainsKey("controller") &&
        !routeData.Values.ContainsKey("Controller"))
        routeData.Values.Add("controller", controller.GetType().Name.ToLower().Replace("controller", ""));

    controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
    return controller;
}

Luego, para representar la Vista en la clase de Servicio:

var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);
RickL
fuente
1

Consejo rapido

Para un modelo fuertemente tipado simplemente agréguelo a la propiedad ViewData.Model antes de pasar a RenderViewToString. p.ej

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
longhairedsi
fuente
0

Para repetir una pregunta más desconocida, eche un vistazo a MvcIntegrationTestFramework .

Le ahorra escribir sus propios ayudantes para transmitir el resultado y está demostrado que funciona lo suficientemente bien. Supongo que esto estaría en un proyecto de prueba y, como beneficio adicional, tendría las otras capacidades de prueba una vez que tenga esta configuración. La molestia principal probablemente sería resolver la cadena de dependencia.

 private static readonly string mvcAppPath = 
     Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory 
     + "\\..\\..\\..\\MyMvcApplication");
 private readonly AppHost appHost = new AppHost(mvcAppPath);

    [Test]
    public void Root_Url_Renders_Index_View()
    {
        appHost.SimulateBrowsingSession(browsingSession => {
            RequestResult result = browsingSession.ProcessRequest("");
            Assert.IsTrue(result.ResponseText.Contains("<!DOCTYPE html"));
        });
}
paloma
fuente
0

Aquí hay una clase que escribí para hacer esto para ASP.NETCore RC2. Lo uso para generar correo electrónico html con Razor.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using System.IO;
using System.Threading.Tasks;

namespace cloudscribe.Web.Common.Razor
{
    /// <summary>
    /// the goal of this class is to provide an easy way to produce an html string using 
    /// Razor templates and models, for use in generating html email.
    /// </summary>
    public class ViewRenderer
    {
        public ViewRenderer(
            ICompositeViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IHttpContextAccessor contextAccesor)
        {
            this.viewEngine = viewEngine;
            this.tempDataProvider = tempDataProvider;
            this.contextAccesor = contextAccesor;
        }

        private ICompositeViewEngine viewEngine;
        private ITempDataProvider tempDataProvider;
        private IHttpContextAccessor contextAccesor;

        public async Task<string> RenderViewAsString<TModel>(string viewName, TModel model)
        {

            var viewData = new ViewDataDictionary<TModel>(
                        metadataProvider: new EmptyModelMetadataProvider(),
                        modelState: new ModelStateDictionary())
            {
                Model = model
            };

            var actionContext = new ActionContext(contextAccesor.HttpContext, new RouteData(), new ActionDescriptor());
            var tempData = new TempDataDictionary(contextAccesor.HttpContext, tempDataProvider);

            using (StringWriter output = new StringWriter())
            {

                ViewEngineResult viewResult = viewEngine.FindView(actionContext, viewName, true);

                ViewContext viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewData,
                    tempData,
                    output,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

                return output.GetStringBuilder().ToString();
            }
        }
    }
}
Joe Audette
fuente
0

Encontré una mejor manera de representar la página de vista de afeitar cuando recibí un error con los métodos anteriores, esta solución para el entorno de formularios web y el entorno de mvc. No se necesita controlador.

Aquí está el ejemplo de código, en este ejemplo simulé una acción mvc con un controlador http asíncrono:

    /// <summary>
    /// Enables processing of HTTP Web requests asynchronously by a custom HttpHandler that implements the IHttpHandler interface.
    /// </summary>
    /// <param name="context">An HttpContext object that provides references to the intrinsic server objects.</param>
    /// <returns>The task to complete the http request.</returns>
    protected override async Task ProcessRequestAsync(HttpContext context)
    {
        if (this._view == null)
        {
            this.OnError(context, new FileNotFoundException("Can not find the mvc view file.".Localize()));
            return;
        }
        object model = await this.LoadModelAsync(context);
        WebPageBase page = WebPageBase.CreateInstanceFromVirtualPath(this._view.VirtualPath);
        using (StringWriter sw = new StringWriter())
        {
            page.ExecutePageHierarchy(new WebPageContext(new HttpContextWrapper(context), page, model), sw);
            await context.Response.Output.WriteAsync(sw.GetStringBuilder().ToString());
        }
    }
dexiang
fuente