web-dev-qa-db-de.com

Wie wird eine ASP.NET MVC-Ansicht als Zeichenfolge gerendert?

Ich möchte zwei verschiedene Ansichten ausgeben (eine als Zeichenfolge, die als E-Mail gesendet wird) und die andere die 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 keine Umleitung durchgeführt 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ückschicke, ist sie völlig durcheinander und sieht überhaupt nicht richtig aus.

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

Danke vielmals!

Unten ist ein Beispiel. Was ich versuche, ist das Erstellen der GetViewForEmail-Methode :

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

Beispielverwendung

Angenommen, der Controller ruft an, um die Auftragsbestätigungs-E-Mail zu erhalten, und leitet den Site.Master-Standort weiter.

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

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 einen Controller als Parameter akzeptiert, nehme ich an)

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

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

Bearbeiten: Razor Code hinzugefügt.

556
Ben Lesh

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

Dafür müssen Sie static class in class file machen. (Angenommen, Ihr Klassendateiname lautet Utils.cs.)

Dieses Beispiel ist für Rasiermesser.

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

Wie von @Sergey vorgeschlagen, kann diese Erweiterungsmethode auch vom Cotroller aus aufgerufen werden (siehe unten)

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

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

66
Dilip0165

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;
    }
}
32
Tim Scott

Ich habe eine neue Lösung gefunden, mit der eine Ansicht in einen String gerendert werden kann, ohne mit dem Antwort-Stream des aktuellen HTTP-Kontexts in Konflikt zu geraten (wodurch Sie den ContentType oder andere Header der Antwort nicht ändern können).

Grundsätzlich müssen Sie nur einen gefälschten HttpContext erstellen, damit sich die Ansicht selbst rendert:

/// <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 in ASP.NET MVC 1.0 zusammen mit ContentResult, JsonResult usw. (das Ändern von Headern in der ursprünglichen HttpResponse bewirkt nicht, dass der " Server den Inhaltstyp nicht festlegen kann, nachdem HTTP-Header gesendet wurden "Ausnahme).

pdate: in ASP.NET MVC 2.0 RC ändert sich der Code ein wenig, da wir das StringWriter übergeben müssen, das zum Schreiben der Ansicht in das ViewContext verwendet wird:

//...

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

//...
29
LorenzCK

Dieser Artikel beschreibt das Rendern einer Ansicht für einen String in verschiedenen Szenarien:

  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 mit dem Namen 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));
10
Jenny O'Reilly

Wenn Sie ganz auf MVC verzichten möchten und dabei das gesamte HttpContext-Chaos vermeiden möchten ...

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 großartige Open Source Razor Engine verwendet: https://github.com/Antaris/RazorEngine

8
Josh Noe

auf diese Weise erhalten Sie die Ansicht in 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)

OR

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

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

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

Um eine Ansicht auf eine Zeichenfolge in der Service-Ebene zu rendern, ohne ControllerContext weitergeben zu müssen, finden Sie hier einen guten Rick Strahl-Artikel http://www.codemag.com/Article/1312081 , der eine generische Zeichenfolge erstellt Regler. 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);
2
RickL

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

    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;

        }

    }

Mehr zu Razor Render - MVC3 View Render to String

2
Adamy

Kurzer Tipp

Fügen Sie ein stark typisiertes Modell 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);
1
longhairedsi

Hier ist eine Klasse, die ich für ASP.NETCore RC2 geschrieben habe. 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();
            }
        }
    }
}
0
Joe Audette

Um eine unbekannte Frage zu beantworten, werfen Sie einen Blick auf MvcIntegrationTestFramework .

Es erspart Ihnen, Ihre eigenen Helfer zu schreiben, um die Ergebnisse zu streamen, und funktioniert nachweislich gut genug. Ich würde davon ausgehen, dass dies in einem Testprojekt vorkommt und dass Sie als Bonus die anderen Testfunktionen haben, sobald Sie dieses Setup haben. Die Hauptstörung würde wahrscheinlich darin bestehen, 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"));
        });
}
0
dove

Ich habe einen besseren Weg gefunden, um die Rasiermesserseite zu rendern, als ich eine Fehlermeldung mit den oben genannten Methoden erhielt. 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());
        }
    }
0
dexiang