如何将ASP.NET MVC视图呈现为字符串?

最后都变了- 提交于 2020-02-27 08:42:09

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

在ASP.NET MVC beta中可能吗?

我尝试了多个示例:

1. 在ASP.NET MVC Beta中将RenderPartial转换为字符串

如果使用此示例,则会收到“发送HTTP标头后无法重定向”。

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

如果使用此方法,则似乎无法执行redirectToAction,因为它尝试呈现可能不存在的视图。 如果我确实返回视图,则它完全被弄乱了,看起来根本不正确。

是否有人对我遇到的这些问题有任何想法/解决方案,或者对更好的问题有任何建议?

非常感谢!

下面是一个例子。 我想做的是创建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);
}

蒂姆·斯科特(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);

#1楼

我正在使用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

#2楼

我找到了一个新的解决方案,该视图可以将视图呈现为字符串,而不必弄乱当前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);

//...

#3楼

如果您想完全放弃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 Engine: https//github.com/Antaris/RazorEngine


#4楼

这个答案不在我身边。 这最初来自https://stackoverflow.com/a/2759898/2318354,但在这里,我展示了将其与“静态”关键字一起使用以使其对所有Controller通用的方法。

为此,您必须在类文件中创建static类。 (假设您的类文件名是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”作为参数传递给Controller的方式,通过在Controller File中添加NameSpace的方式从控制器中调用此类。

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

根据@Sergey的建议,此扩展方法也可以从cotroller调用,如下所示

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

我希望这对您使代码整洁有帮助。


#5楼

要重复一个更未知的问题,请看一下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"));
        });
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!