翻譯|使用教程|編輯:李顯亮|2020-11-03 09:59:45.900|閱讀 413 次
概述:在各種業(yè)務(wù)環(huán)境中,將各種文檔合并為一個PDF是客戶最常問的問題之一。本文演示了如何使用ASP.NET Core框架將多個文檔合并到一個PDF中。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
在各種業(yè)務(wù)環(huán)境中,將各種文檔合并為一個PDF是客戶最常問的問題之一。例如,假設(shè)您的組織有多個應(yīng)用程序以XPS和PDF生成特定的文檔,使用掃描的圖像,并且您的用戶希望將其中一些文檔合并為一個PDF。
本文演示了如何使用ASP.NET Core框架將多個文檔合并到一個PDF中。Aspose.PDF提出了幾種使用.NET合并PDF的方法,這些內(nèi)容在本文中進行了介紹。在本文中,將討論以下主題:
在本文中,我們將創(chuàng)建一個簡單的ASP.NET Web API應(yīng)用程序,該應(yīng)用程序允許我們上載文檔,選擇2個或更多文件進行合并以及下載結(jié)果。
(安裝包僅提供部分功能,并設(shè)置限制,如需試用完整功能請。)
實施ASP.NET Core Web App以將各種文檔合并為PDF
步驟1:創(chuàng)建一個ASP.NET Core Web應(yīng)用程序
我們將為此應(yīng)用程序使用Web應(yīng)用程序(模型-視圖-控制器)模板。
創(chuàng)建基本應(yīng)用程序后,我們將需要執(zhí)行一些其他操作。
"Folders": { "Files": "files", "Temporary" : "temp" }
步驟2:實施Web API控制器以管理服務(wù)器上的文件
我們的控制器應(yīng)執(zhí)行以下操作:
using Aspose.Demo.Pdf.Merger.Models; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.Configuration; namespace Aspose.Demo.Pdf.Merger.Controllers { [Route("api/[controller]")] [ApiController] public class FilesController : ControllerBase { private readonly Dictionary<string, string> _contentType; private readonly ILogger<FilesController> _logger; private readonly string _storageRootFolder; public FilesController(ILogger<FilesController> logger, IWebHostEnvironment env, IConfiguration configuration) { _logger = logger; _storageRootFolder = Path.Combine(env.WebRootPath, configuration["Folders:Files"]); _contentType = new Dictionary<string, string> { { ".txt", "text/plain"}, { ".pdf", "application/pdf"}, { ".doc", "application/vnd.ms-word"}, { ".docx", "application/vnd.ms-word"}, { ".xls", "application/vnd.ms-excel"}, { ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, { ".png", "image/png"}, { ".jpg", "image/jpeg"}, { ".jpeg", "image/jpeg"}, { ".gif", "image/gif"}, { ".csv", "text/csv"} }; } // GET: /api/files [HttpGet] public IEnumerable<FileViewModel> GetFiles() { _logger.LogInformation($"Get files from {_storageRootFolder}"); var files = new DirectoryInfo(_storageRootFolder).EnumerateFiles("*.pdf").ToList(); files.AddRange(new DirectoryInfo(_storageRootFolder).EnumerateFiles("*.jpg")); files.AddRange(new DirectoryInfo(_storageRootFolder).EnumerateFiles("*.oxps")); //TODO: add other file types below return files.Select(f => new FileViewModel { Name = f.Name, Size = f.Length }); } [HttpGet("{id}")] public IActionResult OnGetFile(string id) { _logger.LogInformation($"Get file {id}"); var fileName = Path.Combine(_storageRootFolder, id); return File(System.IO.File.OpenRead(fileName), _contentType[Path.GetExtension(fileName)]); } [HttpDelete("{id}")] public IActionResult OnDeleteFile(string id) { _logger.LogInformation($"Delete file {id}"); var fileName = Path.Combine(_storageRootFolder, id); System.IO.File.Delete(fileName); return Ok(); } } }
然后將使用附加的庫Resumable.JS來加載文件,因此將與加載文件相關(guān)的代碼移至單獨的控制器是有意義的。
步驟3:實現(xiàn)Web API控制器以使用Resumable.JS上傳文件
Resumable.JS庫的主要功能是它允許您分塊加載文件。因此,我們需要實現(xiàn)一些方法來處理此過程:
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.IO; using Microsoft.Extensions.Configuration; namespace Aspose.Demo.Pdf.Merger.Controllers { [Route("api/[controller]")] [ApiController] public class UploadController : ControllerBase { private readonly ILogger_logger; private readonly string _storageRootFolder; private readonly string _filesRootFolder; public UploadController( ILoggerlogger, IConfiguration configuration, IWebHostEnvironment env) { _logger = logger; _storageRootFolder = Path.Combine(env.WebRootPath, configuration["Folders:Temporary"]); _filesRootFolder = Path.Combine(env.WebRootPath, configuration["Folders:Files"]); if (!Directory.Exists(_storageRootFolder)) Directory.CreateDirectory(_storageRootFolder); } [HttpOptions] public object UploadFileOptions() { return Ok(); } [HttpGet] public object Upload(int resumableChunkNumber, string resumableIdentifier) { _logger.LogInformation($"Check if chunck {resumableChunkNumber} from {resumableIdentifier} is here."); return ChunkIsHere(resumableChunkNumber, resumableIdentifier) ? Ok() : StatusCode(418); } [HttpPost] public IActionResult Upload( [FromQuery(Name = "ResumableIdentifier")] string resumableIdentifier, [FromQuery(Name = "ResumableFilename")] string resumableFilename, [FromQuery(Name = "ResumableChunkNumber")] int resumableChunkNumber, [FromQuery(Name = "ResumableTotalChunks")] int resumableTotalChunks, IFormFile file) { _logger.LogInformation(file.FileName); var stream = System.IO.File.Create(GetChunkFileName(resumableChunkNumber, resumableIdentifier)); file.CopyTo(stream); stream.Close(); TryAssembleFile(resumableFilename, resumableIdentifier, resumableTotalChunks); return Ok(); } #region Chunk methods [NonAction] private string GetChunkFileName(int chunkNumber, string identifier) { return Path.Combine(_storageRootFolder, $"{identifier}_{chunkNumber}"); } [NonAction] private string GetFilePath(string identifier) { return Path.Combine(_storageRootFolder, identifier); } [NonAction] private bool ChunkIsHere(int chunkNumber, string identifier) { return System.IO.File.Exists(GetChunkFileName(chunkNumber, identifier)); } [NonAction] private bool AllChunksAreHere(string identifier, int chunks) { for (var chunkNumber = 1; chunkNumber <= chunks; chunkNumber++) if (!ChunkIsHere(chunkNumber, identifier)) return false; return true; } [NonAction] private void DeleteChunks(string identifier, int chunks) { for (var chunkNumber = 1; chunkNumber <= chunks; chunkNumber++) { var chunkFileName = GetChunkFileName(chunkNumber, identifier); System.IO.File.Delete(chunkFileName); } } [NonAction] private string ConsolidateFile(string identifier, int chunks) { var path = GetFilePath(identifier); using var destStream = System.IO.File.Create(path, 15000); for (var chunkNumber = 1; chunkNumber <= chunks; chunkNumber++) { var chunkFileName = GetChunkFileName(chunkNumber, identifier); using var sourceStream = System.IO.File.OpenRead(chunkFileName); sourceStream.CopyTo(destStream); } destStream.Close(); return path; } [NonAction] private void TryAssembleFile(string rfn, string ri, int rtc) { if (AllChunksAreHere(ri, rtc)) { // Create a single file var path = ConsolidateFile(ri, rtc); // Move consolidated file System.IO.File.Move(path, Path.Combine(_filesRootFolder, rfn),true); // Delete chunk files DeleteChunks(ri, rtc); } } #endregion } }
該庫將標識符用于內(nèi)部目的。它可以以不同的方式生成。在示例應(yīng)用程序中,我們使用了一個單獨的控制器。
using Microsoft.AspNetCore.Mvc; using System; using System.Linq; namespace Aspose.Demo.Pdf.Merger.Controllers { [Route("api/[controller]")] [ApiController] public class TokenController : ControllerBase { // GET: api/Token?id=<filename> [HttpGet("{id}")] public string OnGet(string id) { var hash = new System.Security.Cryptography.SHA1Managed() .ComputeHash(System.Text.Encoding.UTF8.GetBytes(id + DateTime.Now.Ticks.ToString())); return string.Concat(hash.Select(b => b.ToString("x2"))); } } }
步驟4:為合并的應(yīng)用程序?qū)崿F(xiàn)Web UI
現(xiàn)在,我們可以開始實現(xiàn)Web界面了。在示例應(yīng)用程序中,我們沒有使用Angular,React Vue或其他框架,但是我們實現(xiàn)了基于Bootstrap和JQuery的單頁應(yīng)用程序。應(yīng)用程序頁面可以分為兩個部分:
由于該網(wǎng)頁的代碼量很大,因此在此不再顯示,我們將完全局限于描述該算法的兩個想法。
以下代碼段演示了這兩種操作的處理程序:
let lastIndex = 0; function selectFileClickHandler() { let order = parseInt($(this).attr('data-order')); if (order > 0) { $(this).attr('data-order', '0'); $(this).find('span').hide('slow'); for (let cell of $("*[data-order]")) { let currentOrder = parseInt(cell.dataset.order); if (currentOrder > order) { cell.dataset.order = currentOrder - 1; cell.firstElementChild.innerHTML = currentOrder - 1; } } lastIndex--; } else { $(this).attr('data-order', ++lastIndex); $(this).find('span').html(lastIndex); $(this).find('span').show('slow'); } $('#btnMerge').prop('disabled', lastIndex<2); } $('#btnMerge').click((e) => { e.preventDefault(); const files = $('*[data-order]').sort(function (a, b) { const contentA = parseInt($(a).data('order')); const contentB = parseInt($(b).data('order')); return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0; }); const data = []; for (let file of files) { const currentOrder = parseInt(file.dataset.order); if (currentOrder > 0) data.push(file.dataset.id); } fetch('api/merge/', { method: 'POST', mode: 'cors', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(data) } ) .then(res => res.json()) .then(res => { console.log(res); refreshFileTable(); }) .catch(err => alert(err)); lastIndex = 0; });
將各種文檔合并為PDF
完成準備階段后,我們可以考慮項目的主要部分。.NET庫的Aspose.PDF提供了幾種合并文檔的方法。您可以在上一篇文章中學習其中的一些內(nèi)容,但是現(xiàn)在我們將重點介紹一下,并討論影響PDF中任何文檔的可能性。
實際上,如果文檔為PDF格式,那么我們必須執(zhí)行兩個操作,然后合并;如果文檔不是PDF,則首先進行轉(zhuǎn)換然后合并。
步驟1:實施Web API控制器以將各種文檔合并為PDF
using Aspose.Pdf; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Drawing; using System.IO; namespace Aspose.Demo.Pdf.Merger.Controllers { [Route("api/[controller]")] [ApiController] public class MergeController : ControllerBase { private readonly ILogger<MergeController> _logger; private readonly string _storageRootFolder; public MergeController(ILogger<MergeController> logger, IWebHostEnvironment env) { _logger = logger; _storageRootFolder = Path.Combine(env.WebRootPath, "files"); //var license = new License(); //license.SetLicense(@"<path to license>"); } // POST: /api/merge [HttpPost] public IActionResult PostMergeFiles(IEnumerable<string> list) { //TODO: Implement Image to PDF conversion throw new NotImplementedException(); } } }
如您所見,我們的控制器調(diào)用HTTP-Post方法來合并文檔。現(xiàn)在我們實現(xiàn)此方法。我們合并的想法是將所有頁面從一個文檔添加到另一個文檔。這很簡單,因為我們知道Document類包含一個Pages集合,而最后一個具有Add方法。
// POST: /api/merge [HttpPost] public IActionResult PostMergeFiles(IEnumerable<string> list) { var document = new Document(); foreach (var item in list) { var filePath = Path.Combine(_storageRootFolder, item); var pdfDocument = Path.GetExtension(item) switch { ".jpg" => ConvertFromImage(filePath), ".jpeg" => ConvertFromImage(filePath), ".png" => ConvertFromImage(filePath), ".oxps" => new Document(filePath, new XpsLoadOptions()), _ => new Document(filePath) }; document.Pages.Add(pdfDocument.Pages); pdfDocument.Dispose(); } var guid = Guid.NewGuid(); document.Save(Path.Combine(_storageRootFolder, $"{guid}.pdf")); _logger.LogInformation($"The merge result saved as: {guid}"); return Ok(new { filename = guid.ToString() }); } private Document ConvertFromImage(string filePath) { var docStream = new MemoryStream(); var doc = new Document(); var page = doc.Pages.Add(); var image = new Aspose.Pdf.Image { ImageStream = new FileStream(filePath, FileMode.Open, FileAccess.Read) }; page.PageInfo.Margin.Bottom = 0; page.PageInfo.Margin.Top = 0; page.PageInfo.Margin.Left = 0; page.PageInfo.Margin.Right = 0; var imageSize = System.Drawing.Image.FromStream(image.ImageStream).Size; page.PageInfo.Width = imageSize.Width; page.PageInfo.Height = imageSize.Height; page.Paragraphs.Add(image); doc.Save(docStream); return doc; } }
步驟2:實現(xiàn)用于將圖像轉(zhuǎn)換為PDF的輔助方法
private Document ConvertFromImage(string filePath) { var docStream = new MemoryStream(); var doc = new Document(); var page = doc.Pages.Add(); var image = new Aspose.Pdf.Image { ImageStream = new FileStream(filePath, FileMode.Open, FileAccess.Read) }; page.PageInfo.Margin.Bottom = 0; page.PageInfo.Margin.Top = 0; page.PageInfo.Margin.Left = 0; page.PageInfo.Margin.Right = 0; var imageSize = System.Drawing.Image.FromStream(image.ImageStream).Size; page.PageInfo.Width = imageSize.Width; page.PageInfo.Height = imageSize.Height; page.Paragraphs.Add(image); doc.Save(docStream); return doc; }
本文示例演示了Aspose.PDF庫在ASP.NET Core環(huán)境中的正常運行。該應(yīng)用程序的目的是展示使用.NET Core的Aspose.PDF合并任何文檔并將其保存為PDF格式的可能性,并且可能需要對其進行改進。例如,此程序不考慮保存具有相同名稱的文件。該問題的可能解決方案是使用具有生成名稱的文件夾上載每個文檔或使用數(shù)據(jù)庫存儲文件。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn