Wie rendere ich eine ASP.NET MVC-Ansicht als Zeichenfolge?

485

Ich möchte zwei verschiedene Ansichten ausgeben (eine als Zeichenfolge, die als E-Mail gesendet wird) und die andere Seite, die einem Benutzer angezeigt wird.

Ist dies in ASP.NET MVC Beta möglich?

Ich habe mehrere Beispiele ausprobiert:

1. RenderPartial to String in ASP.NET MVC Beta

Wenn ich dieses Beispiel verwende, erhalte ich die Meldung "Nach dem Senden von HTTP-Headern kann nicht umgeleitet werden".

2. MVC Framework: Erfassen der Ausgabe einer Ansicht

Wenn ich dies verwende, kann ich anscheinend keine redirectToAction ausführen, da versucht wird, eine Ansicht zu rendern, die möglicherweise nicht vorhanden ist. Wenn ich die Ansicht zurückgebe, ist sie völlig durcheinander und sieht überhaupt nicht richtig aus.

Hat jemand Ideen / Lösungen für diese Probleme oder Vorschläge für bessere?

Danke vielmals!

Unten ist ein Beispiel. Ich versuche, die GetViewForEmail-Methode zu erstellen :

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

Akzeptierte Antwort von Tim Scott (von mir ein wenig geändert und formatiert):

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

Anwendungsbeispiel

Angenommen, der Controller ruft an, um die Bestellbestätigungs-E-Mail zu erhalten, und übergibt den Standort Site.Master.

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
Dan Atkinson
quelle
2
Wie können Sie dies mit einer stark typisierten Ansicht verwenden? Dh. Wie kann ich der Seite ein Modell hinzufügen?
Kjensen
Kann dies nicht verwenden und anschließend JsonResult erstellen, da der Inhaltstyp nicht festgelegt werden kann, nachdem Header gesendet wurden (weil Flush sie sendet).
Arnis Lapsa
Weil es wohl keine einzige richtige Antwort gibt. :) Ich habe eine Frage erstellt, die spezifisch für mich war, aber ich wusste, dass es auch eine häufig gestellte Frage sein würde.
Dan Atkinson
2
Die vorgeschlagene Lösung funktioniert nicht in MVC 3.
Kasper Holdum
1
@Qua: Die vorgeschlagene Lösung ist über zwei Jahre alt. Ich würde auch nicht erwarten, dass es für MVC 3 funktioniert! Außerdem gibt es jetzt bessere Möglichkeiten, dies zu tun.
Dan Atkinson

Antworten:

572

Folgendes habe ich mir ausgedacht und es funktioniert für mich. Ich habe meiner Controller-Basisklasse die folgenden Methoden hinzugefügt. (Sie können diese statischen Methoden immer an einer anderen Stelle erstellen, die vermutlich einen Controller als Parameter akzeptiert.)

MVC2 .ascx-Stil

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

Rasiermesser .cshtml Stil

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

Bearbeiten: Rasiermesser-Code hinzugefügt.

Ben Lesh
quelle
31
Das Rendern einer Ansicht in eine Zeichenfolge ist immer "inkonsistent mit dem gesamten Routing-Konzept", da es nichts mit Routing zu tun hat. Ich bin mir nicht sicher, warum eine Antwort, die funktioniert, abgelehnt wurde.
Ben Lesh
4
Ich denke, Sie müssen möglicherweise die "statische" aus der Methodendeklaration der Razor-Version entfernen, da sie sonst ControllerContext et al. Nicht finden kann.
Mike
3
Sie müssen Ihre eigene Methode zum Entfernen dieser überflüssigen Leerzeichen implementieren. Der beste Weg, den ich mir vorstellen kann, besteht darin, die Zeichenfolge in ein XmlDocument zu laden und sie dann mit einem XmlWriter gemäß dem Link, den ich in meinem letzten Kommentar hinterlassen habe, wieder in eine Zeichenfolge zu schreiben. Ich hoffe wirklich, dass das hilft.
Ben Lesh
3
Hmm wie soll ich das mit einem WebApi Controller machen, alle Vorschläge wären willkommen
Alexander
3
Hallo allerseits, um es mit dem Schlüsselwort "Static" für alle Controller zu verwenden, um es gemeinsam zu machen, müssen Sie eine statische Klasse erstellen und diese Methode mit "this" als Parameter in "ControllerContext" einfügen. Sie können hier stackoverflow.com/a/18978036/2318354 sehen .
Dilip0165
68

