¿Cómo representar una vista ASP.NET MVC como una cadena?
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);
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.
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 static
una 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.
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;
}
}