c# - 以编程方式将 Razor 页面呈现为 HTML 字符串
问题描述
目标
- 我正在尝试在后端生成一个 HTML 字符串,因为我想使用 HtmlToPDF 库将其转换为 PDF。
- 我还希望能够在浏览器中轻松查看生成的 HTML,以进行调试/调整。该页面仅在
IsDevelopment()
. - 我希望它尽可能简单。
我正在使用 ASP.NET Core 3.1
方法
剃刀页面
我想我会尝试新的Razor Pages,因为它们被宣传为超级简单。
@page
@using MyProject.Pages.Pdf
@model IndexModel
<h2>Test</h2>
<p>
@Model.Message
</p>
namespace MyProject.Pages.Pdf
{
public class IndexModel : PageModel
{
private readonly MyDbContext _context;
public IndexModel(MyDbContext context)
{
_context = context;
}
public string Message { get; private set; } = "PageModel in C#";
public async Task<IActionResult> OnGetAsync()
{
var count = await _context.Foos.CountAsync();
Message += $" Server time is { DateTime.Now } and the Foo count is { count }";
return Page();
}
}
}
这适用于浏览器 - 耶!
渲染并获取 HTML 字符串
我发现Render a Razor Page to string这似乎可以满足我的要求。
但这就是麻烦开始的地方:(
问题
首先,我觉得很奇怪的是,当您通过_razorViewEngine.FindPage
它找到页面时不知道如何填充ViewContext
或Model
. 我认为的工作IndexModel
是填充这些。我希望可以向 ASP.NET 询问IndexModel
页面,就是这样。
无论如何……下一个问题。为了呈现页面,我必须手动创建一个ViewContext
并且我必须为它提供一个Model
. 但是 Page 是 Model,因为它是一个 Page,所以它不是一个简单的 ViewModel。它依赖于 DI,并期望OnGetAsync()
被执行以填充模型。这几乎是第 22 条规则。
我还尝试通过获取视图而不是页面,_razorViewEngine.FindView
但这也需要模型,所以我们回到了 catch-22。
另一个问题。调试/调整页面的目的是轻松查看生成的内容。但是,如果我必须创建一个Model
外部IndexModel
,那么它就不再代表某处服务中实际生成的内容。
这一切都让我想知道我是否走在正确的道路上。还是我错过了什么?
解决方案
请参考以下步骤将部分视图渲染为字符串:
将接口添加到名为 IRazorPartialToStringRenderer.cs 的 Services 文件夹。
public interface IRazorPartialToStringRenderer { Task<string> RenderPartialToStringAsync<TModel>(string partialName, TModel model); }
使用以下代码将 C# 类文件添加到名为 RazorPartialToStringRenderer.cs 的 Services 文件夹中:
using System; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; namespace RazorPageSample.Services { public class RazorPartialToStringRenderer : IRazorPartialToStringRenderer { private IRazorViewEngine _viewEngine; private ITempDataProvider _tempDataProvider; private IServiceProvider _serviceProvider; public RazorPartialToStringRenderer( IRazorViewEngine viewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) { _viewEngine = viewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; } public async Task<string> RenderPartialToStringAsync<TModel>(string partialName, TModel model) { var actionContext = GetActionContext(); var partial = FindView(actionContext, partialName); using (var output = new StringWriter()) { var viewContext = new ViewContext( actionContext, partial, new ViewDataDictionary<TModel>( metadataProvider: new EmptyModelMetadataProvider(), modelState: new ModelStateDictionary()) { Model = model }, new TempDataDictionary( actionContext.HttpContext, _tempDataProvider), output, new HtmlHelperOptions() ); await partial.RenderAsync(viewContext); return output.ToString(); } } private IView FindView(ActionContext actionContext, string partialName) { var getPartialResult = _viewEngine.GetView(null, partialName, false); if (getPartialResult.Success) { return getPartialResult.View; } var findPartialResult = _viewEngine.FindView(actionContext, partialName, false); if (findPartialResult.Success) { return findPartialResult.View; } var searchedLocations = getPartialResult.SearchedLocations.Concat(findPartialResult.SearchedLocations); var errorMessage = string.Join( Environment.NewLine, new[] { $"Unable to find partial '{partialName}'. The following locations were searched:" }.Concat(searchedLocations)); ; throw new InvalidOperationException(errorMessage); } private ActionContext GetActionContext() { var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider }; return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); } } }
ConfigureServices
在类的方法中注册服务Startup
:public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddTransient<IRazorPartialToStringRenderer, RazorPartialToStringRenderer>(); }
使用 RenderPartialToStringAsync() 方法将 Razor 页面呈现为 HTML 字符串:
public class ContactModel : PageModel { private readonly IRazorPartialToStringRenderer _renderer; public ContactModel(IRazorPartialToStringRenderer renderer) { _renderer = renderer; } public void OnGet() { } [BindProperty] public ContactForm ContactForm { get; set; } [TempData] public string PostResult { get; set; } public async Task<IActionResult> OnPostAsync() { var body = await _renderer.RenderPartialToStringAsync("_ContactEmailPartial", ContactForm); //transfer model to the partial view, and then render the Partial view to string. PostResult = "Check your specified pickup directory"; return RedirectToPage(); } } public class ContactForm { public string Email { get; set; } public string Message { get; set; } public string Name { get; set; } public string Subject { get; set; } public Priority Priority { get; set; } } public enum Priority { Low, Medium, High }
调试截图如下:
更多详细步骤,请查看此博客Rendering A Partial View To A String。
推荐阅读
- error-handling - Apache Flink - “keyBy”中的异常处理
- three.js - 如何在three.js中单击更改几何颜色
- c++ - 使用“const char*”和“char*”参数连接两个第三方模块
- powershell - 大型文本文件的 -match 操作的速度问题
- unix - 外部过滤器搜索任何数字在数据阶段失败,预期操作员名称,得到:“[”,第 134 行;文字:pxbridge
- python-3.x - 如何修复 FileNotFoundError: [WinError 2] 系统找不到指定的文件
- accordion - amp-accordion 上的 Deeplink
- cmake - 如何修复 Buildozer 构建失败并出现 cmake 错误
- swift - 如何快速从模型中搜索数据?
- r - 根据数据分配绘图颜色