Diese Antwort ist nicht auf meinem Weg. Dies ist ursprünglich von https://stackoverflow.com/a/2759898/2318354, aber hier habe ich gezeigt, wie man es mit dem Schlüsselwort "Static" verwendet, um es für alle Controller gemeinsam zu machen.

Dafür müssen Sie staticKlasse in Klassendatei machen. (Angenommen, Ihr Klassendateiname lautet Utils.cs)

Dieses Beispiel ist For 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();
        }
    }
}

Jetzt können Sie diese Klasse von Ihrem Controller aus aufrufen, indem Sie NameSpace wie folgt in Ihre Controller-Datei einfügen, indem Sie "this" als Parameter an Controller übergeben.

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

Als Vorschlag von @Sergey kann diese Erweiterungsmethode auch von cotroller aufgerufen werden, wie unten angegeben

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

Ich hoffe, dies ist nützlich, um den Code sauber und ordentlich zu machen.

Dilip0165
quelle
1
Schöne Lösung! Eine Sache, RenderRazorViewToString ist tatsächlich eine Erweiterungsmethode (da Sie den Controller-Parameter mit diesem Schlüsselwort übergeben), sodass diese Erweiterungsmethode folgendermaßen aufgerufen werden kann: this.RenderRazorViewToString ("ViewName", Modell);
Sergey
@Sergey Hmmm ... Lassen Sie mich auf diese Weise überprüfen, ob es in Ordnung ist, dann werde ich meine Antwort aktualisieren. Trotzdem Danke für deinen Vorschlag.
Dilip0165
Dilip0165, ich habe einen Nullreferenzfehler bei var viewResult = ViewEngines.Engines.FindPartialView (controller.ControllerContext, viewName);. Hast du irgendeine Idee?
CB4
@ CB4 Ich denke, es könnte das Problem von "viewName" sein, das Sie in die Funktion übergeben. Sie müssen "viewName" mit dem vollständigen Pfad gemäß Ihrer Ordnerstruktur übergeben. Schauen Sie sich dieses Ding an.
Dilip0165
1
@ Sergey Vielen Dank für Ihren Vorschlag, ich habe meine Antwort gemäß Ihrem Vorschlag aktualisiert, der völlig korrekt ist
Dilip0165
32

Das funktioniert bei mir:

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;
    }
}
Tim Scott
quelle
Vielen Dank für Ihren Kommentar, aber wird das nicht zum Rendern in einer Ansicht verwendet? Wie kann ich es in dem Kontext verwenden, mit dem ich die Frage aktualisiert habe?
Dan Atkinson
Tut mir leid, ich denke immer noch an Silverlight im letzten Jahr, dessen erstes RC 0 war. :) Ich probiere es heute aus. (Sobald ich das richtige Format des Ansichtspfads herausgefunden habe)
NikolaiDante
Dies bricht immer noch Weiterleitungen in RC1
besiegt
besiegt: Nein, das tut es nicht. Wenn ja, dann machen Sie etwas falsch.
Dan Atkinson
Dies wurde mit stackoverflow.com/questions/520863/… zusammengeführt , das Bewusstsein für ViewEnginesCollection erhöht, versucht, eine Teilansicht zu öffnen, und diese stackoverflow.com/questions/520863/… erhalten . : E
Arnis Lapsa
31

Ich habe eine neue Lösung gefunden, die eine Ansicht in eine Zeichenfolge umwandelt, ohne sich mit dem Antwortstrom des aktuellen HttpContext herumschlagen zu müssen (wodurch Sie den ContentType der Antwort oder andere Header nicht ändern können).

Grundsätzlich erstellen Sie lediglich einen gefälschten HttpContext, damit sich die Ansicht selbst rendern kann:

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

