在 ASP.NET Core 中将 html 导出为 pdf
Posted
技术标签:
【中文标题】在 ASP.NET Core 中将 html 导出为 pdf【英文标题】:Export html to pdf in ASP.NET Core 【发布时间】:2017-01-14 20:05:12 【问题描述】:我想将一段 html 导出为 pdf 文件,但我没有任何兼容的 nuget 包。
当我尝试安装任何人时:“X 与 netcoreapp1.0 (.NETCoreApp,Version=v1.0) 不兼容。”
有人知道使用 asp.net core 导出为 pdf 的任何方法吗?
【问题讨论】:
Export to pdf using ASP.NET 5的可能重复 net core 与 asp.net 5 不同,另一个框架,另一个库 AspNet Core 1.0 是新名称。 AspNet 5(以前称为 AspNet vNext)是最初的名称,但由于它是一个全新的产品,MS 决定将其名称完全更改为 AspNet Core。 看看那个答案。它目前在 IIS 上的 PoC 环境中工作,仅具有核心堆栈和节点子集,如答案本身所述。框架设置为您正在使用的内容;) 所以之前没有在此处列出 - 但对我来说非常有用的解决方案是 github.com/aaxelm/Rotativa.NetCore 的 NuGet 包 【参考方案1】:在服务器端,您可以输出 html 的 pdf,并在获得 pdf 后使用从 HTML 字符串 .NET Core 生成 PDF 的库,您需要将其传递给库,请参阅此链接以将 HTML 转换为 PDF .NET。
安装 nuget 包:Select.HtmlToPdf.NetCore
HtmlToPdf htmlToPdf = new HtmlToPdf();
htmlToPdf.Options.PdfPageOrientation = PdfPageOrientation.Portrait;
// put css in pdf
htmlToPdf.Options.MarginLeft = 15;
htmlToPdf.Options.MarginRight = 15;
---------------------------
string url = "<html><head></head><body>Hello World</body></html>"
PdfDocument pdfDocument = htmlToPdf.ConvertHtmlString(url);
byte[] pdf = pdfDocument.Save();
//convert to memory stream
Stream stream = new MemoryStream(pdf);
pdfDocument.Close();
//if want to transfer stream to file
File(stream, "application/pdf", Guid.NewGuid().ToString() + ".pdf");
【讨论】:
当心:这个答案对超过 5 页的文件没有好处【参考方案2】:如果您在 .net core 2.0 中也可以使用jsreport .net sdk,并且没有更复杂的节点服务。这包括将现有剃刀视图转换为 pdf 的其他功能过滤器。来自docs:
1。 安装 nugets jsreport.Binary、jsreport.Local 和 jsreport.AspNetCore
2。
在你Startup.cs
中配置如下
public void ConfigureServices(IServiceCollection services)
services.AddMvc();
services.AddJsReport(new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create());
3。
然后您需要将MiddlewareFilter
属性添加到特定操作并指定要使用的转换。在这种情况下 html 到 pdf 的转换。
[MiddlewareFilter(typeof(JsReportPipeline))]
public IActionResult Invoice()
HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf);
return View();
您可以在JsReportFeature()
上找到许多其他的页眉、页脚或页面布局选项。请注意,同样的方式您也可以从 html 生成 excel 文件。在documentation 中查看更多信息。
PS:我是jsreport的作者。
【讨论】:
之后如何保存文件? razor中的css和图片文件是如何处理的? ajax 调用? pdf输出与浏览器中看到的一样吗? 哇——我以前从未遇到过这种情况。我查看了该站点,这是一个非常全面的应用程序。它与 ASP.NET 的轻松集成给我留下了深刻的印象——在我使用各种方法并做很多噩梦之前。为了节省我的时间并使 PDF 生成更容易,我欠你一杯啤酒。 如何包含css?或者是否还有一个选项可以只选择 html 的特定部分来呈现为 pdf? 在依赖项和 nuget 方面遇到了很多麻烦,这太可怕了,但我设法配置了它。此外,stackoveflow 警察不断删除这些 cmets。也许他们爱上了这个作者【参考方案3】:这是一个适用于 ASP.NET Core 2.0 的解决方案,它允许从cshtml
生成动态 PDF 文件,直接将它们发送给用户和/或在发送前保存它们。
为了补充Jan Blaha answer there,为了获得更大的灵活性,您可能需要使用以下代码:
/// Generate a PDF from a html string
async Task<(string ContentType, MemoryStream GeneratedFileStream)> GeneratePDFAsync(string htmlContent)
IJsReportFeature feature = new JsReportFeature(HttpContext);
feature.Recipe(Recipe.PhantomPdf);
if (!feature.Enabled) return (null, null);
feature.RenderRequest.Template.Content = htmlContent;
var report = await _RenderService.RenderAsync(feature.RenderRequest);
var contentType = report.Meta.ContentType;
MemoryStream ms = new MemoryStream();
report.Content.CopyTo(ms);
return (contentType, ms);
使用类将 cshtml 文件呈现为字符串,您可以使用following service(可以作为作用域服务注入):
public class ViewToStringRendererService: ViewExecutor
private ITempDataProvider _tempDataProvider;
private IServiceProvider _serviceProvider;
public ViewToStringRendererService(
IOptions<MvcViewOptions> viewOptions,
IHttpResponseStreamWriterFactory writerFactory,
ICompositeViewEngine viewEngine,
ITempDataDictionaryFactory tempDataFactory,
DiagnosticSource diagnosticSource,
IModelMetadataProvider modelMetadataProvider,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
: base(viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticSource, modelMetadataProvider)
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
var context = GetActionContext();
if (context == null) throw new ArgumentNullException(nameof(context));
var result = new ViewResult()
ViewData = new ViewDataDictionary<TModel>(
metadataProvider: new EmptyModelMetadataProvider(),
modelState: new ModelStateDictionary())
Model = model
,
TempData = new TempDataDictionary(
context.HttpContext,
_tempDataProvider),
ViewName = viewName,
;
var viewEngineResult = FindView(context, result);
viewEngineResult.EnsureSuccessful(originalLocations: null);
var view = viewEngineResult.View;
using (var output = new StringWriter())
var viewContext = new ViewContext(
context,
view,
new ViewDataDictionary<TModel>(
metadataProvider: new EmptyModelMetadataProvider(),
modelState: new ModelStateDictionary())
Model = model
,
new TempDataDictionary(
context.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());
await view.RenderAsync(viewContext);
return output.ToString();
private ActionContext GetActionContext()
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = _serviceProvider;
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
/// <summary>
/// Attempts to find the <see cref="IView"/> associated with <paramref name="viewResult"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param>
/// <param name="viewResult">The <see cref="ViewResult"/>.</param>
/// <returns>A <see cref="ViewEngineResult"/>.</returns>
ViewEngineResult FindView(ActionContext actionContext, ViewResult viewResult)
if (actionContext == null)
throw new ArgumentNullException(nameof(actionContext));
if (viewResult == null)
throw new ArgumentNullException(nameof(viewResult));
var viewEngine = viewResult.ViewEngine ?? ViewEngine;
var viewName = viewResult.ViewName ?? GetActionName(actionContext);
var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true);
var originalResult = result;
if (!result.Success)
result = viewEngine.FindView(actionContext, viewName, isMainPage: true);
if (!result.Success)
if (originalResult.SearchedLocations.Any())
if (result.SearchedLocations.Any())
// Return a new ViewEngineResult listing all searched locations.
var locations = new List<string>(originalResult.SearchedLocations);
locations.AddRange(result.SearchedLocations);
result = ViewEngineResult.NotFound(viewName, locations);
else
// GetView() searched locations but FindView() did not. Use first ViewEngineResult.
result = originalResult;
if(!result.Success)
throw new InvalidOperationException(string.Format("Couldn't find view '0'", viewName));
return result;
private const string ActionNameKey = "action";
private static string GetActionName(ActionContext context)
if (context == null)
throw new ArgumentNullException(nameof(context));
if (!context.RouteData.Values.TryGetValue(ActionNameKey, out var routeValue))
return null;
var actionDescriptor = context.ActionDescriptor;
string normalizedValue = null;
if (actionDescriptor.RouteValues.TryGetValue(ActionNameKey, out var value) &&
!string.IsNullOrEmpty(value))
normalizedValue = value;
var stringRouteValue = routeValue?.ToString();
if (string.Equals(normalizedValue, stringRouteValue, StringComparison.OrdinalIgnoreCase))
return normalizedValue;
return stringRouteValue;
然后得出结论,在您的控制器中,假设 razor cshtml 视图模板为/Views/Home/PDFTemplate.cshtml
,您可以使用以下内容。
注意:cshtml
文件在发布时可能需要复制(即使视图已编译)。
var htmlContent = await _ViewToStringRendererService.RenderViewToStringAsync("Home/PDFTemplate", viewModel);
(var contentType, var generatedFile) = await GeneratePDFAsync(htmlContent);
Response.Headers["Content-Disposition"] = $"attachment; filename=\"System.Net.WebUtility.UrlEncode(fileName)\"";
// You may save your file here
using (var fileStream = new FileStream(Path.Combine(folder, fileName), FileMode.Create))
await generatedFile.CopyToAsync(fileStream);
// You may need this for re-use of the stream
generatedFile.Seek(0, SeekOrigin.Begin);
return File(generatedFile.ToArray(), "application/pdf", fileName);
【讨论】:
如何声明_RenderService
?
_RenderService
是下面的ViewToStringRendererService
。它应该在启动期间作为作用域或瞬态注入。
@LiquidCore 请详细说明。除了在某些情况下有用的 ViewToStringRendererService,它是 15 行代码【参考方案4】:
您可以查看DinkToPdf 库。它是 .NET Core 的 wkhtmltopdf 库的包装器。
同步转换器
在多线程应用程序和 Web 服务器中使用此转换器。转换任务保存到阻塞集合并在单个线程上执行。
var converter = new SynchronizedConverter(new PdfTools());
定义要转换的文档
var doc = new HtmlToPdfDocument()
GlobalSettings =
ColorMode = ColorMode.Color,
Orientation = Orientation.Landscape,
PaperSize = PaperKind.A4Plus,
,
Objects =
new ObjectSettings()
PagesCount = true,
HtmlContent = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In consectetur mauris eget ultrices iaculis. Ut odio viverra, molestie lectus nec, venenatis turpis.",
WebSettings = DefaultEncoding = "utf-8" ,
HeaderSettings = FontSize = 9, Right = "Page [page] of [toPage]", Line = true, Spacing = 2.812
;
【讨论】:
这对 Kestrel 非常有效,但不适用于 IIS。任何想法为什么? IIS 的问题是 IIS 如何管理应用程序池。要解决该问题,应使用远程工具,但 .NET Core 不支持它们。如果有人对此问题有解决方案,请发表评论。 第一次生成pdf,第二次拒绝生成不知道为什么 @Steve 不,我们没有解决问题。现在去 2.1,因为它发布了,在 1.1 中管理第三方库是一个活生生的地狱。我们所做的是使用可兼容的 itextsharp 在 dotnetframework web api 中创建一个项目,然后调用它 @user1646245 是的,在对代码进行了一些调试之后,我们能够在这个单例问题中准确地发现问题。老实说,我们将它作为单例,但有人决定在每次调用导出到 pdf 例程时重新初始化它。现在它可以正常工作了【参考方案5】:我遇到了同样的问题!我想从 HTML 字符串生成 PDF 文件。然后我遇到了PhantomJs,这是一个用于将 html 文件转换为 pdf 的命令行实用程序。我在 C# 中为 .NET CORE 编写了一个跨平台包装器,它在 Linux 上运行良好!虽然到目前为止它仅适用于 64 位 Linux,因为这是目前 .NET Core 支持的唯一平台。 项目可以找到here
PhantomJs.NetCore.PdfGenerator gen = new PhantomJs.NetCore.PdfGenerator("/path/to/pantomjsfolder");
string outputFilePath = gen.GeneratePdf("<h1>Hello</h1>","/folder/to/write/file/in");
【讨论】:
Mac 开发者 Lenny 怎么样? 当我看到你归档你的 repo 时我很难过。【参考方案6】:从我这里的原始答案Export to pdf using ASP.NET 5复制:
在 .NET Core(没有任何 .NET 框架依赖项)中从 html 生成 pdf 的一种方法是在 .NET Core 应用程序中使用 Node.js。 以下示例展示了如何在干净的 ASP.NET Core Web 应用程序项目(Web API 模板)中实现 HTML 到 PDF 转换器。
安装 NuGet 包Microsoft.AspNetCore.NodeServices
在 Startup.cs 中添加 services.AddNodeServices()
这样的行
public void ConfigureServices(IServiceCollection services)
// ... all your existing configuration is here ...
// Enable Node Services
services.AddNodeServices();
现在安装所需的 Node.js 包:
从命令行将工作目录更改为 .NET Core 项目的根目录并运行这些命令。
npm init
并按照说明创建 package.json 文件
npm install jsreport-core --save
npm install jsreport-jsrender --save
npm install jsreport-phantom-pdf --save
在项目的根目录下创建一个文件pdf.js
,包含
module.exports = function (callback)
var jsreport = require('jsreport-core')();
jsreport.init().then(function ()
return jsreport.render(
template:
content: '<h1>Hello :foo</h1>',
engine: 'jsrender',
recipe: 'phantom-pdf'
,
data:
foo: "world"
).then(function (resp)
callback(/* error */ null, resp.content.toJSON().data);
);
).catch(function (e)
callback(/* error */ e, null);
)
;
查看here 以获取有关jsreport-core
的更多说明。
现在在调用此 Node.js 脚本的 Mvc 控制器中创建一个操作
[HttpGet]
public async Task<IActionResult> MyAction([FromServices] INodeServices nodeServices)
var result = await nodeServices.InvokeAsync<byte[]>("./pdf");
HttpContext.Response.ContentType = "application/pdf";
string filename = @"report.pdf";
HttpContext.Response.Headers.Add("x-filename", filename);
HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "x-filename");
HttpContext.Response.Body.Write(result, 0, result.Length);
return new ContentResult();
当然,您可以使用从 nodeServices 返回的byte[]
做任何您想做的事情,在这个示例中,我只是从控制器操作中输出它,以便可以在浏览器中查看它。
您还可以使用 resp.content.toString('base64')
in pdf.js
并使用 base64 编码字符串在 Node.js 和 .NET Core 之间交换数据
var result = await nodeServices.InvokeAsync<byte[]>("./pdf");
在action中然后解码base64编码的字符串。
替代方案
大多数 pdf 生成器解决方案仍然依赖于 .NET 4.5/4.6 框架。但是,如果您不喜欢使用 Node.js,似乎有一些付费替代方案可用:
NReco.PdfGenerator.LT .NET Core 的 EVO HTML 到 PDF 转换器客户端 Winnovative HTML to PDF Converter Client for .NET Core这些我都没有试过。
我希望我们能很快看到这方面的一些开源进展。
【讨论】:
知道如何使用现有的 Razor 视图(或任何其他 html 页面)作为输入吗? 如果你只是在寻找 wkHtmlToPdf-Wrapper,你可以使用我的:github.com/ststeiger/wkHtmlToPdfSharp 你必须为 .NET Core 稍微修改一下。 @jao 您应该可以使用module.exports = function (callback, html)
和模板集content: html
更改pdf.js,然后在您的操作中执行var result = await nodeServices.InvokeAsync<byte[]>("./pdf", razorRenderedHtmlString);
。但是,除非您提前内联它,否则您可能会在使用 css 时遇到困难。
关于在 ASP.NET 中使用节点服务的 TIL
此实现导致我出现错误“System.InvalidOperationException:标头是只读的,响应已经开始。”以上是关于在 ASP.NET Core 中将 html 导出为 pdf的主要内容,如果未能解决你的问题,请参考以下文章
如何在 ASP.NET Core 中将角色添加到 Windows 身份验证
如何在asp.net mvc中将Webcontrol表导出到excel
在 ASP.NET Core 中将 Razor 视图渲染为字符串
防止 AddRedirectToWwwPermanent() 在 ASP.NET Core 2.1 中将“www”添加到 *.azurewebsites.net 的前面