How to render an ASP.NET MVC view as a string?
我想输出两个不同的视图(一个作为字符串,将作为电子邮件发送),另一个是显示给用户的页面。
这在ASP.NET MVC测试版中是可能的吗?
我尝试过多个例子:
1。renderpartial to string in ASP.NET MVC beta
If I use this example, I receive the"Cannot redirect after HTTP
headers have been sent.".
2。MVC框架:捕获视图的输出
If I use this, I seem to be unable to do a redirectToAction, as it
tries to render a view that may not exist. If I do return the view, it
is completely messed up and doesn't look right at all.
是否有人对我的这些问题有任何想法/解决方案,或对更好的问题有任何建议?
多谢!
下面是一个例子。我要做的是创建getViewForemail方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } |
接受蒂姆·斯科特的回答(我做了一些修改和格式化):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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; } } |
示例用法
假设一个来自控制器的电话得到订单确认邮件,通过站点。主位置。
1 | string myString = RenderViewToString(this.ControllerContext,"~/Views/Order/OrderResultEmail.aspx","~/Views/Shared/Site.Master", this.ViewData, this.TempData); |
这就是我想到的,它对我很有用。我将以下方法添加到控制器基类中。(我想,您可以在其他地方使用这些静态方法来接受控制器作为参数)
mvc2.ascx样式
1 2 3 4 5 6 7 8 9 10 11 | 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样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } } |
编辑:添加了Razor代码。
这个答案不在我的路上。这最初来自https://stackoverflow.com/a/2759898/2318354,但这里我介绍了如何将其与"static"关键字一起使用,以使其对所有控制器都通用。
为此,您必须在类文件中创建
这个例子适用于剃须刀。
U.S.CS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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(); } } } |
现在,您可以从控制器中调用这个类,方法是在控制器文件中添加名称空间,如下所示,将"this"作为参数传递给控制器。
1 | string result = RazorViewToString.RenderRazorViewToString(this ,"ViewName", model); |
根据@sergey的建议,此扩展方法也可以从cotroll调用,如下所示
1 | string result = this.RenderRazorViewToString("ViewName", model); |
我希望这将有助于您使代码干净整洁。
这对我很有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; } } |
我发现了一个新的解决方案,它可以将视图呈现为字符串,而不必破坏当前httpContext的响应流(这不允许您更改响应的ContentType或其他头)。
基本上,您所要做的就是为视图创建一个假的httpContext来呈现它自己:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /// <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上的头不会引发"发送HTTP头后服务器无法设置内容类型"异常)。
更新:在ASP.NET MVC 2.0 RC中,代码发生了一些变化,因为我们必须传递用于将视图写入
1 2 3 4 5 6 7 8 9 10 | //... //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); //... |
本文介绍如何在不同的场景中将视图呈现为字符串:
解决方案/代码作为一个名为viewrenderer的类提供。它是RickStahl在Github的WestindToolkit的一部分。
用法(3)。-WebAPI示例):
1 | string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id)); |
如果你想完全放弃MVC,从而避免所有的httpContext混乱…
1 2 3 4 5 | 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/razorence网站
您可以用这种方式以字符串形式获取视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); } } |
我们以两种方式调用这个方法
1 | string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", null) |
或
1 2 | var model = new Person() string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model) |
我使用的是MVC1.0RTM,上面的所有解决方案都不适合我。但这一次是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
为了在不需要传递ControllerContext的情况下将视图呈现给服务层中的字符串,这里有一篇很好的Rick Strahl文章http://www.codemag.com/article/1312081,它创建了一个通用控制器。代码摘要如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | // 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; } |
然后在服务类中呈现视图:
1 | var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext,"~/Path/To/View/Location/_viewName.cshtml", theViewModel, true); |
我从另一个网站看到了MVC 3和Razor的实现,它对我很有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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; } } |
有关Razor渲染的详细信息-MVC3视图渲染到字符串
快速提示
对于强类型模型,只需将其添加到ViewData.Model属性中,然后再传递到renderView-ToString。例如
1 2 | this.ViewData.Model = new OrderResultEmailViewModel(order); string myString = RenderViewToString(this.ControllerContext,"~/Views/Order/OrderResultEmail.aspx","~/Views/Shared/Site.Master", this.ViewData, this.TempData); |
当上面的方法出错时,我找到了一种更好的方法来呈现Razor视图页面,这个解决方案同时适用于Web窗体环境和MVC环境。不需要控制器。
下面是代码示例,在这个示例中,我使用异步HTTP处理程序模拟了MVC操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /// <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()); } } |
这是我为asp.netcore rc2编写的一个类。我使用它,这样我就可以使用Razor生成HTML电子邮件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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(); } } } } |
要从更未知的问题中重复,请查看mvcintegrationTestFramework。
它可以节省您编写自己的助手以流式传输结果的时间,并且被证明工作得足够好。我假设这将在一个测试项目中进行,并且作为一个额外的奖励,一旦您有了这个设置,您将拥有其他的测试功能。主要的麻烦可能是整理依赖链。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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")); }); } |