首页 > 解决方案 > 使用 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();@Modelnull

任何想法如何解决这个问题?

标签: asp.net-corepdf-generationwkhtmltopdfrazor-pages

解决方案


如果我更改 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;
}

推荐阅读