507 votes

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

Je veux produire deux vues différentes (l'une sous la forme d'une chaîne qui sera envoyée par courriel), et l'autre sous la forme de la page affichée à un utilisateur.

Est-ce possible dans ASP.NET MVC beta ?

J'ai essayé plusieurs exemples :

1. RenderPartial vers String en ASP.NET MVC Beta

Si j'utilise cet exemple, je reçois le message "Cannot redirect after HTTP ont été envoyés".

2. Cadre MVC : Capturer la sortie d'une vue

Si je l'utilise, il semble que je ne puisse pas faire de redirectToAction, car elle tente de rendre une vue qui peut ne pas exister. tente de rendre une vue qui n'existe peut-être pas. Si je renvoie la vue, elle est est complètement désordonnée et n'a pas du tout l'air correcte.

Est-ce que quelqu'un a des idées/solutions à ces problèmes que j'ai, ou a des suggestions pour de meilleures solutions ?

Merci beaucoup !

Vous trouverez ci-dessous un exemple. Ce que j'essaie de faire, c'est de créer l'élément 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);
}

Réponse acceptée de Tim Scott (modifiée et formatée 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'utilisation

Supposons un appel du contrôleur pour obtenir l'email de confirmation de la commande, en passant l'emplacement Site.Master.

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);

2 votes

Comment pouvez-vous utiliser ceci avec une vue, qui est fortement typée ? Par exemple, comment puis-je alimenter un modèle dans la page ?

0 votes

On ne peut pas l'utiliser et créer JsonResult ensuite, car le type de contenu ne peut pas être défini après l'envoi des en-têtes (parce que Flush les envoie).

0 votes

Parce qu'il n'y a pas une seule bonne réponse, je suppose :) J'ai créé une question qui m'était propre, mais je savais qu'elle serait aussi largement posée.

589voto

Ben Lesh Points 39290

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

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

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

Edit : ajouté le code Razor.

0 votes

+1 J'avais des problèmes de templating imbriqué ("tempData value must not be null") avec l'ancienne version de l'application RenderPartialToString méthode. Votre solution fonctionne parfaitement.

0 votes

Ce n'est pas une bonne solution car cela vous oblige à écrire du code comme celui-ci : RenderViewToString ("~/Views/Application/Review.ascx", model) qui n'est pas cohérent avec l'ensemble du concept de routage.

32 votes

Le rendu d'une vue en une chaîne de caractères est toujours "incompatible avec l'ensemble du concept de routage", car il n'a rien à voir avec le routage. Je ne sais pas pourquoi une réponse qui fonctionne a reçu un vote négatif.

72voto

Dilip0165 Points 655

Cette réponse n'est pas sur mon chemin . Elle provient 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 faire static dans le fichier de classe . (Supposons que le nom de votre fichier de classe est Utils.cs )

Cet exemple est pour 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();
        }
    }
}

Maintenant vous pouvez appeler cette classe à partir de votre contrôleur en ajoutant le NameSpace dans votre fichier de contrôleur de la manière suivante en passant "this" comme paramètre au contrôleur.

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

Comme suggéré par @Sergey, cette méthode d'extension peut également être appelée par le 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 soigné.

1 votes

Bonne solution ! Une chose, RenderRazorViewToString est en fait une méthode d'extension (parce que vous passez le paramètre du contrôleur avec ce mot-clé), donc cette méthode d'extension peut être appelée de cette façon : this.RenderRazorViewToString("ViewName", model) ;

0 votes

@Sergey Hmmm... Laissez-moi vérifier de cette façon, si tout va bien, je mettrai à jour ma réponse. En tout cas, merci pour votre suggestion.

0 votes

Dilip0165, j'ai une erreur de référence nulle sur var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName) ;. Avez-vous une idée ?

34voto

Tim Scott Points 7043

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

0 votes

Merci pour votre commentaire, mais n'est-ce pas utilisé pour le rendu à l'intérieur d'une vue ? Comment pourrais-je l'utiliser dans le contexte avec lequel j'ai mis à jour la question ?

0 votes

Désolé, je pense encore à Silverlight l'année dernière dont le premier rc était 0. :) Je vais tenter le coup aujourd'hui. (Dès que j'aurai trouvé le format correct du chemin d'accès).

0 votes

Cela casse toujours les redirections dans la RC1

32voto

LorenzCK Points 2819

J'ai trouvé une nouvelle solution qui rend une vue en chaîne de caractères sans avoir à s'occuper du flux de réponse du HttpContext actuel (qui ne vous permet pas de changer le ContentType de la réponse ou d'autres en-têtes).

En gros, tout ce que vous faites, c'est créer un faux HttpContext pour que la vue se rende elle-même :

/// <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 avec ASP.NET MVC 1.0, ainsi qu'avec ContentResult, JsonResult, etc. (la modification des en-têtes de la réponse HttpResponse originale n'entraîne pas l'erreur " 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 parce que nous devons passer dans l'élément 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);

//...

0 votes

Il n'y a pas de méthode RenderPartial sur l'objet HtmlHelper. Ce n'est pas possible - html.RenderPartial(viewName, viewData) ;

1 votes

Dans ASP.NET MVC version 1.0, il y a quelques méthodes d'extension RenderPartial. Celle que j'utilise en particulier est System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(this HtmlHelper, string, object). Je ne sais pas si cette méthode a été ajoutée dans les dernières révisions de MVC et n'était pas présente dans les précédentes.

0 votes

Merci. Il fallait juste ajouter l'espace de nom System.Web.Mvc.Html à la déclaration using (sinon html.RenderPartial(..) ne sera bien sûr pas accessible :))

10voto

Josh Noe Points 878

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

Il utilise l'impressionnant moteur open source Razor Engine : https://github.com/Antaris/RazorEngine

0 votes

Joli ! Savez-vous s'il existe un moteur d'analyse similaire pour la syntaxe WebForms ? J'ai encore quelques vieilles vues WebForms qui ne peuvent pas être déplacées vers Razor pour le moment.

0 votes

Bonjour, j'ai eu beaucoup de problèmes avec le moteur Razor, et le rapport d'erreur n'est pas très agréable. Je ne pense pas que Url helper soit supporté

0 votes

@Layinka Je ne sais pas si cela peut vous aider, mais la plupart des informations sur les erreurs se trouvent dans le fichier d'aide de l'utilisateur. CompilerErrors de l'exception.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X