求助:将Razor页面转换为PDF的可行替代方案
Razor页面转PDF替代方案
我尝试用IronPDF和iTextSharp将Razor页面转为PDF时持续报错,排查错误时还导致应用无响应,现在卡在这个任务上,求其他实现方案和代码。以下是待转换的Razor页面及后台代码:
待转换的Razor页面(Print.cshtml)
@page @model TestApp01.Pages.Clients.PrintModel @{} <br> <h2>Edit Client</h2> @if (Model.errorMessage.Length > 0) { <div class='alert alert-warning alert-dismissible fade show' role='alert'> <strong>@Model.errorMessage</strong> <button type='button' class='btn-close' data-bs-dismiss='alert' aria-label='Close'></button> </div> } <form method="POST"> <input type="hidden" name="id" value="@Model.clientInfo.id"/> <div class="row mb-1"> <label class="col-sm-3 col-form-label">Policy Number:</label> <div class="col-sm-6"> <input type="text" class="form-control" name="policy_number" value="@Model.clientInfo.policy_number" readonly/> </div> </div> <div class="row mb-1"> <label class="col-sm-3 col-form-label">First Name:</label> <div class="col-sm-6"> <input type="text" class="form-control" name="first_name" value="@Model.clientInfo.first_name" readonly/> </div> </div> <div class="row mb-1"> <label class="col-sm-3 col-form-label">Last Name:</label> <div class="col-sm-6"> <input type="text" class="form-control" name="last_name" value="@Model.clientInfo.last_name" readonly/> </div> </div> <div class="row mb-1"> <label class="col-sm-3 col-form-label">Email:</label> <div class="col-sm-6"> <input type="text" class="form-control" name="email" value="@Model.clientInfo.email" readonly/> </div> </div> <div class="row mb-1"> <label class="col-sm-3 col-form-label">Phone:</label> <div class="col-sm-6"> <input type="text" class="form-control" name="phone" value="@Model.clientInfo.phone" readonly/> </div> </div> <div class="row mb-1"> <label class="col-sm-3 col-form-label">Address:</label> <div class="col-sm-6"> <input type="text" class="form-control" name="address" value="@Model.clientInfo.address" readonly/> </div> </div> <div class="row mb-3"> <div class="offset-sm-3 col-sm-3 d-grid"> <button type="submit" class="btn btn-primary" value="Submit">Submit</button> </div> <div class="col-sm-3 d-grid"> <a class="btn btn-outline-primary" href="/Clients/Index" role="button">Cancel</a> </div> </div> </form>
对应的后台代码(Print.cshtml.cs)
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using System.Data.SqlClient; namespace TestApp01.Pages.Clients { public class PrintModel : PageModel { public ClientInfo clientInfo = new ClientInfo(); public string errorMessage = ""; public string successMessage = ""; public void OnGet() { string id = Request.Query["id"]; try { string connectionString = "myDB"; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); string sql = "SELECT * FROM clients WHERE id=@id"; using (SqlCommand command = new SqlCommand(sql, connection)) { command.Parameters.AddWithValue("@id", id); using (SqlDataReader reader = command.ExecuteReader()) { if (reader.Read()) { clientInfo.id = "" + reader.GetInt32(0); clientInfo.policy_number = reader.GetString(1); clientInfo.first_name = reader.GetString(2); clientInfo.last_name = reader.GetString(3); clientInfo.email = reader.GetString(4); clientInfo.phone = reader.GetString(5); clientInfo.address = reader.GetString(6); } } } } } catch (Exception ex) { errorMessage = ex.Message; } } public void OnPost() { clientInfo.id = Request.Form["id"]; clientInfo.policy_number = Request.Form["policy_number"]; clientInfo.first_name = Request.Form["first_name"]; clientInfo.last_name = Request.Form["last_name"]; clientInfo.email = Request.Form["email"]; clientInfo.phone = Request.Form["phone"]; clientInfo.address = Request.Form["address"]; if (clientInfo.policy_number.Length == 0 || clientInfo.first_name.Length == 0 || clientInfo.last_name.Length == 0 || clientInfo.email.Length == 0 || clientInfo.phone.Length == 0 || clientInfo.address.Length == 0) { errorMessage = "All the fields are required"; return; } } } // 补充原代码缺失的ClientInfo类 public class ClientInfo { public string id { get; set; } public string policy_number { get; set; } public string first_name { get; set; } public string last_name { get; set; } public string email { get; set; } public string phone { get; set; } public string address { get; set; } } }
替代方案1:使用DinkToPdf(基于wkhtmltopdf)
这是稳定性较高的方案,依赖wkhtmltopdf的HTML渲染能力,支持完整的CSS和JavaScript。
实现步骤:
- 安装NuGet包:
Install-Package DinkToPdf # 根据服务器系统安装对应运行时包,例如Windows系统: Install-Package DinkToPdf.Runtime.Windows
- 在
Program.cs中注册PDF转换器服务:
using DinkToPdf; using DinkToPdf.Contracts; var builder = WebApplication.CreateBuilder(args); // 注册同步PDF转换器 builder.Services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools())); // 其他服务注册 builder.Services.AddRazorPages(); var app = builder.Build(); // 中间件配置... app.Run();
- 修改
PrintModel,添加生成PDF的处理方法:
using DinkToPdf.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; using System.IO; using System.Threading.Tasks; namespace TestApp01.Pages.Clients { public class PrintModel : PageModel { private readonly IConverter _pdfConverter; private readonly ICompositeViewEngine _viewEngine; // 注入转换器和视图引擎 public PrintModel(IConverter pdfConverter, ICompositeViewEngine viewEngine) { _pdfConverter = pdfConverter; _viewEngine = viewEngine; } // 原有OnGet、OnPost方法保持不变... // 新增生成PDF的POST处理方法 public async Task<IActionResult> OnPostGeneratePdf() { // 渲染当前Razor页面为HTML字符串 var htmlContent = await RenderViewToStringAsync(); // 配置PDF文档参数 var pdfConfig = new HtmlToPdfDocument { GlobalSettings = { ColorMode = ColorMode.Color, Orientation = Orientation.Portrait, PaperSize = PaperKind.A4, Margins = new MarginSettings { Top = 15, Bottom = 15, Left = 10, Right = 10 } }, Objects = { new ObjectSettings { HtmlContent = htmlContent, WebSettings = { DefaultEncoding = "utf-8", // 设置基础URL,确保外部CSS/图片能加载 BaseUrl = $"{Request.Scheme}://{Request.Host}" }, HeaderSettings = { FontSize = 10, Center = "Client Details", Line = true }, FooterSettings = { FontSize = 8, Right = $"Page [page] of [toPage] | Generated on {DateTime.Now:yyyy-MM-dd}" } } } }; // 生成PDF字节数组 var pdfBytes = _pdfConverter.Convert(pdfConfig); // 返回PDF文件供下载 return File(pdfBytes, "application/pdf", $"Client_Details_{clientInfo.id}.pdf"); } // 辅助方法:异步渲染视图为HTML字符串 private async Task<string> RenderViewToStringAsync() { var viewResult = _viewEngine.FindView(ControllerContext, "Print", false); if (!viewResult.Success) { throw new InvalidOperationException($"无法找到视图:Print"); } using var sw = new StringWriter(); var viewContext = new ViewContext( ControllerContext, viewResult.View, ViewData, TempData, sw, new HtmlHelperOptions() ); await viewResult.View.RenderAsync(viewContext); return sw.ToString(); } } }
- 在Razor页面中添加生成PDF的按钮:
<div class="row mb-3"> <div class="offset-sm-3 col-sm-3 d-grid"> <button type="submit" class="btn btn-success" asp-page-handler="GeneratePdf">生成PDF</button> </div> <div class="col-sm-3 d-grid"> <button type="submit" class="btn btn-primary">Submit</button> </div> <div class="col-sm-3 d-grid"> <a class="btn btn-outline-primary" href="/Clients/Index" role="button">Cancel</a> </div> </div>
替代方案2:使用RazorEngineCore + PdfSharpCore
如果不想依赖外部工具,可以用纯.NET库实现,先渲染Razor模板为HTML,再转换为PDF。
实现步骤:
- 安装NuGet包:
Install-Package RazorEngineCore Install-Package PdfSharpCore Install-Package HtmlRenderer.PdfSharpCore
- 修改
PrintModel添加PDF生成方法:
using RazorEngineCore; using PdfSharpCore.Pdf; using System.IO; using System.Threading.Tasks; namespace TestApp01.Pages.Clients { public class PrintModel : PageModel { // 原有属性和方法保持不变... public async Task<IActionResult> OnPostGeneratePdf() { // 读取Razor模板文件内容 var templatePath = Path.Combine(Directory.GetCurrentDirectory(), "Pages", "Clients", "Print.cshtml"); var templateContent = await System.IO.File.ReadAllTextAsync(templatePath); // 编译并渲染模板 var razorEngine = new RazorEngine(); var compiledTemplate = razorEngine.Compile(templateContent); var htmlContent = compiledTemplate.Run(this); // 将HTML转换为PDF var pdfDocument = TheArtOfDev.HtmlRenderer.PdfSharp.PdfGenerator.GeneratePdf(htmlContent, PdfSharpCore.PageSize.A4); // 保存到内存流并返回 using var ms = new MemoryStream(); pdfDocument.Save(ms); ms.Position = 0; return File(ms, "application/pdf", $"Client_Details_{clientInfo.id}.pdf"); } } }
注意事项:
- 方案1需要服务器安装对应版本的wkhtmltopdf,或者通过NuGet安装运行时包。
- 渲染HTML时,确保外部资源(CSS、图片)使用绝对路径,或者转为内联资源。
- 如果页面中有JavaScript动态内容,方案1的wkhtmltopdf支持执行JS,方案2的HtmlRenderer对JS支持有限。
内容的提问来源于stack exchange,提问作者Cyrus Yabut
相关产品推荐
相关产品推荐

