64

I want to export a piece of html to a pdf file but i do not any compatible nuget package.

When I try to install anyone: "X not compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0)."

Does anyone know any way to export to a pdf using asp.net core??

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
Carlos
  • 641
  • 1
  • 5
  • 5
  • Possible duplicate of [Export to pdf using ASP.NET 5](http://stackoverflow.com/questions/36983300/export-to-pdf-using-asp-net-5) – Anderson Matos Sep 08 '16 at 17:41
  • 24
    net core is different to asp.net 5, another framework, another libraries – Carlos Sep 12 '16 at 16:26
  • 3
    AspNet Core 1.0 is the new name. AspNet 5 (formely AspNet vNext) was the original name but since it was a whole new product, MS decided to fully change it's name to AspNet Core. – Anderson Matos Sep 12 '16 at 18:32
  • Take a look at that answer. It's currently working in a PoC environment on IIS with core stack only and a subset of node as explained on the answer itself. Framework is set to exactly what you are using ;) – Anderson Matos Sep 12 '16 at 18:36
  • So not listed here previously - but the solution that worked great for me is the NuGet package of https://github.com/aaxelm/Rotativa.NetCore – Lance Larsen - Microsoft MVP Sep 16 '17 at 23:56
  • This company offers a free comunity license and this forum post let's guess it will be available later this month: https://www.syncfusion.com/forums/127732/html-to-pdf-converter-asp-net-core – Christian Gollhardt Nov 11 '17 at 04:39

7 Answers7

40

You can use jsreport .net sdk if you are in .net core 2.0 also without more complex node services. This includes among other features filters to convert your existing razor views into pdf. From the docs:

1. Install nugets jsreport.Binary, jsreport.Local and jsreport.AspNetCore

2. In you Startup.cs configure it as the following

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();              
    services.AddJsReport(new LocalReporting()
        .UseBinary(JsReportBinary.GetBinary())
        .AsUtility()
        .Create());
}

3. Then you need to add MiddlewareFilter attribute to the particular action and specify which conversion you want to use. In this case html to pdf conversion.

[MiddlewareFilter(typeof(JsReportPipeline))]
public IActionResult Invoice()
{
    HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf);
    return View();
}

You can reach bunch of other options for headers, footers or page layout on JsReportFeature(). Note that the same way you can also produce excel files from html. See more information in the documentation.

PS: I'm the author of jsreport.

Jan Blaha
  • 3,069
  • 1
  • 22
  • 35
  • 1
    how to save the file after that? – MoFarid Apr 01 '18 at 15:59
  • 1
    how are the css and images files in razor handled? ajax calls? is the pdf output the same as what can be seen in browser? – Alvin Jul 05 '18 at 08:20
  • Wow - I have never come across this before. I had a look at the site and it is a very comprehensive application you got going there. Very impressed in how easily it integrates with ASP.NET - Before I used all sorts and had many nightmares. I owe you allot of beer for saving me time and making PDF generation easier. – Piotr Kula Jul 18 '18 at 14:34
  • how to include css? or is there also an option to only select a specific part of html to render to pdf? – rjps12 Apr 25 '19 at 03:30
  • 3
    had a lot of trouble with dependencies and nuget, it was horrible but I managed to configure it. Also, stackoveflow police keeps removing these comments. Maybe they are in love with the author of this – Liquid Core Sep 19 '19 at 09:04
  • @Jan Blaha thanks, it works with 3.1 too. – Teo Bebekis Jan 29 '21 at 16:29
  • Please do checkout my detailed article based on jsreport. https://code.soundaranbu.com/convert-html-to-pdf-in-asp-net-core – Soundar Anbu May 31 '21 at 14:51
23

Copied from my original answer here Export to pdf using ASP.NET 5:

One way to generate pdf from html in .NET Core (without any .NET framework dependencies) is using Node.js from within the .NET Core application. The following example shows how to implement an HTML to PDF converter in a clean ASP.NET Core Web Application project (Web API template).

Install the NuGet package Microsoft.AspNetCore.NodeServices

In Startup.cs add the line services.AddNodeServices() like this

public void ConfigureServices(IServiceCollection services)
{
    // ... all your existing configuration is here ...

    // Enable Node Services
    services.AddNodeServices();
}

Now install the required Node.js packages:

From the command line change working directory to the root of the .NET Core project and run these commands.

npm init

and follow the instructions to create the package.json file

npm install jsreport-core --save
npm install jsreport-jsrender --save
npm install jsreport-phantom-pdf --save

Create a file pdf.js in the root of the project containing

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);
    })
};

Have a look here for more explanation on jsreport-core.

Now create an action in an Mvc controller that calls this Node.js script

[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();
}

Off course you can do whatever you want with the byte[] returned from nodeServices, in this example I'm just outputting it from a controller action so it can be viewed in the browser.

You could also exchange the data between Node.js and .NET Core by a base64 encoded string using resp.content.toString('base64') in pdf.js and use var result = await nodeServices.InvokeAsync<byte[]>("./pdf"); in the action and then decode the base64 encoded string.


Alternatives

Most pdf generator solutions still depend on .NET 4.5/4.6 framework. But there seems to be some paid alternatives available if you don't like to use Node.js:

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

I haven't tried any of these though.

I hope we will soon see some open source progress in this area.

Community
  • 1
  • 1
