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

Resuelto Dan Atkinson asked hace 15 años • 16 respuestas

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

¿Es esto posible en ASP.NET MVC beta?

He probado varios ejemplos:

1. RenderPartial a cadena en ASP.NET MVC Beta

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

2. MVC Framework: capturando el resultado de una vista

Si uso esto, parece que no puedo realizar una redirección a acción, ya que intenta representar una vista que puede no existir. Si devuelvo la vista, estará completamente desordenada y no se verá nada bien.

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

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

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

Uso de ejemplo

Suponiendo una llamada del controlador para recibir el correo electrónico 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);
Dan Atkinson avatar Jan 27 '09 18:01 Dan Atkinson
Aceptado

Esto es lo que se me ocurrió y está funcionando para mí. Agregué los siguientes métodos a mi clase base de controlador. (Supongo que siempre puedes crear estos métodos estáticos en otro lugar que acepte un controlador como parámetro)

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

Estilo maquinilla de afeitar .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();
  }
}

Editar: código Razor agregado.

Ben Lesh avatar May 03 '2010 17:05 Ben Lesh

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

Para eso tienes que crear staticuna clase en un archivo de clase. (Supongamos que el nombre de su 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 se puede llamar desde el controlador como se indica a continuación

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

Espero que esto le resulte útil para que el código esté limpio y ordenado.

Dilip Langhanoja avatar Sep 24 '2013 09:09 Dilip Langhanoja

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;
    }
}
 avatar Jan 27 '2009 19:01