Dies funktioniert unter ASP.NET MVC 1.0 zusammen mit ContentResult, JsonResult usw. (das Ändern von Headern in der ursprünglichen HttpResponse löst nicht die Ausnahme " Server kann Inhaltstyp nicht festlegen, nachdem HTTP-Header gesendet wurden " aus).

Update: In ASP.NET MVC 2.0 RC ändert sich der Code ein wenig, da wir den StringWriterzum Schreiben der Ansicht verwendeten Code in Folgendes übergeben müssen 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);

//...
LorenzCK
quelle
Es gibt keine RenderPartial-Methode für das HtmlHelper-Objekt. Dies ist nicht möglich - html.RenderPartial (viewName, viewData);
MartinF
1
In ASP.NET MVC Release 1.0 gibt es einige RenderPartial-Erweiterungsmethoden. Ich verwende insbesondere System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial (dieser HtmlHelper, String, Objekt). Mir ist nicht bekannt, ob die Methode in den letzten Versionen von MVC hinzugefügt wurde und in früheren Versionen nicht vorhanden war.
LorenzCK
Vielen Dank. Nur erforderlich, um den System.Web.Mvc.Html-Namespace zur using-Deklaration hinzuzufügen (sonst ist html.RenderPartial (..) natürlich nicht zugänglich :))
MartinF
Hat jemand diese Arbeit mit dem RC von MVC2? Sie haben ViewContext einen zusätzlichen Textwriter-Parameter hinzugefügt. Ich habe versucht, nur einen neuen StringWriter () hinzuzufügen, aber es hat nicht funktioniert.
Beckelmw
1
@beckelmw: Ich habe die Antwort aktualisiert. Sie müssen das Original übergeben, das StringWriterSie zum Schreiben in die verwenden StringBuilder, nicht eine neue Instanz, da sonst die Ausgabe der Ansicht verloren geht.
LorenzCK
11

In diesem Artikel wird beschrieben, wie Sie eine Ansicht in einer Zeichenfolge in verschiedenen Szenarien rendern:

  1. MVC Controller ruft eine andere seiner eigenen ActionMethods auf
  2. MVC Controller ruft eine ActionMethod eines anderen MVC Controllers auf
  3. WebAPI-Controller, der eine ActionMethod eines MVC-Controllers aufruft

Die Lösung / der Code wird als Klasse namens ViewRenderer bereitgestellt . Es ist Teil von Rick Stahls WestwindToolkit bei GitHub .

Verwendung (3. - WebAPI-Beispiel):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
Jenny O'Reilly
quelle
3
Auch als NuGet-Paket West Wind Web MVC Utilities ( nuget.org/packages/Westwind.Web.Mvc ). Als Bonus kann der Ansichtsrenderer nicht nur Teilansichten rendern, sondern auch die gesamte Ansicht einschließlich des Layouts. Blog-Artikel mit Code: weblog.west-wind.com/posts/2012/May/30/…
Jeroen K
Es wäre großartig, wenn dies in kleinere Pakete aufgeteilt würde. Das Nuget-Paket nimmt eine Reihe von Änderungen an Ihrer web.config vor und fügt Ihrem Projekt js-Dateien hinzu, die bei der Deinstallation nicht bereinigt werden: /
Josh Noe
8

Wenn Sie ganz auf MVC verzichten möchten, um das ganze HttpContext-Chaos zu vermeiden ...

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

Hier wird die fantastische Open Source Razor Engine hier verwendet: https://github.com/Antaris/RazorEngine

Josh Noe
quelle
Nett! Wissen Sie, ob es eine ähnliche Analyse-Engine für die WebForms-Syntax gibt? Ich habe noch einige alte WebForms-Ansichten, die noch nicht ganz nach Razor verschoben werden können.
Dan Atkinson
Hallo, ich hatte viele Probleme mit der Rasiermaschine und die Fehlerberichterstattung ist nicht sehr schön. Ich glaube nicht, dass URL-Helfer unterstützt wird
Layinka
@Layinka Ich bin mir nicht sicher, ob dies hilft, aber die meisten Fehlerinformationen befinden sich in der CompilerErrorsEigenschaft der Ausnahme.
Josh Noe
5