Sjolund
  • 519
  • 5
  • 10
  • 3
    Any idea how to use an existing Razor view (or any other html page) as input? – jao Apr 08 '17 at 20:30
  • If you're just looking for a wkHtmlToPdf-Wrapper, you can use mine: https://github.com/ststeiger/wkHtmlToPdfSharp you'll have to modify it a bit for .NET Core. – Stefan Steiger Apr 18 '17 at 13:33
  • 4
    @jao You should be able to change pdf.js with `module.exports = function (callback, html) {` and in the template set `content: html`, then in your action do `var result = await nodeServices.InvokeAsync("./pdf", razorRenderedHtmlString);`. However you will probably struggle with css unless you inline it in advance. – Sjolund Apr 19 '17 at 07:52
  • @sjolund thank you. I'm trying to customize options like so: `var jsreport = require('jsreport-core')({ phantom: { margin: { top: "3cm", right: "0px", bottom: "3cm", left: "0px" }, format: "A3", } });` but that doesn't work. Any idea why? How do you configure your phantom options? – jao Apr 20 '17 at 15:29
  • found it [here](http://stackoverflow.com/questions/42472649/phantom-pdf-on-azure-in-asp-net-core?rq=1): I need to add the phantom options in the template object. – jao Apr 20 '17 at 16:39
  • 2
    TIL about using nodeservices in ASP.NET – Piotr Kula Jul 18 '18 at 14:05
  • 1
    This implementation caused me an error "System.InvalidOperationException: Headers are read-only, response has already started." – Angel Romero Aug 06 '18 at 13:42
  • @Angel Romero This answer helped me get around that problem: https://stackoverflow.com/a/37395430/1551685 – Steve Smith Aug 07 '19 at 08:49
7

You can check DinkToPdf library. It is a wrapper around wkhtmltopdf library for .NET Core.

Synchronized converter

Use this converter in multi threaded applications and web servers. Conversion tasks are saved to blocking collection and executed on a single thread.

var converter = new SynchronizedConverter(new PdfTools());

Define document to convert

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 }
        }
    }
};
user1646245
  • 309
  • 4
  • 8
  • 2
    This works really well with Kestrel, but not on IIS. Any ideas why? – Johan Herstad Jun 11 '17 at 10:08
  • Problem with IIS is how IIS manage app pools. To resolve that issue Remoting tools should be used but they are not supported in .NET Core. If anyone have solution for this problem please comment. – user1646245 Jun 11 '17 at 17:39
  • Have a look at [https://github.com/TheSalarKhan/PhantomJs.NetCore] it works on IIS too, and unlike wkhtmltopdf it works on linux, Mac and windows. – Salar Khan Jun 12 '17 at 00:12
  • wkhtmltopdf works on Linux, Mac and Windows you just need to get right version of the library. – user1646245 Jun 15 '17 at 12:44
  • 3
    it generated pdf the first time and then refused to generate the second time i don't know why – maztt Sep 20 '17 at 11:43
  • Can you provide sample or tell more about how you use it? Do you use it web server? Do you use basic or synchronized converter? – user1646245 Sep 21 '17 at 12:26
  • @maztt did you solve that problem? We are experiencing the same issue in a netcore 1.1 app. It seems to be a threading issue but I can't find anything wrong in their SynchronizerConverter – Steve May 23 '18 at 12:43
  • 1
    @Steve no we didn't solved the problem . go for 2.1 now as it released, managing third party libraries in 1.1 is a living hell. What we did is to create a project in dotnetframework web api using compaitable itextsharp and then called it – maztt Jun 23 '18 at 14:50
  • @Steve, SynchronizedConverter must be declared as singleton. – user1646245 Jun 26 '18 at 11:41
  • 2
    @user1646245 yes after a bit of debugging on the code we were able to discover the problem exactly in this singleton issue. To be honest we have it as singleton but someone decided to reinitilize it everytime the export to pdf routine was called. Now it works correctly – Steve Jun 26 '18 at 12:40
4

I was having the same issue! I wanted to generate PDF files from HTML strings. I then came across PhantomJs which is a command line utility for converting html files to pdf. I wrote a cross-platform wrapper over it in C# for .NET CORE and its working great on Linux! Though as of now its only for 64-bit Linux, because that is the only platform .NET Core Supports currently. The project can be found here

PhantomJs.NetCore.PdfGenerator gen = new PhantomJs.NetCore.PdfGenerator("/path/to/pantomjsfolder");
string outputFilePath = gen.GeneratePdf("<h1>Hello</h1>","/folder/to/write/file/in");
Salar Khan
  • 457
  • 3
  • 13
2

This is a solution working for ASP.NET Core 2.0, which allows either to generate dynamic PDF files from cshtml, directly send them to users and/or save them before sending.

To complement Jan Blaha answer there, for more flexibility, you may want to use the following code:

/// 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);
}

Using a class to render cshtml files as string, you may use the following service (which can be injected as a scoped 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;
    }

}

Then to conclude, in your controller, supposing the razor cshtml view template to be /Views/Home/PDFTemplate.cshtml you may use the following.

Note: The cshtml file may need to be copied when published (even if views are compiled).

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);
Jean
  • 4,911
  • 3
  • 29
  • 50
  • how to declare the `_RenderService`? – rjps12 Apr 25 '19 at 03:51
  • `_RenderService` is the `ViewToStringRendererService` just below. It should be injected as scoped or Transient during startup. – Jean Apr 25 '19 at 12:34
  • @LiquidCore please elaborate. Besides the ViewToStringRendererService, which is useful in several cases, it is a 15 lines piece of code – Jean Sep 19 '19 at 18:53
1

On the server-side, you can output pdf of a html and use library that generate PDF from HTML string .NET Core after you got pdf, you need to pass it to the library see this link to convert HTML to PDF in .NET.

install nuget package : 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");
0

For exporting html to pdf you can use itextsharp library and even you can place html inside a partial view and export that view as pdf. Recently , i have tried that in a project where i needed to export pdf and i got refer How to export view as pdf in Asp.Net core . So you can give this a try.

Shubham
  • 443
  • 2
  • 10