tag route page net for data asp all asp.net-mvc rendering

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



tag helper asp net core 2 (14)

Aquí hay una clase que escribí para hacer esto para ASP.NETCore RC2. Lo uso para poder generar correo electrónico html usando 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(); } } } }

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


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


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


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


Esta respuesta no está en mi camino. Esto es originalmente de https://.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.


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

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

Edición: añadido el código Razor.


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


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


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


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 renderizar la Vista en la clase de Servicio:

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


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


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

Estamos llamados 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)


Vi una implementación para MVC 3 y Razor de 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 Ver Renderizar a Cadena


En este artículo se describe cómo representar una vista en una cadena en diferentes escenarios:

  1. MVC Controller llamando a otro de sus propios ActionMethods
  2. Controlador MVC llamando a un Método de Acción de otro Controlador MVC
  3. Controlador WebAPI llamando a un Método de Acción de un Controlador MVC

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

Uso (3. - Ejemplo de WebAPI):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));