Convert HTML to PDF in MVC with iTextSharp in MVC Razor

后端 未结 9 1202
情深已故
情深已故 2020-12-02 15:56

I am trying to convert HTML to PDF with iTextSharp in MVC Razor, but everything I have tried has not worked. Does anyone know how to accomplish this?

9条回答
  •  时光取名叫无心
    2020-12-02 16:52

    In case you are using ASP.NET Core and iTextSharp is not that important to you here is my solution using PhantomJS: http://nikolay.it/Blog/2018/03/Generate-PDF-file-from-Razor-view-using-ASP-NET-Core-and-PhantomJS/37

    Get HTML string from a Razor view

    This step is pretty straight-forward. There is a service called IRazorViewEngine in ASP.NET Core which can be injected and then used to get the view. After providing the view with default ViewDataDictionary and ActionContext we can request the view to be rendered into StringWriter which can be easily converted to string. Here is ready-to-use code for getting a string from given Razor view file:

    public interface IViewRenderService
    {
        Task RenderToStringAsync(string viewName, object model);
    }
    
    public class ViewRenderService : IViewRenderService
    {
        private readonly IRazorViewEngine razorViewEngine;
        private readonly ITempDataProvider tempDataProvider;
        private readonly IServiceProvider serviceProvider;
    
        public ViewRenderService(
            IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            this.razorViewEngine = razorViewEngine;
            this.tempDataProvider = tempDataProvider;
            this.serviceProvider = serviceProvider;
        }
    
        public async Task RenderToStringAsync(string viewName, object model)
        {
            var httpContext = new DefaultHttpContext { RequestServices = this.serviceProvider };
            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
    
            using (var sw = new StringWriter())
            {
                var viewResult = this.razorViewEngine.GetView(null, viewName, false);
    
                if (viewResult.View == null)
                {
                    throw new ArgumentNullException($"{viewName} does not match any available view");
                }
    
                var viewDictionary =
                    new ViewDataDictionary(
                        new EmptyModelMetadataProvider(),
                        new ModelStateDictionary()) { Model = model };
    
                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, this.tempDataProvider),
                    sw,
                    new HtmlHelperOptions());
    
                await viewResult.View.RenderAsync(viewContext);
                return sw.ToString();
            }
        }
    }
    

    One important think here: if you use view compilation (pre-compiling views to YourProject.Web.PrecompiledViews.dll) then it is important to get the view using the GetView method instead of FindView. More information here.

    Generate the PDF file from HTML using PhantomJS

    For this task we are going to use a headless browser which will render the HTML (with all CSS and JS included in it). There are many such tools but I will use PhantomJS (headless WebKit scriptable with a JavaScript API). PhantomJS can save the rendered page to small-sized PDF pretty fast. For the PDF export to work we are going to need a .js file which will use the PhantomJS API to tell the tool that we want to export the file:

    "use strict";
    var page = require('webpage').create(),
        system = require('system'),
        address,
        output;
    
    console.log('Usage: rasterize.js [URL] [filename] [paperformat]');
    address = system.args[1];
    output = system.args[2];
    page.viewportSize = { width: 600, height: 600 };
    page.paperSize = { format: system.args[3], orientation: 'portrait', margin: '0.5cm' };
    
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit(1);
        } else {
            window.setTimeout(function () {
                page.render(output);
                phantom.exit();
            }, 200);
        }
    });
    

    The next thing is to run the phantomjs.exe process and pass the rasterize.js file along with paths for the HTML file and the output file name for the PDF result. This is done in HtmlToPdfConverter.cs:

    public interface IHtmlToPdfConverter
    {
        byte[] Convert(string htmlCode);
    }
    
    public class HtmlToPdfConverter : IHtmlToPdfConverter
    {
        public byte[] Convert(string htmlCode)
        {
            var inputFileName = "input.html";
            var outputFileName = "output.pdf";
            File.WriteAllText(inputFileName, htmlCode);
            var startInfo = new ProcessStartInfo("phantomjs.exe")
                                {
                                    WorkingDirectory = Environment.CurrentDirectory,
                                    Arguments = string.Format(
                                        "rasterize.js \"{0}\" {1} \"A4\"",
                                        inputFileName,
                                        outputFileName),
                                    UseShellExecute = true,
                                };
    
            var process = new Process { StartInfo = startInfo };
            process.Start();
    
            process.WaitForExit();
    
            var bytes = File.ReadAllBytes(outputFileName);
    
            File.Delete(inputFileName);
            File.Delete(outputFileName);
    
            return bytes;
        }
    }
    

    If you are going to deploy your application in Azure it is important to have UseShellExecute set to true.

    Use the code together

    Since we now have implemented both IViewRenderService and IHtmlToPdfConverter we can start using them by first register them in the Startup.cs file where your ConfigureServices method should be located (services.AddScoped() and services.AddScoped()). Now lets see the code wrapped up together:

    private readonly IViewRenderService viewRenderService;
    private readonly IHtmlToPdfConverter htmlToPdfConverter;
    
    public DashboardController(
        IViewRenderService viewRenderService,
        IHtmlToPdfConverter htmlToPdfConverter)
    {
        this.viewRenderService = viewRenderService;
        this.htmlToPdfConverter = htmlToPdfConverter;
    }
    
    [HttpGet]
    public async Task GetPdf(SomeInputModel input)
    {
        var model = this.GetViewModel(input);
        var htmlData = await this.viewRenderService.RenderToStringAsync("~/Views/Dashboard/GetPdf.cshtml", model);
        var fileContents = this.htmlToPdfConverter.Convert(htmlData);
        return this.File(fileContents, "application/pdf");
    }
    

提交回复
热议问题