วิธีการรวมไฟล์ pdf หลายไฟล์และรูปภาพมาแสดงในครั้งเดียวด้วย iTextSharp (#C)

          ที่มาของบทความนี้ เนื่องด้วยงานที่ผู้เขียนได้รับมอบหมายให้พัฒนาอยู่นั้น มีส่วนหนึ่งที่เป็นการแนบไฟล์เอกสาร/หลักฐานทั้งในรูปแบบไฟล์ PDF และรูปภาพ เข้ามาจากผู้ใช้งาน ซึ่งเอกสารเหล่านี้จะต้องมีส่วนของการแสดงผลให้ทางฝั่งเจ้าหน้าที่ทำการตรวจสอบเอกสาร/หลักฐานดังกล่าวด้วย โดยเดิมทีจะมีการแสดงผลแยกเป็นรายการให้เจ้าหน้าที่เพื่อคลิกดูรายละเอียดทีละรายการ ดังภาพ

          ซึ่งในการทำงานจริงแล้วนั้นพบว่า การตรวจสอบเอกสาร/หลักฐานต่างๆที่ผู้ใช้แนบมาสามารถทำได้ยากและต้องใช้เวลา เนื่องจากเจ้าหน้าที่จะต้องทำการคลิกดูรายละเอียด/ดาวน์โหลดไฟล์คราวละ 1 ไฟล์เพื่อตรวจสอบ ซึ่งไฟล์จะแยกกันอยู่ หากต้องการดาวน์โหลดก็ต้องดาวน์โหลดทีละไฟล์ ดังภาพ

จึงเป็นที่มาของการค้นหาวิธีการแสดงผลไฟล์ทั้งหมดให้เจ้าหน้าที่สามารถตรวจสอบและดูรายละเอียดข้อมูลเอกสารไฟล์แนบได้ในคราวเดียว ซึ่งผู้เขียนก็ได้นำวิธีการดังกล่าวมาแบ่งปันให้กับผู้อ่านที่มีความสนใจและประสบปัญหาคล้ายกันอยู่  ซึ่งจะมีวิธีการดังนี้ค่ะ

1.ดึงข้อมูลไฟล์แนบเอกสารทั้งหมดของผู้ใช้

  DataTable dtFile = new DataTable();

    //เป็นส่วนของการสมมุติการดึงข้อมูลการแนบไฟล์ของผู้ใช้ออกมาในรูปแบบ Datatable
        dtFile.Columns.AddRange(new DataColumn[2] { 
                         new DataColumn("CITIZEN_ID", typeof(string)),
                        new DataColumn("FILE_PATH",typeof(string))});
        dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Document1.pdf");
        dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Document2.pdf");
        dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Document3.pdf");
        dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Image.png");
         

//สร้างตัวแปรในการเก็บค่าชื่อไฟล์ เพื่อใช้ในการส่งเป็นพารามิเตอร์ในการรวมไฟล์
        List<string> listFiles = new List<string>();
        string path = Server.MapPath("../registPDF");

//ตั้งชื่อไฟล์ที่จะรวมไฟล์ทั้งหมดไว้ และประกาศตัวแปรต่างๆที่จำเป็นต้องใช้ในการสร้างและรวมไฟล์
        string genName = Guid.NewGuid().ToString() + ".pdf";

//ตัวแปร tmpPath เป็นตัวแปรที่เป็นชื่อไฟล์ที่สร้างขึ้นเพื่อรวมไฟล์ทั้งหมด

        string tmpPath = "../registPDF/" + genName;
        string[] files = Directory.GetFiles(path);
        string[] fileName;
        string DestName = "";

//กำหนดนามสกุลที่ต้องการตรวจสอบว่าเป็นไฟล์รูปภาพหรือไม่
        string[] ImgExt = { "png","jpg","jpeg","gif"};

       /// เป็นการลบ Temporary file ที่ถูกสร้างขุึ้นเพื่อรวมไฟล์ เพื่อไม่ให้มีไฟล์ที่ไม่ใช้งานค้างอยู่ในเซิร์ฟเวอร์มากเกินไป *****
        foreach (string file in files)
        {
            FileInfo fi = new FileInfo(file);
            if (fi.LastAccessTime < DateTime.Now.AddMinutes(-30))
                fi.Delete();
        }
      

      //วนค่าเพื่อเก็บตัวแปรชื่อไฟล์แนบก่อนรวมไฟล์

        for (int i = 0; i <= dtFile.Rows.Count - 1; i++)
        {

     //ตรวจสอบว่ามีไฟล์ดังกล่าวอยู่จริงหรือไม่บนเซิร์ฟเวอร์ตามที่อยู่ไฟล์ที่อ้างถึง
            if (File.Exists(Server.MapPath("../" + dtFile.Rows[i]["FILE_PATH"].ToString())))
            {

     //ตรวจสอบชนิดของไฟล์ว่ามีนามสกุลอะไร หากไม่ใช่ไฟล์ PDF จะต้องทำการแปลงและสร้างเป็นไฟล์ PDF ก่อนส่งไปรวมไฟล์
                fileName = dtFile.Rows[i]["FILE_PATH"].ToString().Split('.');
                if (fileName[1] == "pdf")
                    listFiles.Add(Server.MapPath("../" + dtFile.Rows[i]["FILE_PATH"].ToString()));
                else 
                {

     //กรณีที่พบว่าไม่ใช่ไฟล์ PDF จะตรวจสอบว่าเป็นไฟล์รูปภาพหรือไม่ หากใช่จะทำการแปลงและสร้างเป็นไฟล์ PDF ก่อนส่งไปรวมไฟล์
                    if (ImgExt.Contains(fileName[1].ToLower()))
                    {
                        DestName = "../registPDF/"  + Guid.NewGuid().ToString() + ".pdf";
                        ConvertImageToPdf(Server.MapPath("../" + dtFile.Rows[i]["FILE_PATH"].ToString()), Server.MapPath(DestName));
                        listFiles.Add(Server.MapPath( DestName));
                    }
                }
            } 
 
        }

//เรียกใช้งานเมธอดในการรวมไฟล์ โดยส่งค่าลิสต์ของชื่อไฟล์ PDF ทั้งหมดที่ต้องการรวม และชื่อไฟล์ปลายทางที่จะใช้ในการรวม

        CombineMultiplePDFs(listFiles.ToArray(), Server.MapPath(tmpPath));


//แสดงผล PDF ไฟล์ที่ได้ทำการรวมเรียบร้อยแล้วด้วย Literal โดยตัวแปร tmpPath คือที่อยู่ของไฟล์ใหม่ที่ทำการรวมไฟล์ PDF ทั้งหมดเรียบร้อยแล้ว


        StringBuilder strObj = new StringBuilder();

        strObj.Append("<object id=\"pdfContainer\" type=\"application/pdf\"");
        strObj.AppendFormat(" data=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\" style=\" z-index:1000; width: 99%;  height: 600px;\">", tmpPath);
        strObj.AppendFormat(" <param name=\"src\" value=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\"> ", tmpPath);
        strObj.Append(" </object>");

        ltrPDF.Text = strObj.ToString();
        dtFile = null;
        

หมายเหตุ ในการทำงานข้างต้น ควรมีการลบไฟล์(Temporary file)ที่ถูกสร้างไว้ก่อนหน้านี้ที่ไม่ใช้งานออกไปด้วย เนื่องจากการรวมไฟล์ด้วยวิธีนี้จะมีการสร้างไฟล์รวมตัวใหม่ขึ้นมาและนำไปแสดงผลให้ผู้ใช้ ซึ่งหลังจากผู้ใช้งานไม่ใช้งานแล้วหากเราไม่ทำการลบทิ้งจะเกิดเป็นไฟล์ขยะบนเซิร์ฟเวอร์จำนวนมากได้

          โดยจากโค้ดข้างต้นจะพบว่า ไฟล์ที่จะใช้ในการรวบรวมจะต้องเป็นไฟล์ PDF ซึ่งในการทำงานจริงของเรา การแนบไฟล์เอกสาร/หลักฐานสามารถแนบได้ทั้งไฟล์ที่เป็น PDF และไฟล์ที่เป็นรูปภาพด้วย ดังนั้น ก่อนที่จะทำการรวมไฟล์ จึงต้องทำการตรวจสอบว่ามีไฟล์ดังกล่าวอยู่จริงหรือไม่ รวมทั้งตรวจสอบชนิดของไฟล์ด้วย หากพบว่าไฟล์ดังกล่าวไม่ได้อยู่ในรูปแบบ PDF ไฟล์ ให้ทำการแปลงไฟล์รูปภาพเหล่านั้นให้เป็น PDF เสียก่อน เพื่อป้องกันความผิดพลาดที่จะเกิดขึ้นในขณะทำการรวมไฟล์นั่นเอง

2. เขียนเมธอดในการรวมไฟล์ดังกล่าวและสร้างเป็นไฟล์ PDF ตัวใหม่

    public static void CombineMultiplePDFs(string[] fileNames, string outFile)
    {
        // ขั้นที่ 1: สร้าง document ที่จะรวมไฟล์ทั้งหมดขึ้นมา
        Document document = new Document();
        // สร้าง FileStream object ที่จะใช้งานและต้องอย่าลืม dispose เมื่อใช้งานเรียบร้อยแล้ว
        using (FileStream newFileStream = new FileStream(outFile, FileMode.Create))
        {
            // ขั้นที่ 2: สร้างตัว
            PdfCopy writer = new PdfCopy(document, newFileStream);
            if (writer == null)
            {
                return;
            }

            //ขั้นที่ 3:เปิดการใช้งานตัว document
            document.Open();

          // วนเพื่ออ่านค่าชื่อไฟล์ และทำการเพิ่มข้อมูลลงในเอกสารตัวใหม่ที่จะรวมไฟล์ทั้งหมด

           foreach (string fileName in fileNames)
            {
                // สร้างตัว reader จากเอกสารแนบที่กำลังวน
                PdfReader reader = new PdfReader(fileName);
                reader.ConsolidateNamedDestinations();

                // ขั้นที่ 4: ทำการเพิ่มหน้าข้อมูลจาก reader ให้กับตัว writer ทีละหน้า
                for (int i = 1; i <= reader.NumberOfPages; i++)
                {
                    PdfImportedPage page = writer.GetImportedPage(reader, i);
                    writer.AddPage(page);
                }

                PRAcroForm form = reader.AcroForm;
                if (form != null)
                {
                    writer.CopyAcroForm(reader);
                }

                reader.Close();
            }

            // ขั้นที่ 5: ปิดการทำงาน document และ writer
            writer.Close();
            document.Close();
        } 
    }

3.เขียนเมธอดในแปลงไฟล์รูปภาพให้เป็นไฟล์ PDF

 public static void ConvertImageToPdf(string srcFilename, string dstFilename)
    {
        iTextSharp.text.Rectangle pageSize = null;

        using (var srcImage = new Bitmap(srcFilename))
        {
            pageSize = new iTextSharp.text.Rectangle(0, 0, srcImage.Width, srcImage.Height);
        }
        using (var ms = new MemoryStream())
        {
            var document = new iTextSharp.text.Document(pageSize, 0, 0, 0, 0);
            iTextSharp.text.pdf.PdfWriter.GetInstance(document, ms).SetFullCompression();
            document.Open();
            var image = iTextSharp.text.Image.GetInstance(srcFilename);
            document.Add(image);
            document.Close();

            File.WriteAllBytes(dstFilename, ms.ToArray());
        }
    }

4.แสดงผลไฟล์ PDF ที่รวมเรียบร้อยแล้ว ด้วย Literal

        StringBuilder strObj = new StringBuilder();

        strObj.Append("<object id=\"pdfContainer\" type=\"application/pdf\"");
        strObj.AppendFormat(" data=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\" style=\" z-index:1000; width: 99%;  height: 600px;\">", tmpPath);
        strObj.AppendFormat(" <param name=\"src\" value=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\"> ", tmpPath);
        strObj.Append(" </object>");

        ltrPDF.Text = strObj.ToString();

หมายเหตุ ในการใช้งานโค้ดข้างต้น มีไลบรารีที่จำเป็นต้องใช้งาน ดังนี้ค่ะ

using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Data;
using System.Text;
using System.Drawing;

ผลลัพธ์ ตัวอย่างไฟล์ที่ได้จากการรวมไฟล์เอกสาร PDF 3 ไฟล์ และไฟล์รูปภาพ 1 ไฟล์

744273ca-7ef2-4117-881c-c39eb5389bf9

          ซึ่งวิธีการนี้เป็นเพียงหนึ่งในแนวทางแก้ปัญหาในการแสดงผลไฟล์ PDF หลายๆไฟล์ในครั้งเดียวเท่านั้น และอาจมีข้อจำกัดหากผู้ใช้มีการแนบไฟล์จำนวนมาก เนื่องจากจะส่งผลให้ขนาดของไฟล์ที่รวมได้มีขนาดใหญ่มากตามไปด้วย ในส่วนของการแนบไฟล์จึงควรมีการจำกัดขนาดและรูปแบบให้เหมาะสมในการแนบไฟล์แต่ละครั้ง เพื่อประหยัดเนื้อที่ในการจัดเก็บไฟล์บนเซิร์ฟเวอร์ด้วย และผู้เขียนหวังเป็นอย่างยิ่งว่าบทความนี้จะช่วยแก้ปัญหาให้กับนักพัฒนาที่กำลังประสบปัญหาเดียวกันอยู่ไม่มากก็น้อยนะคะ ขอบคุณค่ะ ^^

แหล่งอ้างอิง

https://stackoverflow.com/questions/6029142/merging-multiple-pdfs-using-itextsharp-in-c-net

https://alandjackson.wordpress.com/2013/09/27/convert-an-image-to-a-pdf-in-c-using-itextsharp/