Comment rendre une vue ASP.NET MVC sous forme de chaîne?

Je souhaite afficher deux vues différentes (une en tant que chaîne qui sera envoyée sous forme de courrier électronique), et l’autre, la page affichée à un utilisateur.

Est-ce possible dans ASP.NET MVC beta?

J’ai essayé plusieurs exemples:

1. RenderPartial à Ssortingng dans ASP.NET MVC Beta

Si j’utilise cet exemple, je reçois le message “Impossible de redirect une fois que les en-têtes HTTP ont été envoyés”.

2. MVC Framework: Capture de la sortie d’une vue

Si j’utilise ceci, il me semble impossible de faire une redirectToAction, car il tente de rendre une vue qui pourrait ne pas exister. Si je retourne la vue, elle est complètement foirée et ne semble pas correcte du tout.

Quelqu’un at-il des idées / solutions à ces questions que j’ai, ou avez-vous des suggestions pour mieux?

Merci beaucoup!

Voici un exemple. Ce que j’essaie de faire est de créer la méthode GetViewForEmail :

public ActionResult OrderResult(ssortingng ref) { //Get the order Order order = OrderService.GetOrder(ref); //The email helper would do the meat and veg by getting the view as a ssortingng //Pass the control name (OrderResultEmail) and the model (order) ssortingng emailView = GetViewForEmail("OrderResultEmail", order); //Email the order out EmailHelper(order, emailView); return View("OrderResult", order); } 

Réponse acceptée de Tim Scott (modifiée et formatée un peu par moi):

 public virtual ssortingng RenderViewToSsortingng( ControllerContext controllerContext, ssortingng viewPath, ssortingng 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; } } 

Exemple d’utilisation