Auf diese Weise erhalten Sie die Ansicht in einer Zeichenfolge

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

Wir nennen diese Methode auf zwei Arten

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

ODER

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)
Jayesh Patel
quelle
4

Zusätzlicher Tipp für ASP NET CORE:

Schnittstelle:

public interface IViewRenderer
{
  Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model);
}

Implementierung:

public class ViewRenderer : IViewRenderer
{
  private readonly IRazorViewEngine viewEngine;

  public ViewRenderer(IRazorViewEngine viewEngine) => this.viewEngine = viewEngine;

  public async Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model)
  {
    ViewEngineResult viewEngineResult = this.viewEngine.FindView(controller.ControllerContext, name, false);

    if (!viewEngineResult.Success)
    {
      throw new InvalidOperationException(string.Format("Could not find view: {0}", name));
    }

    IView view = viewEngineResult.View;
    controller.ViewData.Model = model;

    await using var writer = new StringWriter();
    var viewContext = new ViewContext(
       controller.ControllerContext,
       view,
       controller.ViewData,
       controller.TempData,
       writer,
       new HtmlHelperOptions());

       await view.RenderAsync(viewContext);

       return writer.ToString();
  }
}

Registrierung in Startup.cs

...
 services.AddSingleton<IViewRenderer, ViewRenderer>();
...

Und Verwendung in der Steuerung:

public MyController: Controller
{
  private readonly IViewRenderer renderer;
  public MyController(IViewRendere renderer) => this.renderer = renderer;
  public async Task<IActionResult> MyViewTest
  {
    var view = await this.renderer.RenderAsync(this, "MyView", model);
    return new OkObjectResult(view);
  }
}
Marcin
quelle
3

Ich verwende MVC 1.0 RTM und keine der oben genannten Lösungen hat für mich funktioniert. Aber dieser tat:

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
Jeremy Bell
quelle
2

Ich habe eine Implementierung für MVC 3 und Razor von einer anderen Website gesehen, die für mich funktioniert hat:

    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;

        }

    }

Weitere Informationen zu Razor Rendering - MVC3 View Render to String

Adamy
quelle
Ja, dies ist mehr oder weniger eine Kopie der akzeptierten Antwort. :)
Dan Atkinson
2

Um eine Ansicht für eine Zeichenfolge in der Service-Schicht zu rendern, ohne ControllerContext weitergeben zu müssen, gibt es hier einen guten Rick Strahl-Artikel http://www.codemag.com/Article/1312081 , der einen generischen Controller erstellt. Code-Zusammenfassung unten:

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

So rendern Sie die Ansicht in der Service-Klasse:

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

Kurzer Tipp

Für ein stark typisiertes Modell fügen Sie es einfach der ViewData.Model-Eigenschaft hinzu, bevor Sie es an RenderViewToString übergeben. z.B

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
langhaarig
quelle
0

Um eine unbekanntere Frage zu wiederholen, werfen Sie einen Blick auf MvcIntegrationTestFramework .

Es erspart Ihnen das Schreiben eigener Helfer, um Ergebnisse zu streamen, und funktioniert nachweislich gut genug. Ich würde annehmen, dass dies in einem Testprojekt wäre und als Bonus würden Sie die anderen Testfunktionen haben, sobald Sie dieses Setup haben. Die Hauptaufgabe wäre wahrscheinlich, die Abhängigkeitskette zu sortieren.

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

Hier ist eine Klasse, die ich geschrieben habe, um dies für ASP.NETCore RC2 zu tun. Ich benutze es, damit ich mit Razor HTML-E-Mails generieren kann.

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();
            }
        }
    }
}
Joe Audette
quelle
0

Ich habe einen besseren Weg gefunden, um die Seite der Rasiermesseransicht zu rendern, als ich Fehler mit den oben genannten Methoden bekam. Diese Lösung ist sowohl für die Webformularumgebung als auch für die MVC-Umgebung geeignet. Es wird kein Controller benötigt.

Hier ist das Codebeispiel. In diesem Beispiel habe ich eine MVC-Aktion mit einem asynchronen http-Handler simuliert:

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