在 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&lt;byte[]&gt;("./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&lt;byte[]&gt;("./pdf", razorRenderedHtmlString);。但是,除非您提前内联它,否则您可能会在使用 css 时遇到困难。 关于在 ASP.NET 中使用节点服务的 TIL 此实现导致我出现错误“System.InvalidOperationException:标头是只读的,响应已经开始。”

以上是关于在 ASP.NET Core 中将 html 导出为 pdf的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.net Core 中将对象转换为 Json

如何在 ASP.NET Core 中将角色添加到 Windows 身份验证

如何在asp.net mvc中将Webcontrol表导出到excel

在 ASP.NET Core 中将 Razor 视图渲染为字符串

防止 AddRedirectToWwwPermanent() 在 ASP.NET Core 2.1 中将“www”添加到 *.azurewebsites.net 的前面

在 Asp.Net Core 2 中将 SignInManager 和 AspNetUserManager 注入中间件