En supposant un appel du contrôleur pour obtenir l’e-mail de confirmation de la commande, en passant l’emplacement Site.Master.

 ssortingng mySsortingng = RenderViewToSsortingng(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData); 

    Voici ce que j’ai trouvé et ça marche pour moi. J’ai ajouté la ou les méthodes suivantes à ma classe de base de contrôleur. (Vous pouvez toujours faire ces méthodes statiques quelque part ailleurs qui acceptent un contrôleur comme paramètre, je suppose)

    Style MVC2 .ascx

     protected ssortingng RenderViewToSsortingng(ssortingng viewPath, T model) { ViewData.Model = model; using (var writer = new SsortingngWriter()) { 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.ToSsortingng(); } } 

    Style de razor .cshtml

     public ssortingng RenderRazorViewToSsortingng(ssortingng viewName, object model) { ViewData.Model = model; using (var sw = new SsortingngWriter()) { 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.GetSsortingngBuilder().ToSsortingng(); } } 

    Edit: ajout du code Razor.

    Cette réponse n’est pas sur mon chemin. Ceci est à l’origine de https://stackoverflow.com/a/2759898/2318354 mais ici j’ai montré comment l’utiliser avec le mot-clé “Static” pour le rendre commun à tous les contrôleurs.

    Pour cela, vous devez créer static classe static dans un fichier de classe. (Supposons que votre nom de fichier de classe soit Utils.cs)

    Cet exemple est For Razor.

    Utils.cs

     public static class RazorViewToSsortingng { public static ssortingng RenderRazorViewToSsortingng(this Controller controller, ssortingng viewName, object model) { controller.ViewData.Model = model; using (var sw = new SsortingngWriter()) { 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.GetSsortingngBuilder().ToSsortingng(); } } } 

    Maintenant, vous pouvez appeler cette classe depuis votre contrôleur en ajoutant NameSpace dans votre fichier Controller de la manière suivante en transmettant “this” comme paramètre à Controller.

     ssortingng result = RazorViewToSsortingng.RenderRazorViewToSsortingng(this ,"ViewName", model); 

    Comme suggéré par @Sergey, cette méthode d’extension peut également appeler depuis le cotroller comme indiqué ci-dessous

     ssortingng result = this.RenderRazorViewToSsortingng("ViewName", model); 

    J’espère que cela vous sera utile pour rendre le code propre et net.

    Cela fonctionne pour moi:

     public virtual ssortingng 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; } } 

    J’ai trouvé une nouvelle solution qui rend une vue à la chaîne sans avoir à jouer avec le stream de réponse du HttpContext actuel (qui ne vous permet pas de modifier le ContentType de la réponse ou d’autres en-têtes).

    Fondamentalement, tout ce que vous faites est de créer un faux HttpContext pour que la vue se rende:

     /// Renders a view to ssortingng. public static ssortingng RenderViewToSsortingng(this Controller controller, ssortingng viewName, object viewData) { //Create memory writer var sb = new SsortingngBuilder(); var memWriter = new SsortingngWriter(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.ToSsortingng(); } /// Fake IView implementation used to instantiate an HtmlHelper. public class FakeView : IView { #region IView Members public void Render(ViewContext viewContext, System.IO.TextWriter writer) { throw new NotImplementedException(); } #endregion } 

    Cela fonctionne sur ASP.NET MVC 1.0, avec ContentResult, JsonResult, etc. (la modification de l’en-tête HttpResponse d’origine ne déclenche pas l’exception “Le serveur ne peut pas définir le type de contenu après l’envoi des en-têtes HTTP “).

    Mise à jour: dans ASP.NET MVC 2.0 RC, le code change un peu car il faut passer le SsortingngWriter utilisé pour écrire la vue dans 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); //... 

    Cet article explique comment rendre une vue à une chaîne dans différents scénarios:

    1. MVC Controller appelant un autre de ses propres ActionMethods
    2. MVC Controller appelant une méthode ActionMet d’un autre contrôleur MVC
    3. Contrôleur WebAPI appelant une méthode d’action d’un contrôleur MVC

    La solution / code est fournie en tant que classe appelée ViewRenderer . Il fait partie de WestwindToolkit de Rick Stahl chez GitHub .

    Utilisation (3. – Exemple WebAPI):

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

    Si vous voulez renoncer complètement à MVC, évitant ainsi tout le désordre HttpContext …

     using RazorEngine; using RazorEngine.Templating; // For extension methods. ssortingng razorText = System.IO.File.ReadAllText(razorTemplateFileLocation); ssortingng emailBody = Engine.Razor.RunComstack(razorText, "templateKey", typeof(Model), model); 

    Cela utilise le génial moteur open source Razor ici: https://github.com/Antaris/RazorEngine

    vous obtenez la vue en chaîne en utilisant cette façon

     protected ssortingng RenderPartialViewToSsortingng(ssortingng viewName, object model) { if (ssortingng.IsNullOrEmpty(viewName)) viewName = ControllerContext.RouteData.GetRequiredSsortingng("action"); if (model != null) ViewData.Model = model; using (SsortingngWriter sw = new SsortingngWriter()) { ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName); ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw); viewResult.View.Render(viewContext, sw); return sw.GetSsortingngBuilder().ToSsortingng(); } } 

    Nous appelons cette méthode de deux manières

     ssortingng strView = RenderPartialViewToSsortingng("~/Views/Shared/_Header.cshtml", null) 

    OU

     var model = new Person() ssortingng strView = RenderPartialViewToSsortingng("~/Views/Shared/_Header.cshtml", model) 

    J’utilise MVC 1.0 RTM et aucune des solutions ci-dessus n’a fonctionné pour moi. Mais celui-ci a fait:

     Public Function RenderView(ByVal viewContext As ViewContext) As Ssortingng Dim html As Ssortingng = "" Dim response As HttpResponse = HttpContext.Current.Response Using tempWriter As New System.IO.SsortingngWriter() 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.ToSsortingng() 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 

    J’ai vu une implémentation pour MVC 3 et Razor à partir d’un autre site Web, cela a fonctionné pour moi:

      public static ssortingng RazorRender(Controller context, ssortingng DefaultAction) { ssortingng Cache = ssortingng.Empty; System.Text.SsortingngBuilder sb = new System.Text.SsortingngBuilder(); System.IO.TextWriter tw = new System.IO.SsortingngWriter(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.ToSsortingng(); return Cache; } public static ssortingng RenderRazorViewToSsortingng(ssortingng viewName, object model) { ViewData.Model = model; using (var sw = new SsortingngWriter()) { var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName); var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw); viewResult.View.Render(viewContext, sw); return sw.GetSsortingngBuilder().ToSsortingng(); } } public static class HtmlHelperExtensions { public static ssortingng RenderPartialToSsortingng(ControllerContext context, ssortingng partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData) { ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName); if (result.View != null) { SsortingngBuilder sb = new SsortingngBuilder(); using (SsortingngWriter sw = new SsortingngWriter(sb)) { using (HtmlTextWriter output = new HtmlTextWriter(sw)) { ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output); result.View.Render(viewContext, output); } } return sb.ToSsortingng(); } return Ssortingng.Empty; } } 

    Plus sur Razor render – MVC3 View Render to Ssortingng

    Conseil rapide

    Pour un modèle fortement typé, ajoutez-le simplement à la propriété ViewData.Model avant de passer à RenderViewToSsortingng. par exemple

     this.ViewData.Model = new OrderResultEmailViewModel(order); ssortingng mySsortingng = RenderViewToSsortingng(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData); 

    Pour rendre une vue sur une chaîne dans la couche de service sans avoir à passer ControllerContext, il existe un bon article Rick Strahl ici: http://www.codemag.com/Article/1312081 qui crée un contrôleur générique. Résumé du code ci-dessous:

     // Some Static Class public static ssortingng RenderViewToSsortingng(ControllerContext context, ssortingng 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; ssortingng result = null; using (var sw = new SsortingngWriter()) { var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw); view.Render(ctx, sw); result = sw.ToSsortingng(); } return result; } // In the Service Class public class GenericController : Controller { } public static T CreateController(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; } 

    Ensuite, pour afficher la classe View dans le service:

     var ssortingngView = RenderViewToSsortingng(CreateController().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true); 

    Pour répéter à partir d’une question plus inconnue, consultez MvcIntegrationTestFramework .

    Cela vous évite d’écrire vos propres assistants pour diffuser les résultats et a fait ses preuves. Je suppose que cela serait dans un projet de test et en bonus, vous auriez les autres capacités de test une fois que vous avez cette configuration. Le problème principal serait probablement de régler la chaîne de dépendance.

      private static readonly ssortingng 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")); }); } 

    Voici une classe que j’ai écrite pour le faire pour ASP.NETCore RC2. Je l’utilise pour que je puisse générer des emails HTML en utilisant 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 { ///  /// the goal of this class is to provide an easy way to produce an html ssortingng using /// Razor templates and models, for use in generating html email. ///  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 RenderViewAsSsortingng(ssortingng viewName, TModel model) { var viewData = new ViewDataDictionary( 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 (SsortingngWriter output = new SsortingngWriter()) { 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.GetSsortingngBuilder().ToSsortingng(); } } } } 

    J’ai trouvé une meilleure façon de rendre la page de vue de razor quand j’ai eu une erreur avec les méthodes ci-dessus, cette solution pour l’environnement de formulaire Web et l’environnement mvc. Aucun contrôleur n’est nécessaire.

    Voici l’exemple de code, dans cet exemple, j’ai simulé une action mvc avec un gestionnaire http asynchrone:

      ///  /// Enables processing of HTTP Web requests asynchronously by a custom HttpHandler that implements the IHttpHandler interface. ///  /// An HttpContext object that provides references to the insortingnsic server objects. /// The task to complete the http request. 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 (SsortingngWriter sw = new SsortingngWriter()) { page.ExecutePageHierarchy(new WebPageContext(new HttpContextWrapper(context), page, model), sw); await context.Response.Output.WriteAsync(sw.GetSsortingngBuilder().ToSsortingng()); } }