如何渲染一个ASP。NET MVC视图作为字符串?

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

这在ASP中可能吗?NET MVC beta版?

我试过很多例子:

1. 在ASP。NET MVC Beta版 < br / >

如果我使用这个例子,我会收到“HTTP后不能重定向”

. Headers has been sent.

2. MVC框架:捕获视图的输出 < br / >

如果我使用这个,我似乎无法做一个重定向动作,因为它 试图呈现一个可能不存在的视图。如果我返回视图 完全搞砸了,看起来一点都不对。

有人对我提出的这些问题有什么想法/解决方案吗?或者有什么更好的建议吗?

很多谢谢!

下面是一个例子。我要做的是创建GetViewForEmail方法:

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

接受蒂姆·斯科特的回答(我做了一些修改和格式化):

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调用订单确认电子邮件。主人的位置。

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

这对我来说很管用:

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

快速提示

对于强类型模型,只需将其添加到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);

我使用的是MVC 1.0 RTM,上面的解决方案都不适合我。但这一个做到了:

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

我发现了一个新的解决方案,可以将视图呈现为字符串,而不必与当前HttpContext的Response流(它不允许您更改响应的ContentType或其他报头)混淆。

基本上,你所要做的就是为视图创建一个伪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上的头文件不会抛出"发送HTTP报头后,服务器无法设置内容类型"异常)。

更新:在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);


//...

为了重复一个更未知的问题,看一下MvcIntegrationTestFramework

它使您节省编写自己的助手流的结果,并被证明工作得足够好。我假设这将在一个测试项目中,作为奖励,一旦您获得了这个设置,您将拥有其他测试功能。主要的麻烦可能是整理依赖链。

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

这是我想到的,它对我很有用。我将以下方法添加到控制器基类中。(你可以在其他地方创建这些静态方法,接受控制器作为参数)

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

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

编辑:添加剃刀代码。

我从另一个网站上看到了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视图渲染字符串

如果你想完全放弃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);

这里使用了很棒的开源剃须刀引擎: https://github.com/Antaris/RazorEngine < / p >

这个答案不在我的路上。这最初来自https://stackoverflow.com/a/2759898/2318354,但在这里我已经展示了使用“Static”关键字的方法,以使它对所有控制器通用。

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

这个例子是剃须刀。

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

现在你可以从你的控制器调用这个类,通过在你的控制器文件中添加命名空间,通过将“this”作为参数传递给控制器,如下所示。

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

正如@Sergey给出的建议,这个扩展方法也可以从控制器调用,如下所示

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

我希望这将有助于您使代码干净整洁。

这篇文章描述了在不同的场景下如何将一个View呈现给一个字符串:

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

解决方案/代码作为一个名为ViewRenderer的类提供。这是Rick Stahl的在GitHub的WestwindToolkit的一部分。

使用(3。- WebAPI示例):

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

你可以用这种方法获取字符串中的视图

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

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

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

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

下面是我为ASP编写的一个类。NETCore RC2。我使用它,所以我可以生成html电子邮件使用Razor。

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();
}
}
}
}
我发现了一个更好的方法来渲染剃刀视图页面,当我得到错误的方法上面,这个解决方案的web表单环境和mvc环境。

.不需要控制器

下面是代码示例,在这个示例中,我模拟了一个mvc动作与异步http处理程序:

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

为了在不传递ControllerContext的情况下在服务层中呈现一个字符串的视图,Rick Strahl在这里有一篇很好的文章http://www.codemag.com/Article/1312081,它创建了一个通用控制器。代码摘要如下:

// 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类中呈现View:

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

asp.net CORE的额外提示:

接口:

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

实现:

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

Startup.cs中注册

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

在控制器中的使用:

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

对我来说最简单的方法是:

  public string GetFileAsString(string path)
{
var html = "";


FileStream fileStream = new FileStream(path, FileMode.Open);


using (StreamReader reader = new StreamReader(fileStream))
{
html += reader.ReadLine();
}


return html;
}

我在电子邮件中使用这个,并确保文件只包含CSS和HTML