在ASP.NET Core中将HTML导出为PDF

65

我想将一段html导出为pdf文件,但我找不到任何兼容的NuGet包。

当我尝试安装任何一个时:"X与netcoreapp1.0(.NETCoreApp,Version=v1.0)不兼容。"

有人知道使用asp.net core导出pdf的方法吗?


可能是使用ASP.NET 5导出PDF的重复问题 - Anderson Matos
24
.Net Core与ASP.Net 5不同,它是另一个框架,有另外的库。 - Carlos
3
AspNet Core 1.0是一个新的名字。AspNet 5(之前称为AspNet vNext)是原来的名称,但由于它是一个全新的产品,微软决定完全更改它的名字为AspNet Core。 - Anderson Matos
看一下那个答案。目前它正在一个仅使用核心堆栈和节点子集在IIS上的PoC环境中运行,正如答案本身所解释的那样。框架设置与您使用的完全相同 ;) - Anderson Matos
之前没有列出来,但对我非常有效的解决方案是使用 https://github.com/aaxelm/Rotativa.NetCore 的 NuGet 包。 - Lance Larsen - Microsoft MVP
该公司提供免费的社区许可证,这篇论坛帖子让我们猜测它将在本月晚些时候发布:https://www.syncfusion.com/forums/127732/html-to-pdf-converter-asp-net-core - Christian Gollhardt
8个回答

42

如果你在 .net core 2.0 中,也不需要更复杂的 node 服务,可以使用 jsreport .net sdk。其中包括将现有的 razor 视图转换为 pdf 的筛选器等功能。从文档中了解更多信息:

1. 安装Nuget包jsreport.Binaryjsreport.Localjsreport.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文件。有关更多信息,请参阅文档

PS:我是jsreport的作者。


1
如何保存文件之后? - MoFarid
1
CSS和图像文件在Razor中是如何处理的?Ajax调用呢?PDF输出与浏览器中看到的是否相同? - Alvin
3
我在依赖和NuGet方面遇到了很多麻烦,真是太可怕了,但我最终还是成功地进行了配置。此外,StackOverflow的管理员一直在删除这些评论。也许他们对这位作者有好感。 - Liquid Core
@Jan Blaha 谢谢,它也适用于3.1版本。 - Teo Bebekis
请查看我的详细文章,基于jsreport。https://code.soundaranbu.com/convert-html-to-pdf-in-asp-net-core - Soundar Anbu
显示剩余2条评论

24

以下内容是我原来的回答,转载自这里Export to pdf using ASP.NET 5:

.NET Core中一种生成PDF文档的方法(不依赖于任何.NET框架)是在.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);
    })
};

点击这里以获取有关jsreport-core的更多解释。

现在在Mvc控制器中创建一个调用此Node.js脚本的操作。

[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')将数据在Node.js和.NET Core之间交换为base64编码字符串,使用pdf.js,并在操作中使用var result = await nodeServices.InvokeAsync< byte [] >(“./ pdf”);,然后解码base64编码的字符串。


替代方案

大多数pdf生成器解决方案仍依赖于.NET 4.5/4.6框架。但是,如果您不想使用Node.js,则似乎有一些付费替代方案可用:

  • NReco.PdfGenerator.LT
  • EVO HTML to PDF Converter Client for .NET Core
  • Winnovative HTML to PDF Converter Client for .NET Core

虽然我没有尝试过任何一个。

我希望我们能很快在这个领域看到一些开源进展。


3
如何使用现有的Razor视图(或任何其他HTML页面)作为输入? - jao
如果您只是在寻找一个wkHtmlToPdf-Wrapper,您可以使用我的:https://github.com/ststeiger/wkHtmlToPdfSharp 您需要稍微修改一下它,以适用于.NET Core。 - Stefan Steiger
4
您可以通过将pdf.js更改为 module.exports = function (callback, html) { ,并在模板中设置 content: html,然后在您的操作中执行 var result = await nodeServices.InvokeAsync<byte[]>("./pdf", razorRenderedHtmlString);。但是,如果您未提前内联CSS,可能会遇到困难。 - Sjolund
2
今天我了解到在ASP.NET中使用nodeservices。 - Piotr Kula
1
这个实现导致了一个错误 "System.InvalidOperationException: Headers are read-only, response has already started." - Angel Romero
显示剩余3条评论

8
您可以查看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 }
        }
    }
};

