在各种业务环境中,将各种文档合并为一个PDF是客户最常问的问题之一。例如,假设您的组织有多个应用程序以XPS和PDF生成特定的文档,使用扫描的图像,并且您的用户希望将其中一些文档合并为一个PDF。

本文演示了如何使用ASP.NET Core框架将多个文档合并到一个PDF中。Aspose.PDF提出了几种使用.NET合并PDF的方法,这些内容在本文中进行了介绍。在本文中,将讨论以下主题:

  • 如何使用ASP.NET Core Web API上传PDF或其他文档;
  • 如何实现简单的Web UI来选择要合并的PDF文件;
  • 如何实现用于合并PDF的简单Web API容器;

在本文中,我们将创建一个简单的ASP.NET Web API应用程序,该应用程序允许我们上载文档,选择2个或更多文件进行合并以及下载结果。

点击下载最新版Aspose.PDF


实施ASP.NET Core Web App以将各种文档合并为PDF

步骤1:创建一个ASP.NET Core Web应用程序

我们将为此应用程序使用Web应用程序(模型-视图-控制器)模板。

创建基本应用程序后,我们将需要执行一些其他操作。

  • 为.NET库添加Aspose.PDF作为依赖项(通过Nuget软件包管理器);
  • 添加resumable.js库;
  • 将临时文件和文档的wwwroot文件夹添加到该文件夹(例如files和temp);
  • 在appsettings.json中创建相应的属性
    "Folders": {"Files": "files","Temporary" :  "temp"
    } 

步骤2:实施Web API控制器以管理服务器上的文件

我们的控制器应执行以下操作:

  • 返回具有某些扩展名的文件列表(在本示例中,将仅显示.pdf,.jpg和.oxps文件);
  • 允许按文件名下载文件;
  • 允许通过文件名删除服务器上的文件;
    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来加载文件,因此将与加载文件相关的代码移至单独的控制器是有意义的。

步骤3:实现Web API控制器以使用Resumable.JS上传文件

Resumable.JS库的主要功能是它允许您分块加载文件。因此,我们需要实现一些方法来处理此过程:

  • HTTP GET请求的方法,该方法应检查服务器上是否存在块;
  • HTTP POST请求的方法,该方法应该是服务器上的上传块;
  • 其他辅助方法(用于HTTP OPTIONS请求,合并块等)
    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 } }

该库将标识符用于内部目的。它可以以不同的方式生成。在示例应用程序中,我们使用了一个单独的控制器。

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:为合并的应用程序实现Web UI

现在,我们可以开始实现Web界面了。在示例应用程序中,我们没有使用Angular,React Vue或其他框架,但是我们实现了基于Bootstrap和JQuery的单页应用程序。应用程序页面可以分为两个部分:

  • 服务器上的文件部分将使我们可以查看服务器上的文件,下载或删除它们。此外,用户可以通过单击文件名来选择要合并的文档。要获取合并的文档,用户应单击“合并”按钮,合并的文档将显示在文件列表中。
  • “上载文件”部分仅用于上载文件。

由于该网页的代码量很大,因此在此不再显示,我们将完全局限于描述该算法的两个想法。

  • 合并序列中的文件位置存储在与其对应的单元格的data-order属性中。因此,要将文件合并为一个PDF,我们应该获取所有数据顺序,对它们进行排序并发送文件名序列;
  • 要选择/取消选择要合并的文件,请单击文件名。选定的文件标有徽章;

以下代码段演示了这两种操作的处理程序:

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提供了几种合并文档的方法。您可以在上一篇文章中学习其中的一些内容,但是现在我们将重点介绍一下,并讨论影响PDF中任何文档的可能性。

实际上,如果文档为PDF格式,那么我们必须执行两个操作,然后合并;如果文档不是PDF,则首先进行转换然后合并。

步骤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 conversionthrow new NotImplementedException();}}
}

如您所见,我们的控制器调用HTTP-Post方法来合并文档。现在我们实现此方法。我们合并的想法是将所有页面从一个文档添加到另一个文档。这很简单,因为我们知道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:实现用于将图像转换为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环境中的正常运行。该应用程序的目的是展示使用.NET Core的Aspose.PDF合并任何文档并将其保存为PDF格式的可能性,并且可能需要对其进行改进。例如,此程序不考虑保存具有相同名称的文件。该问题的可能解决方案是使用具有生成名称的文件夹上载每个文档或使用数据库存储文件。


如果您有任何疑问或需求,请随时加入Aspose技术交流群(642018183),我们很高兴为您提供查询和咨询。

