asp.net-mvc - ¿Cómo representar una vista MVC de ASP.NET como una cadena?



7 Answers

Esta respuesta no está en mi 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 hacer clase static en el archivo de clase. (Suponga 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 al pasar "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 te sea útil para hacer el código limpio y ordenado.

asp.net-mvc rendering

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

¿Es esto posible en ASP.NET MVC beta?

He intentado varios ejemplos:

1. RenderPartial a cadena en ASP.NET MVC Beta

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

2. MVC Framework: captura de la salida de una vista

Si uso esto, parece que no puedo hacer una redirectToAction, ya que intenta generar una vista que puede no existir. Si devuelvo la vista, está completamente desordenada y no se ve 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 estoy tratando de 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

Suponiendo una llamada del controlador para obtener 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);



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

Básicamente, todo lo que haces es crear un HttpContext falso para que la vista se rinda:

/// <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. (el cambio de encabezados en el HttpResponse original no muestra 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 :

//...

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

//...



Si desea renunciar por completo a MVC, evite todo el lío 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 usa el increíble motor de código abierto Razor Engine aquí: https://github.com/Antaris/RazorEngine




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

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



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



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

Le permite ahorrar sus propios ayudantes para transmitir los resultados y se ha comprobado que funciona lo suficientemente bien. Asumiría que esto sería en un proyecto de prueba y como un bono tendría las otras capacidades de prueba una vez que tenga esta configuración. La principal molestia 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"));
        });
}



Encontré una mejor manera de representar la página de la vista de la maquinilla de afeitar cuando me equivoqué con los métodos anteriores, esta solución tanto para el entorno de formularios web como para el entorno mvc No se necesita ningún controlador.

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

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





Related