Merge PDF Pages

This demo uses the PDF Document API to merge different documents into a single PDF. Click Merge files and download to merge uploaded files and obtain the resulting PDF.

@using DevExtreme.AspNet.Mvc

@using (Html.BeginForm("PdfMergeAndDownload", "SplitMerge", FormMethod.Post)) {
    <div class="upload-proceed-no-margins">
        <div class="upload-proceed-item">
            @(Html.DevExtreme().FileUploader()
                               .ID("file-uploader")
                               .Name("myFile")
                               .Multiple(true)
                               .ShowFileList(true)
                               .Accept(".pdf")
                               .AllowedFileExtensions(new List<string> { ".pdf" })
                               .MaxFileSize(4194304)
                               .LabelText("Maximum file size 4Mb.")
                               .UploadMode(FileUploadMode.UseButtons)
                               .OnUploaded("enableButton")
                               .UploadUrl(Url.Action("DocumentUpload", "SplitMerge"))
            )
        </div>
        <div class="upload-proceed-item">
            @(Html.DevExtreme().Button()
                               .ID("downloadButton")
                               .Text("Merge files and download")
                               .Disabled(true)
                               .Type(ButtonType.Default)
                               .StylingMode(ButtonStylingMode.Contained)
                               .UseSubmitBehavior(true)
                               .OnClick("reset")
            )
        </div>
    </div>
    <script>
        function enableButton() {
            $("#downloadButton").dxButton("instance").option("disabled", false);
        }
        function reset() {
            $("#file-uploader").dxFileUploader("instance").reset();
            $("#downloadButton").dxButton("instance").option("disabled", true);
        }
    </script>
}
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class SplitMergeController : OfficeDemoController {
        IMergeDemoService service;

        public SplitMergeController(ILogger<SplitMergeController> logger, IWebHostEnvironment hostingEnvironment, IMergeDemoService service)
            : base(logger, hostingEnvironment) {
            this.service = service;
        }
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using DevExpress.Pdf;

namespace AspNetCoreDemos.OfficeFileAPI {
    public class PdfModelBase : IDisposable {
        readonly PdfDocumentProcessor processor;
        MemoryStream stream;
        List<PdfPageModel> items = new List<PdfPageModel>();

        public MemoryStream Stream { get { return stream; } }
        protected PdfDocumentProcessor Processor { get { return processor; } }
        protected PdfDocument Document { get { return processor.Document; } }
        public List<PdfPageModel> Items { get { return items; } }

        public string DocumentUrl { get; set; }
        public int PageIndex { get; set; }
        internal virtual string SessionKey { get; }
        public PdfPreviewModel PreviewModel { get; internal set; }

        public PdfModelBase() {
            processor = new PdfDocumentProcessor();
            PreviewModel = new PdfPreviewModel();
        }

        protected void CreateEmptyDocument() {
            stream = new MemoryStream();
            Processor.CreateEmptyDocument(stream);
            Document.Creator = "PDF Document Processor Demo";
            Document.Producer = "Developer Express Inc., " + AssemblyInfo.Version;
            Document.Author = "DevExpress Inc.";
        }

        public virtual void LoadDocument(byte[] data) {
            using(MemoryStream stream = new MemoryStream(data))
                LoadDocument(stream);
        }

        protected void LoadDocument(Stream stream) {
            processor.LoadDocument(stream, true);
            for(int pageNumber = 1; pageNumber <= processor.Document.Pages.Count; pageNumber++)
                Items.Add(new PdfPageModel(processor, pageNumber));
        }

        public void Dispose() {
            if(processor != null)
                processor.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}
using System.Collections.Generic;
using System.IO;
using DevExpress.Pdf;

namespace AspNetCoreDemos.OfficeFileAPI {
    public static class PdfPageMergingModel {

        public static Stream GetMergedDocument(IEnumerable<string> fileNames) {
            MemoryStream stream = new MemoryStream();
            using(PdfDocumentProcessor processor = new PdfDocumentProcessor()) {
                processor.CreateEmptyDocument(stream);
                foreach(string fileName in fileNames)
                    processor.AppendDocument(fileName);
                processor.Document.Creator = "PDF Document Processor Demo";
                processor.Document.Producer = "Developer Express Inc., " + AssemblyInfo.Version;
                return stream;
            }
        }
    }
}
using System;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class SplitMergeController {
        static object lockObject = new object();

        const string mergeSessionKey = "PdfMergeFiles";
        const string directorySessionKey = "PdfMergeFilesDirectory";

        public IActionResult PdfPageMerging() {
            EnsureSessionKey();
            return View();
        }

        [HttpPost]
        public IActionResult DocumentUpload(IFormFile myFile) {
            var path = EnsureUploadedDirectory();
            var uploadedFilePath = Path.Combine(path, $"{ Guid.NewGuid() }.pdf");

            using(var fileStream = System.IO.File.Create(uploadedFilePath))
                myFile.CopyTo(fileStream);

            return new OkResult();
        }

        string EnsureUploadedDirectory() {
            lock(lockObject) {
                var key = EnsureSessionKey();
                var dirPath = Path.Combine(service.UploadPath, key);
                if(!Directory.Exists(dirPath))
                    Directory.CreateDirectory(dirPath);

                return dirPath;
            }
        }

        string EnsureSessionKey() {
            var key = HttpContext.Session.GetString(directorySessionKey);
            if(string.IsNullOrEmpty(key)) {
                key = Guid.NewGuid().ToString();
                HttpContext.Session.SetString(directorySessionKey, key);
            }
            return key;
        }

        public IActionResult PdfMergeAndDownload() {
            var path = EnsureUploadedDirectory();
            var filePaths = Directory.GetFiles(path, "*.pdf");

            try {
                if(filePaths.Length > 0) {
                    var stream = PdfPageMergingModel.GetMergedDocument(filePaths);
                    return CreateFileStreamResult(stream, "application/pdf", "pdf", "Result");
                }

                return new NoContentResult();
            } catch(Exception ex) {
                Logger.LogError(ex, null);
                return new NoContentResult();
            } finally {
                Directory.Delete(path, true);
            }
        }
    }

    public interface IMergeDemoService {
        string UploadPath { get; }
    }

    public class PdfMergeDemoService : IMergeDemoService {
        static TimeSpan delay = new TimeSpan(0, 5, 0);

        static void ThreadProc(object obj) {
            string uploadsDirectory = (string)obj;
            while(true) {
                try {
                    if(Directory.Exists(uploadsDirectory)) {
                        DateTimeOffset expirationTime = DateTimeOffset.Now.Subtract(delay);
                        foreach(var directory in Directory.GetDirectories(uploadsDirectory)) {
                            if(expirationTime > Directory.GetLastAccessTime(directory)) {
                                try {
                                    Directory.Delete(directory, true);
                                } catch {
                                }
                            }
                        }
                    }
                } catch {
                }
                Thread.Sleep(delay);
            }
        }

        public PdfMergeDemoService(IWebHostEnvironment environment) {
            Environment = environment ?? throw new ArgumentNullException(nameof(environment));
            Thread thread = new Thread(ThreadProc);
            thread.IsBackground = true;
            thread.Start(UploadPath);
        }

        IWebHostEnvironment Environment { get; }

        public string UploadPath => Path.Combine(Environment.WebRootPath, "uploads");
    }
}