在ASP.NET Core中如何将各种文档合并为PDF?Aspose快速搞定!相关推荐

  1. 教你实践ASP.NET Core Authorization(免看文档教程)

    准备 创建一个名为AuthorizationForoNetCore的(web)解决方案,选择Empty模板 添加相关nuget包引用Microsoft.AspNetCore.Mvc(选择最新版本) 编 ...

  2. ASP.NET CORE 1.0 MVC API 文档用 SWASHBUCKLE SWAGGER实现

    from:https://damienbod.com/2015/12/13/asp-net-5-mvc-6-api-documentation-using-swagger/ 代码生成工具: https ...

  3. 把不同的pdf文档合并在一个pdf文件中,一次批量打印

    Python中的库PyPDF2以实现任意选定页面的合并.通过文件夹遍历,可以将不同文件夹下的PDF按选定的页面合并在一起,方便快捷.比如,可以指定把除了目录页的其他页面合并在一起批量打印. %%tim ...

  4. 如何使用C#在ASP.NET Core中轻松实现QRCoder

    by Yogi 由瑜伽士 如何使用C#在ASP.NET Core中轻松实现QRCoder (How to easily implement QRCoder in ASP.NET Core using ...

  5. asp.net core中IHttpContextAccessor和HttpContextAccessor的妙用

    分享一篇文章,关于asp.net core中httpcontext的拓展. 现在,试图围绕HttpContext.Current构建你的代码真的不是一个好主意,但是我想如果你正在迁移一个企业类型的应用 ...

  6. 在asp.net core中使用托管服务实现后台任务

    在业务场景中经常需要后台服务不停的或定时处理一些任务,这些任务是不需要及时响应请求的. 在 asp.net中会使用windows服务来处理. 在 asp.net core中,可以使用托管服务来实现,托 ...

  7. ASP.NET Core中显示自定义错误页面-增强版

    之前的博文 ASP.NET Core中显示自定义错误页面 中的方法是在项目中硬编码实现的,当有多个项目时,就会造成不同项目之间的重复代码,不可取. 在这篇博文中改用middleware实现,并且放在独 ...

  8. 探索ASP.NET Core中的IStartupFilter

    原文:Exploring IStartupFilter in ASP.NET Core 作者:Andrew Lock 译者:Lamond Lu 在本篇博客中,我将介绍一下IStartupFilter, ...

  9. 如何简单的在 ASP.NET Core 中集成 JWT 认证?

    前情提要:ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统 文章超长预警(1万字以上),不想看全部实现过程的同学可以直接跳转到末尾查看成果或者一键安装相关的 nuget 包 自上一 ...

最新文章

  1. SQL Server存储过程输入参数使用表值
  2. python关键字是什么颜色,python – Matplotlib:如果使用关键字sym,则使用Boxplot异常值颜色更改...
  3. amcharts 网页绘图插件
  4. [Python图像处理] 二十四.图像特效处理之毛玻璃、浮雕和油漆特效
  5. bigdecimal 小于等于0_半场0-0比分的比赛,你需要注意这些
  6. 使用SDK进行二次开发流程简述
  7. 判断用户输入的这个数是不是质数
  8. linux 复用寄存器,I/O多路复用一些概念
  9. oracle的sid
  10. apiCloud实现加载更多效果,基本完美~
  11. 10分钟学会数据库压力测试
  12. 网管学习日记-ospf多区域
  13. 【2019上海网络赛:K】Peekaboo(勾股数知c求a和b--数论)
  14. endnotex9安装后使用方法_endnotex9使用教程
  15. 他因“上帝粒子”获诺奖,却火速搬到乡下:它毁了我的生活
  16. linux如何卸载光驱显示busy,执行umount 的时候却提示:device is busy 的处理方法 卸载挂载的盘提示如下...
  17. 5.3.4—二叉查找树—Convert Sorted Array to Binary Sear Tree
  18. LNK2026 模块*对于 SAFESEH 映像是不安全的“原因以及解决方法
  19. JS 中国标准时间转换yy-mm-dd HH:mm:ss
  20. android 签名文件与sha1获取

热门文章

  1. 【小5聊】layui第三方插件-xm-select参数
  2. 做外贸用哪个企业邮箱比较好?大容量外贸企业邮箱哪家好?
  3. 人脸识别、人证比对核验数据特点
  4. T 基础 高数 上:函数
  5. android wifi 移植记录
  6. Oracle数据库字符集问题解决方案大全
  7. 最新最全论文合集——多模态情感分析
  8. Java培训机构哪个好?该怎么选择
  9. 2022年疫情下的卡塔尔世界杯,你看了么,盘点一下爆冷的赛事
  10. 情人节快乐 2008 追赶情人的情人节