asp.net-core - 使用 Rotativa 生成 Razor Pages PDF - @Model null
问题描述
我有一个使用 .net core 3.1 Razor Pages 构建的 Web 应用程序。我需要添加从视图生成 PDF 的功能。通常该库正在工作,因为我可以生成静态 PDF,但是当我想使用模型为模板播种时会出现问题。
PageModel
看起来像这样:
public class DetailsPdfModel : PageModel
{
private readonly ICablesData cablesData;
private readonly IConfiguration configuration;
public DetailsPdfModel(ICablesData cablesData, IConfiguration configuration)
{
this.cablesData = cablesData;
this.configuration = configuration;
}
public Cable Cable { get; set; }
public IActionResult OnGet(int cableId)
{
Cable = cablesData.GetById(cableId);
if (Cable == null)
{
return RedirectToPage("NotFound");
}
else
{
return new ViewAsPdf("DetailsPdf", this);
}
}
}
视图如下所示:
@page
@model DetailsPdfModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>DetailsPdf</title>
</head>
<body>
<p>@Model.Cable.Name</p>
</body>
</html>
当我试图获取 pdf 时,会发生异常。我注意到@Model
总是null
。如果我将其更改为return new ViewAsPdf("DetailsPdf", this);
不是,但之后只是常规视图而不是 pdf 文件。return Page();
@Model
null
任何想法如何解决这个问题?
解决方案
如果我更改 return new ViewAsPdf("DetailsPdf", this); 返回页面();@Model 不为空,但之后只是常规视图而不是 pdf 文件。
那是因为ViewAsPdf
不是为 Razor Page 设计的。并且Rotativa
不公开 RazorPage 的内置 API。有关详细信息,请参阅Rotativa.AspNetCore的源代码。
作为一种解决方法,您可以创建一个自定义RazorPageAsPdf
类来实现与以下相同的目标:
公共类 DetailsPdfModel : PageModel { ...公共 IActionResult OnGet(int cableId)公共 RazorPageAsPdf OnGet(int cableId) { 电缆 = cableData.GetById(cableId); 如果(电缆 == 空) { return RedirectToPage("未找到"); } 别的 {return new ViewAsPdf("DetailsPdf", this);返回新的 RazorPageAsPdf(this); // 我们不需要页面路径,因为它可以由当前路由确定 } } }
这是我的 RazorPageAsPdf 实现供您参考:
public class RazorPageAsPdf : AsPdfResultBase
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IRazorPageActivator _activator;
private string _razorPageName {get;set;}
public PageModel PageModel {get;set;}
public RazorPageAsPdf(PageModel pageModel)
{
PageModel = pageModel;
var httpContext = pageModel.HttpContext;
this._razorPageName = httpContext.Request.RouteValues["page"].ToString().Trim('/');
if(string.IsNullOrEmpty(_razorPageName)){
throw new ArgumentException("there's no such a 'page' in this context");
}
this._razorViewEngine = httpContext.RequestServices.GetRequiredService<IRazorViewEngine>();
this._tempDataProvider= httpContext.RequestServices.GetRequiredService<ITempDataProvider>();
this._activator = httpContext.RequestServices.GetRequiredService<IRazorPageActivator>();
}
private ViewContext GetViewContext( ActionContext actionContext, IRazorPage page, StringWriter sw)
{
var view = new RazorView( _razorViewEngine, _activator, new List<IRazorPage>(), page, HtmlEncoder.Default, new DiagnosticListener(nameof(RazorPageAsPdf)));
return new ViewContext( actionContext, view, this.PageModel.ViewData, this.PageModel.TempData, sw, new HtmlHelperOptions());
}
private async Task<string> RenderPageAsString(ActionContext actionContext){
using (var sw = new StringWriter())
{
var pageResult = this._razorViewEngine.FindPage(actionContext, this._razorPageName);;
if (pageResult.Page == null)
{
throw new ArgumentNullException($"The page {this._razorPageName} cannot be found.");
}
var viewContext = this.GetViewContext(actionContext, pageResult.Page, sw);
var page = (Page)pageResult.Page;
page.PageContext = this.PageModel.PageContext;
page.ViewContext = viewContext;
_activator.Activate(page, viewContext);
await page.ExecuteAsync();
return sw.ToString();
}
}
protected override async Task<byte[]> CallTheDriver(ActionContext actionContext)
{
var html = await this.RenderPageAsString(actionContext);
// copied from https://github.com/webgio/Rotativa.AspNetCore/blob/c907afa8c7dd6a565d307901741c336c429fc698/Rotativa.AspNetCore/ViewAsPdf.cs#L147-L151
string baseUrl = string.Format("{0}://{1}", actionContext.HttpContext.Request.Scheme, actionContext.HttpContext.Request.Host);
var htmlForWkhtml = Regex.Replace(html.ToString(), "<head>", string.Format("<head><base href=\"{0}\" />", baseUrl), RegexOptions.IgnoreCase);
byte[] fileContent = WkhtmltopdfDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), htmlForWkhtml);
return fileContent;
}
protected override string GetUrl(ActionContext context) => string.Empty;
}
推荐阅读
- java - 控制器方法上的@Transactional 不会回滚事务,但服务方法上会
- r - 我想在 if else 循环中从一行写入两个不同的列
- javascript - 将带有内容的文件保存为带有 NaN 内容的数字结果
- kotlin - 如何使用 Kotlin/Native 从/向控制台读取/写入?
- python - 迭代一个 MD 数组
- spring-batch - Spring-batch:在面向块的处理中重复步骤
- javascript - 是否可以在 Express 中为给定的模板引擎添加全局渲染回调?
- android - 从自定义类获取时间戳
- spring - 带有 VueJs 的 Spring websocket - 被 CORS 阻止
- html - 尝试将变量从我的 Google 脚本传递到 HtmlOutputFromFile 时,在 HtmlService 中找不到 createHtmlTemplateFromFile