【问题标题】:Razor Pages PDF generation with Rotativa - @Model null使用 Rotativa 生成 Razor Pages PDF - @Model null
【发布时间】:2021-04-18 23:47:20
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    如果我更改 return new ViewAsPdf("DetailsPdf", this);返回页面(); @Model 不为空,但之后只是常规视图而不是 pdf 文件。

    那是因为ViewAsPdf 不是为 Razor 页面设计的。 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); return new 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;
    }
    

    【讨论】:

    • 很好的解决方案,谢谢。我只将 OnGet 方法的返回类型更改为IActionResult ,因为没有它我们无法使用RedirectToPage() 方法。但通常一切都很好。
    • 准确!只是一个简单的编辑,我不得不将this._razorPageName = httpContext.Request.RouteValues["page"].ToString().Trim('/'); 替换为this._razorPageName = pageModel.RouteData.Values["page"].ToString().Trim('/');
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多