2
这在Kestrel上运行得非常好,但在IIS上却不行。有什么想法为什么会这样? - Johan Herstad
3
第一次生成了PDF,第二次却无法生成,不知道原因。 - maztt
你能提供示例或更多关于如何使用它的信息吗?你在使用Web服务器吗?你使用基本还是同步转换器? - user1646245
1
@Steve,我们没有解决问题。现在可以去用2.1版本了,因为在1.1版本中管理第三方库非常困难。我们做的是使用兼容的iTextSharp创建dotnetframework web api项目,然后进行调用。 - maztt
2
是的,经过对代码的一些调试,我们能够发现问题确切地在于这个单例模式上。说实话,我们将其作为单例模式,但有人决定每次调用导出为PDF例程时都重新初始化它。现在它可以正常工作了。 - Steve
显示剩余5条评论

4
我曾经遇到了同样的问题!我想从HTML字符串生成PDF文件。后来我发现了一个命令行实用程序PhantomJs,它可以将html文件转换为pdf。我在C#中为.NET CORE编写了跨平台的包装器,并且在Linux上运行得非常好!不过目前只支持64位Linux,因为这是目前.NET Core支持的唯一平台。 该项目可在此处找到。
PhantomJs.NetCore.PdfGenerator gen = new PhantomJs.NetCore.PdfGenerator("/path/to/pantomjsfolder");
string outputFilePath = gen.GeneratePdf("<h1>Hello</h1>","/folder/to/write/file/in");

Lenny,Mac开发者怎么样了? - Lutaaya Huzaifah Idris
当我看到你归档了你的代码库时,我感到很难过。 - Lutaaya Huzaifah Idris

3
以下几点可能会有所帮助。
  • 在服务器端完成

  • 如果HTML在服务器上,则可以尝试使用以下软件包。

  • 软件包Puppeteer Sharp,请查看教程

  • 软件包Playwright Dotnet,请查看教程

  • 在客户端完成(如果跨平台方案不起作用,这也会有所帮助)

  • JsPdf,请查看教程

  • Puppeteer,请查看教程

  • 最后一种选择是使用浏览器打印PDF,请查看教程

  • 其他要点

  • 不要随意选择互联网上的软件包,因为可能存在安全问题、隐私等问题,而是检查是否有像Microsoft、Google等大公司提供的免费开源软件包,一旦选择了软件包,如有需要,请与安全团队进行验证,或者检查评论和下载量。还要检查其许可页面,看看是免费还是付费。

  • 尝试一下gotenbergc# 示例这个这个

  • 如果Linux容器有问题,请在容器外部创建一个API来将HTML转换为PDF,然后尝试在容器应用程序中调用您的新API,或者尝试gotenberg,它们有容器和SDK版本。

  • 还根据需求和性能权衡,决定在服务器端还是客户端完成。


2
这是一个适用于ASP.NET Core 2.0的解决方案,可以从cshtml生成动态PDF文件,直接发送给用户和/或在发送之前保存。为了增加灵活性,可以使用以下代码来补充Jan Blaha的答案
/// 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 文件渲染为字符串,您可以使用以下服务(可以作为作用域服务注入):这里
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 - rjps12
_RenderService 是下面的 ViewToStringRendererService。在启动时,它应该被注入为作用域或瞬态。 - Jean
@LiquidCore 请详细说明。除了 ViewToStringRendererService,这是一个有用的代码片段,并且只有15行。 - Jean

1
在服务器端,您可以输出HTML的PDF,并使用生成PDF的库。在.NET Core中,获取PDF后,需要将其传递给库以将HTML转换为PDF。请参阅此链接。
安装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");

1
注意:此答案不适用于超过5页的文件。 - mamashare
这只能在 Windows 平台上运行。 - umeshkumar sohaliya

0

如果要将HTML导出为PDF,您可以使用iTextSharp库,甚至可以将HTML放在部分视图中,并将该视图导出为PDF。最近,我在一个需要导出PDF的项目中尝试了这种方法,我参考了如何在Asp.Net Core中将视图导出为PDF。所以您可以尝试一下。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接