Comment rendre ASP.NET MVC view as a string?

je veux sortir deux vues différentes (une comme une chaîne qui sera envoyée comme un email), et l'autre la page affichée à un utilisateur.

est-ce possible en ASP.NET MVC beta?

j'ai essayé plusieurs exemples:

1. RenderPartial à la Chaîne dans ASP.NET MVC Bêta

si j'utilise cet exemple, je recevoir le "ne peut pas rediriger après HTTP les en-têtes ont été envoyés.".

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

si j'utilise ceci, je semble incapable de faire une redirection versaction, car il essaie de rendre une vue qui n'existe peut-être pas. Si je dois retourner la voir, elle est complètement foiré et ne pas regarder à droite.

est-ce que quelqu'un a des idées/des solutions à ces questions que j'AI, ou a des suggestions pour de meilleures?

Merci beaucoup!

En voici un exemple. Ce que j'essaie de faire est de créer la méthode 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);
}

Accepté la réponse de Tim Scott (changé et mis en forme un peu par moi):

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

exemple d'usage

supposant un appel du contrôleur pour recevoir l'e-mail de confirmation de commande, en passant par le Site.Localisation principale.

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
435
demandé sur Dan Atkinson 2009-01-27 14:43:56

14 réponses

voilà ce que j'ai trouvé, et ça marche pour moi. J'ai ajouté la(Les) méthode (s) suivante (s) à ma classe de base controller. (Vous pouvez toujours faire ces méthodes statiques ailleurs qui acceptent un contrôleur comme paramètre je suppose)

MVC2 .ascx style

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 .CSHTML style

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

Edit: ajout de Rasoir code.

521
répondu Ben Lesh 2015-05-12 12:38:19

Cette réponse n'est pas sur mon chemin . C'est à l'origine de https://stackoverflow.com/a/2759898/2318354 mais ici j'ai montrer la façon de l'utiliser avec le mot-clé" statique " pour le rendre commun pour tous les contrôleurs .

pour cela vous devez faire la classe static dans le fichier de classe . (Supposons que votre nom de fichier de classe soit Utils.cs)

cet exemple est pour le rasoir.

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

Maintenant vous pouvez appeler cette classe depuis votre controller en ajoutant NameSpace dans votre fichier Controller comme suit en passant" ceci " comme paramètre à Controller.

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

comme suggestion de @Sergey cette méthode d'extension peut aussi appeler de cotroller comme indiqué ci-dessous

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

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

53
répondu Dilip0165 2018-05-21 06:39:10

ça marche pour moi:

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;
    }
}
31
répondu Tim Scott 2009-01-27 19:48:12

j'ai trouvé une nouvelle solution qui rend une vue à string sans avoir à jouer avec le flux de réponse du HttpContext courant (qui ne vous permet pas de changer 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 la vue de se rendre:

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

cela fonctionne sur ASP.NET MVC 1.0, avec ContentResult, JsonResult,etc. (Changement des en-têtes sur la réponse originale de https) le serveur " ne peut pas définir le type de contenu APRÈS QUE LES en-têtes HTTP ont été envoyés " exception).

mise à jour: en ASP.NET MVC 2.0 RC, le code change un peu parce que nous devons passer dans le StringWriter utilisé pour écrire la vue dans le 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);

//...
28
répondu LorenzCK 2015-11-30 22:58:04

cet article décrit comment rendre une vue à une chaîne dans différents scénarios:

  1. MVC Contrôleur de l'appel d'une autre de ses propres ActionMethods
  2. MVC Controller calling an action method of another MVC Controller
  3. contrôleur WebAPI appelant une méthode D'action D'un contrôleur MVC

la solution / le code est fourni sous la forme d'une classe appelée ViewRenderer . Il fait partie du WestwindToolkit de Rick Stahl à github .

Utilisation (3. - Exemple WebAPI):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
10
répondu Jenny O'Reilly 2014-10-02 07:13:55

si vous voulez renoncer entièrement à MVC, évitant ainsi tout le désordre 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);

cela utilise le moteur de rasoir open source impressionnant ici: https://github.com/Antaris/RazorEngine

7
répondu Josh Noe 2016-07-21 14:16:29

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

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

nous appelons cette méthode dans les deux sens

string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", null)

ou

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)
4
répondu Jayesh Patel 2018-02-05 09:25:06

j'utilise MVC 1.0 RTM et aucune des solutions ci-dessus n'a fonctionné pour moi. Mais celui-ci fit:

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
3
répondu Jeremy Bell 2009-07-13 20:44:07

j'ai vu une implémentation pour MVC 3 et Razor sur un autre site, ça a marché pour moi:

    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;

        }

    }

plus sur rendu de rasoir - MVC3 vue rendu à chaîne de caractères

2
répondu Adamy 2012-02-20 21:34:56

astuce

pour un modèle fortement typé, il suffit de l'Ajouter aux ViewData.Propriété Model avant de passer à RenderViewToString. E. g

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
1
répondu longhairedsi 2009-06-15 16:42:27

pour rendre une vue à une chaîne dans la couche de Service sans avoir à passer Controllercontexte autour, il y a un bon article de 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 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;
}

ensuite pour rendre la vue dans la classe de Service:

var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);
1
répondu RickL 2017-03-02 15:32:05

pour reprendre une question plus inconnue, jetez un oeil à Mvcintégrationtestfram Framework .

il fait vous sauve en écrivant vos propres helpers pour le résultat de flux et est prouvé pour travailler assez bien. Je suppose que ce serait dans un projet de test et comme bonus vous auriez les autres capacités de test une fois que vous avez cette configuration. La principale difficulté serait probablement de trier la chaîne de dépendances.

 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"));
        });
}
0
répondu dove 2010-03-31 15:58:05

voici un cours que j'ai écrit pour faire ça pour ASP.NETCore RC2. Je l'utilise pour que je puisse générer des e-mails 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
{
    /// <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();
            }
        }
    }
}
0
répondu Joe Audette 2016-06-09 11:28:41

j'ai trouvé une meilleure façon de rendre la page de vue de rasoir quand j'ai eu l'erreur avec les méthodes ci-dessus, Cette solution à la fois pour l'environnement de forme de 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 async:

    /// <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());
        }
    }
0
répondu dexiang 2016-08-12 16:33:38