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

时间:2019-12-16 00:05:26

标签: asp.net-core pdf-generation wkhtmltopdf razor-pages

我有一个使用.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文件。

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

1 个答案:

答案 0 :(得分:1)

  

如果更改返回的新ViewAsPdf(“ DetailsPdf”,此);返回Page(); @Model不为null,但之后仅是常规视图,而不是pdf文件。

这是因为ViewAsPdf不是为Razor Page设计的。而且Rotativa没有公开RazorPage的内置API。有关更多详细信息,请参见Rotativa.AspNetCore的源代码。

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

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;
}