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

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"关键字一起使用,以使其对所有控制器都通用。

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

这个例子适用于剃须刀。

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中,代码发生了一些变化,因为我们必须传递用于将视图写入ViewContextStringWriter

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

//...


本文介绍如何在不同的场景中将视图呈现为字符串:

  • MVC控制器调用自己的另一个actionMethods
  • MVC控制器调用另一个MVC控制器的ActionMethod
  • WebAPI控制器调用MVC控制器的ActionMethod
  • 解决方案/代码作为一个名为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"));
            });
    }