以stringforms呈现视图

我想输出两个不同的视图(其中一个作为一个string,将作为电子邮件发送),另一个页面显示给用户。

这是可能的ASP.NET MVCtesting版?

我已经尝试了多个例子:

1. 在ASP.NET MVC Beta中将RenderPartial转换为string

如果我使用这个例子,我会收到“HTTP头被发送后无法redirect”。

2. MVC框架:捕获视图的输出

如果我使用这个,我似乎无法做一个redirectToAction,因为它试图呈现一个可能不存在的视图。 如果我确实返回了这个观点,那就完全搞砸了,根本看不正确。

有没有人有任何想法/解决这些问题,我有,或者有更好的build议吗?

非常感谢!

下面是一个例子。 我想要做的是创buildGetViewForEmail方法

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

Tim Scott接受的答案(由我更改和格式化):

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

用法示例

假设来自控制器的电话获得订单确认电子邮件,通过Site.Master位置。

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

这就是我想出来的,而且是为我工作的。 我将下面的方法添加到我的控制器基类中。 (你总是可以使这些静态方法在其他地方接受控制器作为我想的参数)

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

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

编辑:添加剃刀代码。

这个答案不在我的路上。 这是从https://stackoverflow.com/a/2759898/2318354原来,但在这里我已经显示了与“静态”关键字使用它的方式,使所有的控制器通用。;

为此,您必须在类文件中创buildstatic类。 (假设你的类文件名是Utils.cs)

这个例子是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(); } } } 

现在,您可以通过在控制器文件中添加NameSpace,将“this”作为parameter passing给Controller,从而从控制器调用此类。

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

我希望这会对你使代码干净整洁有用。

这适用于我:

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

我发现了一个新的解决scheme,可以使视图呈现string,而不必混淆当前HttpContext的响应stream(不允许您更改响应的ContentType或其他标题)。

基本上,你所做的只是为视图创build一个假的HttpContext来呈现它自己:

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

这与ASP.NET MVC 1.0以及ContentResult,JsonResult等一起工作(改变原始HttpResponse上的Headers不会抛出“ 服务器在发送HTTP头之后无法设置内容types ”exception)。

更新:在ASP.NET MVC 2.0 RC中,代码发生了一些变化,因为我们必须传入用于将视图写入ViewContextStringWriter

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

本文介绍如何在不同的情况下将视图呈现为string:

  1. MVC控制器调用另一个自己的ActionMethods
  2. MVC控制器调用另一个MVC控制器的ActionMethod
  3. WebAPI控制器调用MVC控制器的ActionMethod

解决scheme/代码作为名为ViewRenderer的类提供。 它是GitHub的Rick Stahl的WestwindToolkit的一部分。

用法 (3. – WebAPI示例):

 string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id)); 

如果你想完全放弃MVC,从而避免所有的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); 

这里使用真棒开源Razor引擎: https : //github.com/Antaris/RazorEngine

我正在使用MVC 1.0 RTM,并没有上述解决scheme为我工作。 但是这个做了:

 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 

我从另一个网站看到MVC 3和Razor的实现,它为我工作:

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

更多关于剃刀渲染 – MVC3查看渲染到string

你用这种方式得到string中的视图

 protected string RenderPartialViewToString(string viewName, object model) { try { 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(); } } catch (Exception ex) { throw ex } return string.Empty; } 

我们用两种方法来调用这个方法

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

快速提示

对于强types的模型,只需将其添加到ViewData.Model属性,然后传递给RenderViewToString。 例如

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

重复一个更加未知的问题,看看MvcIntegrationTestFramework 。

这使得您可以节省您编写自己的助手来传输结果,并被certificate工作得很好。 我会假设这将在一个testing项目中,作为一个奖励,你将拥有其他testingfunction,一旦你有这个设置。 主要麻烦可能是整理依赖链。

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

这里是我写的为ASP.NETCore RC2做的这个类。 我使用它,所以我可以使用Razor生成HTML电子邮件。

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

我发现了一个更好的方式来渲染剃刀视图页面,当我得到上述方法的错误,这个解决scheme为web表单环境和mvc环境。 不需要控制器。

这里是代码示例,在这个例子中,我使用asynchronoushttp处理程序模拟了mvc操作:

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

为了在服务层渲染一个string的视图,而不必传递ControllerContext,这里有一个很好的Rick Strahl文章http://www.codemag.com/Article/1312081 ,它创build了一个通用控制器。 代码总结如下:

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

然后在Service类中呈现视图:

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