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

Dan*_*son 473 asp.net-mvc rendering

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

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

我尝试过多个例子:

1. ASP.NET MVC Beta中的RenderPartial到String

如果我使用此示例,则会收到"在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);
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例

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

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

Ben*_*esh 563

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

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

剃刀.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();
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑:添加了Razor代码.

  • 将视图呈现为字符串总是"与整个路由概念不一致",因为它与路由无关.我不确定为什么一个有效的答案得到了投票. (28认同)
  • 我想你可能需要从Razor版本的方法声明中删除"静态",否则它找不到ControllerContext等. (4认同)
  • 您需要为那些多余的空格实现自己的删除方法.我能想到的最好的方法是将字符串加载到XmlDocument中,然后根据我在上一条评论中留下的链接将其写回带有XmlWriter的字符串.我真的希望有所帮助. (3认同)
  • 嗯,我应该如何使用WebApi控制器,任何建议将不胜感激 (3认同)
  • 大家好,所有控制器都使用"静态"关键字使它变得常见,你必须创建静态类,在其中你必须把这个方法与"this"作为参数放到"ControllerContext".你可以在http://stackoverflow.com/a/18978036/2318354看到它. (3认同)

Dil*_*165 67

这个答案不在我的路上.这最初来自/sf/answers/193192891/, 但在这里我已经展示了将它与"静态"关键字一起使用的方法,使其适用于所有控制器.

为此,你必须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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

string result = RazorViewToString.RenderRazorViewToString(this ,"ViewName", model);
Run Code Online (Sandbox Code Playgroud)

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

string result = this.RenderRazorViewToString("ViewName", model);
Run Code Online (Sandbox Code Playgroud)

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


Tim*_*ott 32

这对我有用:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Lor*_*zCK 29

我发现了一个新的解决方案,可以将视图呈现为字符串,而不必混淆当前HttpContext的响应流(它不允许您更改响应的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
}
Run Code Online (Sandbox Code Playgroud)

这适用于ASP.NET MVC 1.0,以及ContentResult,JsonResult等.(更改原始HttpResponse上的Headers不会抛出" 服务器无法在发送HTTP标头后设置内容类型 "异常).

更新:在ASP.NET MVC 2.0 RC中,代码有所改变,因为我们必须传入StringWriter用于将视图写入ViewContext:

//...

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

//...
Run Code Online (Sandbox Code Playgroud)


Jen*_*lly 11

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

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

解决方案/代码作为名为ViewRenderer的类提供.它是Rick Stahl 在GitHubWestwindToolkit的一部分.

用法(3. - WebAPI示例):

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

  • 另外作为NuGet包West Wind Web MVC Utilities(https://www.nuget.org/packages/Westwind.Web.Mvc/).作为奖励,视图渲染器不仅可以渲染部分视图,还可以渲染包括布局在内的整个视图.博客文章包含代码:https://weblog.west-wind.com/posts/2012/May/30/Rendering-ASPNET-MVC-Views-to-String (3认同)

Jos*_*Noe 8

如果你想完全放弃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);
Run Code Online (Sandbox Code Playgroud)

这里使用了很棒的开源Razor引擎:https: //github.com/Antaris/RazorEngine


Mar*_*cin 6

ASP NET CORE 的附加提示:

界面:

public interface IViewRenderer
{
  Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model);
}
Run Code Online (Sandbox Code Playgroud)

执行:

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

注册 Startup.cs

...
 services.AddSingleton<IViewRenderer, ViewRenderer>();
...
Run Code Online (Sandbox Code Playgroud)

以及在控制器中的用法:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

您将使用这种方式获取字符串视图

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们以两种方式称呼此方法

string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", null)
Run Code Online (Sandbox Code Playgroud)

要么

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)
Run Code Online (Sandbox Code Playgroud)


Ric*_*ckL 5

要将视图渲染到服务层中的字符串而不必传递 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;
}
Run Code Online (Sandbox Code Playgroud)

然后在服务类中呈现视图:

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