首页 文章

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

提问于
浏览
442

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

这是否可以在ASP.NET MVC beta中使用?

我尝试过多个例子:

1. RenderPartial to String in ASP.NET MVC Beta

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

2. MVC Framework: Capturing the output of a view

如果我使用它,我似乎无法执行redirectToAction,因为它尝试渲染可能不存在的视图 . 如果我确实返回了视图,那么它完全搞砸了,看起来并不正确 .

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

非常感谢!

以下是一个例子 . 我要做的是创建 GetViewForEmail method

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

Accepted answer from Tim Scott (changed and formatted a little by me):

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

Example usage

假设来自控制器的呼叫获取订单确认电子邮件,则传递Site.Master位置 .

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

14 回答

  • 3

    我在另一个网站上看到了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;
    
            }
    
        }
    

    更多关于Razor render- MVC3 View Render to String

  • 28

    你是用这种方式得到字符串中的视图

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

    这是我想出来的,它对我有用 . 我将以下方法添加到我的控制器基类中 . (您可以随时将这些静态方法设置为接受控制器作为参数的其他地方)

    MVC2 .ascx style

    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 style

    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代码 .

  • 57

    这个答案不在我的路上 . 这最初来自https://stackoverflow.com/a/2759898/2318354但是在这里我已经展示了将它与"Static"关键字一起使用以使其适用于所有控制器的方法 .

    为此,你必须在类文件中生成 static 类 . (假设您的类文件名是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();
            }
        }
    }
    

    现在,您可以通过将“this”作为参数传递给Controller,在控制器文件中添加NameSpace,从控制器调用此类 .

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

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

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

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

  • 0

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

    基本上,您所做的就是为视图创建一个假的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标头后设置内容类型”异常) .

    在ASP.NET MVC 2.0 RC中 Update: ,代码有所改变,因为我们必须传入用于将视图写入 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);
    
    //...
    
  • 10

    这是我为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();
                }
            }
        }
    }
    
  • 525

    我使用的是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
    
  • 4

    要从一个更未知的问题重复,请看一下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"));
            });
    }
    
  • 0

    小建议

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

    要在服务层中呈现字符串视图而不必传递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);
    
  • 2

    这对我有用:

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

    This article描述了如何在不同场景中将视图呈现为字符串:

    • MVC Controller调用另一个自己的ActionMethods

    • MVC Controller调用另一个MVC控制器的ActionMethod

    • WebAPI Controller调用MVC控制器的ActionMethod

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

    Usage (3. - WebAPI示例):

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

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

  • 1

    当我遇到上述方法的错误时,我找到了一种更好的方法来渲染剃刀视图页面,这个解决方案适用于Web表单环境和mvc环境 . 不需要控制器 .

    下面是代码示例,在此示例中,我使用异步http处理程序模拟了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());
            }
        }
    

相关问题