502 votos

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

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

¿Es posible en ASP.NET MVC beta?

He intentado varios ejemplos:

1. RenderPartial to String in ASP.NET MVC Beta

Si utilizo este ejemplo, recibo el mensaje "Cannot redirect after HTTP headers have been sent.".

2. MVC Framework: Capturing the output of a view

Si lo utilizo, parezco no poder realizar un redirectToAction, ya que intenta renderizar una vista que puede que no exista. Si retorno la vista, está completamente desordenada y no se ve bien en absoluto.

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

¡Muchas gracias!

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

public ActionResult OrderResult(string ref)
{
    //Obtener la orden
    Order order = OrderService.GetOrder(ref);

    //El ayudante del correo haría la mayor parte del trabajo al obtener la vista como texto
    //Pasar el nombre del control (OrderResultEmail) y el modelo (order)
    string emailView = GetViewForEmail("OrderResultEmail", order);

    //Enviar por correo electrónico la orden
    EmailHelper(order, emailView);
    return View("OrderResult", order);
}

Respuesta aceptada de Tim Scott (cambiada 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();

    //Crear nuestra vista
    viewPage.ViewContext = new ViewContext(controllerContext, new WebFormView(viewPath, masterPath), viewData, tempData);

    //Obtener el contexto de respuesta, vaciarlo y obtener el filtro de respuesta.
    var response = viewPage.ViewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;

    try
    {
        //Poner un nuevo filtro en la respuesta
        filter = new MemoryStream();
        response.Filter = filter;

        //Ahora renderizar la vista en el memorystream y vaciar la respuesta
        viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
        response.Flush();

        //Ahora leer la vista renderizada.
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        //Limpiar.
        if (filter != null)
        {
            filter.Dispose();
        }

        //Ahora reemplazar el filtro de respuesta
        response.Filter = oldFilter;
    }
}

Ejemplo de uso

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

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);

2 votos

¿Cómo se puede usar esto con una vista que sea fuertemente tipada? Es decir, ¿cómo puedo pasar un modelo a la página?

0 votos

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 las cabeceras (porque Flush las envía).

0 votos

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 común.

586voto

Ben Lesh Puntos 39290

Aquí está lo que se me ocurrió, y me está funcionando. He añadido el siguiente método o métodos a mi clase base de controlador. (Supongo que siempre puedes hacer estos métodos estáticos en otro lugar que acepte un controlador como parámetro)

Estilo MVC2 .ascx

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

Estilo Razor .cshtml

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

Editado: añadido código Razor.

0 votos

+1 Estaba teniendo problemas de plantillas anidadas ("el valor de tempData no debe ser nulo") con el método anterior RenderPartialToString. Tu solución funciona perfectamente.

0 votos

Esta no es una gran solución porque te obliga a escribir código como: RenderViewToString ("~/Views/Application/Review.ascx", modelo) lo cual es inconsistente con todo el concepto de enrutamiento.

32 votos

Representar una vista como una cadena es siempre "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ó una calificación negativa.

70voto

Dilip0165 Puntos 655

Esta respuesta no está en mi camino. Originalmente proviene de https://stackoverflow.com/a/2759898/2318354 pero aquí he mostrado la forma de usarlo con la palabra clave "Static" para hacerlo común para todos los controladores.

Para ello, debes crear una clase static en el archivo de la clase. (Supongamos que el nombre de tu 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 puedes llamar a esta clase desde tu controlador agregando el espacio de nombres en tu archivo de controlador de la siguiente manera pasando "this" 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 se puede llamar desde el controlador de la siguiente manera

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

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

1 votos

¡Buena solución! Una cosa, RenderRazorViewToString en realidad es un método de extensión (porque pasas 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("NombreVista", modelo);

0 votos

@Sergey Hmm... Déjame verificar de esa manera, si está bien entonces actualizaré mi respuesta. De todos modos, gracias por tu sugerencia.

0 votos

Dilip0165, obtuve un error de referencia nula en var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerCon‌​text, viewName);. ¿Tienes alguna idea?

33voto

Tim Scott Puntos 7043

Esto funciona para mí:

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

0 votos

Gracias por tu comentario, pero ¿no se usa para renderizar dentro de una vista? ¿Cómo puedo usarlo en el contexto con el que he actualizado la pregunta?

0 votos

Lo siento, aún pensando en Silverlight del año pasado cuyo primer rc fue 0. :) Estoy probando esto hoy. (Tan pronto como trabaje en el formato correcto de la ruta de vista)

0 votos

Esto sigue rompiendo redirecciones en RC1

32voto

LorenzCK Puntos 2819

Encontré una nueva solución que permite renderizar una vista como cadena sin tener que manipular el flujo de respuesta del HttpContext actual (lo cual no permite cambiar el ContentType de la respuesta u otros encabezados).

Básicamente, todo lo que tienes que hacer es crear un HttpContext falso para que la vista se renderice a sí misma:

/// Renderiza una vista como cadena.
public static string RenderViewToString(this Controller controller,
                                        string viewName, object viewData) {
    //Crea un escritor en memoria
    var sb = new StringBuilder();
    var memWriter = new StringWriter(sb);

    //Crea un HttpContext falso para renderear la vista
    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;

    //Utiliza HtmlHelper para renderizar la vista parcial al contexto falso
    var html = new HtmlHelper(new ViewContext(fakeControllerContext,
        new FakeView(), new ViewDataDictionary(), new TempDataDictionary()),
        new ViewPage());
    html.RenderPartial(viewName, viewData);

    //Restaura el contexto
    HttpContext.Current = oldContext;    

    //Limpia la memoria y retorna la salida
    memWriter.Flush();
    return sb.ToString();
}

/// Implementación falsa de IView utilizada para instanciar un HtmlHelper.
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 StringWriter utilizado para escribir la vista en el ViewContext:

//...

//Utiliza HtmlHelper para renderizar la vista parcial al contexto falso
var html = new HtmlHelper(
    new ViewContext(fakeControllerContext, new FakeView(),
        new ViewDataDictionary(), new TempDataDictionary(), memWriter),
    new ViewPage());
html.RenderPartial(viewName, viewData);

//...

0 votos

No hay un método RenderPartial en el objeto HtmlHelper. Esto no es posible - html.RenderPartial(viewName, viewData);

1 votos

En ASP.NET MVC release 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(th‌​is HtmlHelper, string, object). Desconozco si el método se ha añadido en las últimas revisiones de MVC y no estaba presente en las anteriores.

0 votos

Gracias. Solo necesitaba agregar el espacio de nombres System.Web.Mvc.Html a la declaración using (de lo contrario, html.RenderPartial(..) por supuesto no será accesible :))

4voto

Craig Norton Puntos 585

Después de buscar un poco, encontré esta publicación en el blog

http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string

Aquí el autor proporciona una solución creando una subclase al Controller.

Esta no es una mala solución excepto que contamina tu jerarquía de herencia. Así que lo reescribí como un método de extensión en C#.

Esto te permite utilizar las funciones de renderizado sin contaminar tu jerarquía de objetos.

Ver el código: http://learningdds.com/public/ControllerExtension.cs

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X