使用 Rotativa 生成 Razor Pages PDF - @Model null

Razor Pages PDF generation with Rotativa - @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); 更改为 return Page();,则 @Model 不是 null,但之后只是常规视图而不是 pdf 文件。

有什么解决这个问题的想法吗?

If I change the return new ViewAsPdf("DetailsPdf", this); to return Page(); the @Model is not null but after that is only regular view not the pdf file.

那是因为 ViewAsPdf 不是为 Razor Page 设计的。 Rotativa 不会公开 RazorPage 的内置 API。更多详细信息,请参阅 Rotativa.AspNetCore.

的源代码

作为解决方法,您可以创建一个自定义 RazorPageAsPdf class 来实现与以下相同的目标:

public class DetailsPdfModel : PageModel
{
    ...

    public IActionResult OnGet(int cableId)
    public RazorPageAsPdf OnGet(int cableId)
    {
        Cable = cablesData.GetById(cableId);

        if (Cable == null)
        {
            return RedirectToPage("NotFound");
        }
        else
        {
            return new ViewAsPdf("DetailsPdf", this);
            return new RazorPageAsPdf(this);       // we don't need page path because it can be determined by current route
        }
    }
}

下面是我实现的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;
}