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);
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.
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.
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;
}
}
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);
//...
Dieser Artikel beschreibt das Rendern einer Ansicht für einen String in verschiedenen Szenarien:
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));
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
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)
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
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);
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;
}
}
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);
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();
}
}
}
}
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"));
});
}
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());
}
}