首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 檔案與 XML 互轉完整指南:Base64 編碼實現二進位檔案 XML 序列化傳輸

     編輯:關於C語言

前言:為什麼要將檔案轉為 XML?

在異構系統整合、Web Service 傳輸、訊息佇列、資料庫儲存二進位資料等場景中,XML 作為平台無關、文字基礎、自我描述的格式,是二進位檔案跨系統傳輸的理想載體。核心技術是 **Base64 編碼**——將任意二進位資料轉為 ASCII 字串,安全嵌入 XML 節點中。

核心原理:Base64 編碼機制

  • 原理:每 3 個位元組 (24 bits) 分成 4 組 6 bits,對應 64 個可列印字元 (A-Z, a-z, 0-9, +, /)
  • 體積膨脹:編碼後體積約增加 33% (4/3)
  • 適用性:任何二進位檔案——Office 文件、圖片、PDF、壓縮檔、執行檔等
  • .NET API:Convert.ToBase64String(byte[]) / Convert.FromBase64String(string)

完整解決方案架構

核心轉換類別

using System;
using System.IO;
using System.Xml;

namespace FileXmlConverter
{
    /// 
    /// 檔案與 XML 雙向轉換工具類別
    /// 
    public static class FileXmlConverter
    {
        private const string RootElementName = "FileData";
        private const string ContentAttributeName = "Content";
        private const string FileNameAttributeName = "FileName";
        private const string ExtensionAttributeName = "Extension";
        private const string LengthAttributeName = "Length";
        private const string CreatedAttributeName = "CreatedUtc";
        private const string ModifiedAttributeName = "ModifiedUtc";

        /// 
        /// 將檔案轉換為 XML 文件 (Base64 編碼)
        /// 
        /// 來源檔案完整路徑
        /// 輸出 XML 路徑 (可選,null 則回傳 XmlDocument)
        /// 包含檔案資料的 XmlDocument
        public static XmlDocument FileToXml(string filePath, string xmlOutputPath = null)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException("找不到指定檔案", filePath);

            var fileInfo = new FileInfo(filePath);
            byte[] fileBytes = File.ReadAllBytes(filePath);
            string base64Content = Convert.ToBase64String(fileBytes);

            var xmlDoc = new XmlDocument();
            
            // 建立根節點
            XmlElement root = xmlDoc.CreateElement(RootElementName);
            xmlDoc.AppendChild(root);

            // 基本屬性
            root.SetAttribute(FileNameAttributeName, fileInfo.Name);
            root.SetAttribute(ExtensionAttributeName, fileInfo.Extension);
            root.SetAttribute(LengthAttributeName, fileInfo.Length.ToString());
            root.SetAttribute(CreatedAttributeName, fileInfo.CreationTimeUtc.ToString("o")); // ISO 8601
            root.SetAttribute(ModifiedAttributeName, fileInfo.LastWriteTimeUtc.ToString("o"));

            // Base64 內容 (使用 CDATA 區段避免特殊字元問題)
            XmlCDataSection cdata = xmlDoc.CreateCDataSection(base64Content);
            root.AppendChild(cdata);

            // 選擇性儲存至檔案
            if (!string.IsNullOrEmpty(xmlOutputPath))
            {
                var settings = new XmlWriterSettings 
                { 
                    Indent = true, 
                    Encoding = System.Text.Encoding.UTF8,
                    NewLineChars = "\n"
                };
                using (var writer = XmlWriter.Create(xmlOutputPath, settings))
                {
                    xmlDoc.Save(writer);
                }
            }

            return xmlDoc;
        }

        /// 
        /// 從 XML 文件還原檔案 (Base64 解碼)
        /// 
        /// 輸入 XML 路徑
        /// 輸出目錄 (可選,預設為目前目錄)
        /// 自訂檔名 (可選,預設使用 XML 中記錄的檔名)
        /// 還原後的檔案完整路徑
        public static string XmlToFile(string xmlInputPath, string outputDirectory = null, string customFileName = null)
        {
            if (!File.Exists(xmlInputPath))
                throw new FileNotFoundException("找不到 XML 檔案", xmlInputPath);

            var xmlDoc = new XmlDocument();
            xmlDoc.Load(xmlInputPath);

            XmlElement root = xmlDoc.DocumentElement;
            if (root == null || root.Name != RootElementName)
                throw new InvalidOperationException("XML 格式無效:缺少根節點 FileData");

            // 讀取 Base64 內容 (支援 CDATA 或直接文字)
            string base64Content = root.FirstChild?.Value?.Trim() 
                                 ?? root.InnerText.Trim();
            
            if (string.IsNullOrEmpty(base64Content))
                throw new InvalidOperationException("XML 中無檔案內容資料");

            byte[] fileBytes;
            try
            {
                fileBytes = Convert.FromBase64String(base64Content);
            }
            catch (FormatException ex)
            {
                throw new InvalidOperationException("Base64 解碼失敗:XML 內容可能已損毀", ex);
            }

            // 決定輸出檔名
            string fileName = customFileName ?? root.GetAttribute(FileNameAttributeName);
            if (string.IsNullOrEmpty(fileName))
                fileName = "RestoredFile" + root.GetAttribute(ExtensionAttributeName);

            string outputDir = outputDirectory ?? Environment.CurrentDirectory;
            Directory.CreateDirectory(outputDir);
            string outputPath = Path.Combine(outputDir, fileName);

            // 寫入檔案 (若存在則覆蓋)
            File.WriteAllBytes(outputPath, fileBytes);

            // 選擇性還原時間屬性
            if (DateTime.TryParse(root.GetAttribute(CreatedAttributeName), out DateTime created))
                File.SetCreationTimeUtc(outputPath, created);
            if (DateTime.TryParse(root.GetAttribute(ModifiedAttributeName), out DateTime modified))
                File.SetLastWriteTimeUtc(outputPath, modified);

            return outputPath;
        }

        /// 
        /// 從 XmlDocument 直接還原檔案 (不經檔案系統)
        /// 
        public static byte[] XmlToBytes(XmlDocument xmlDoc)
        {
            XmlElement root = xmlDoc.DocumentElement;
            string base64Content = root.FirstChild?.Value?.Trim() ?? root.InnerText.Trim();
            return Convert.FromBase64String(base64Content);
        }
    }
}

Windows Forms 完整示範應用程式

包含四個按鈕:載入檔案→產生 XML、載入 XML→還原檔案、另存 XML、另存檔案

using System;
using System.IO;
using System.Windows.Forms;
using System.Xml;

namespace FileXmlConverterDemo
{
    public partial class MainForm : Form
    {
        private XmlDocument _currentXmlDoc;
        private string _originalFilePath;

        public MainForm()
        {
            InitializeComponent();
            Text = "檔案 ⇄ XML 轉換器 (Base64)";
            Size = new System.Drawing.Size(500, 300);
        }

        private void InitializeComponent()
        {
            var btnLoadFile = new Button { Text = "1. 載入檔案", Top = 20, Left = 20, Width = 120 };
            var btnGenXml = new Button { Text = "2. 產生 XML", Top = 20, Left = 160, Width = 120, Enabled = false };
            var btnLoadXml = new Button { Text = "3. 載入 XML", Top = 60, Left = 20, Width = 120 };
            var btnRestore = new Button { Text = "4. 還原檔案", Top = 60, Left = 160, Width = 120, Enabled = false };
            var btnSaveXml = new Button { Text = "另存 XML...", Top = 100, Left = 20, Width = 120, Enabled = false };
            var btnSaveFile = new Button { Text = "另存檔案...", Top = 100, Left = 160, Width = 120, Enabled = false };
            var lblStatus = new Label { Top = 150, Left = 20, Width = 440, Height = 80, BorderStyle = BorderStyle.Fixed3D };

            btnLoadFile.Click += (s, e) => LoadFile(btnGenXml, btnSaveXml, lblStatus);
            btnGenXml.Click += (s, e) => GenerateXml(btnLoadXml, btnRestore, btnSaveXml, lblStatus);
            btnLoadXml.Click += (s, e) => LoadXml(btnRestore, btnSaveFile, lblStatus);
            btnRestore.Click += (s, e) => RestoreFile(btnSaveFile, lblStatus);
            btnSaveXml.Click += (s, e) => SaveXmlDialog(lblStatus);
            btnSaveFile.Click += (s, e) => SaveFileDialog(lblStatus);

            Controls.AddRange(new Control[] { btnLoadFile, btnGenXml, btnLoadXml, btnRestore, btnSaveXml, btnSaveFile, lblStatus });
        }

        private void LoadFile(Button enableGenXml, Button enableSaveXml, Label status)
        {
            using (var ofd = new OpenFileDialog { Filter = "所有檔案|*.*", Title = "選擇要轉換的檔案" })
            {
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        _originalFilePath = ofd.FileName;
                        _currentXmlDoc = FileXmlConverter.FileToXml(_originalFilePath);
                        enableGenXml.Enabled = true;
                        enableSaveXml.Enabled = true;
                        status.Text = $"✅ 已載入: {Path.GetFileName(_originalFilePath)} ({new FileInfo(_originalFilePath).Length:N0} bytes)\nXML 節點: {_currentXmlDoc.DocumentElement.Name}";
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"載入失敗: {ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }

        private void GenerateXml(Button enableLoadXml, Button enableRestore, Button enableSaveXml, Label status)
        {
            // FileToXml 內部已建立 XML,這裡只是狀態更新
            enableLoadXml.Enabled = true;
            enableRestore.Enabled = true;
            enableSaveXml.Enabled = true;
            status.Text += $"\n✅ XML 文件已在記憶體中建立完成";
        }

        private void SaveXmlDialog(Label status)
        {
            if (_currentXmlDoc == null) return;
            using (var sfd = new SaveFileDialog { Filter = "XML 檔案|*.xml", Title = "儲存 XML 檔案", FileName = Path.GetFileNameWithoutExtension(_originalFilePath) + ".xml" })
            {
                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    _currentXmlDoc.Save(sfd.FileName);
                    status.Text += $"\n💾 XML 已儲存至: {sfd.FileName}";
                }
            }
        }

        private void LoadXml(Button enableRestore, Button enableSaveFile, Label status)
        {
            using (var ofd = new OpenFileDialog { Filter = "XML 檔案|*.xml", Title = "選擇要還原的 XML 檔案" })
            {
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        _currentXmlDoc = new XmlDocument();
                        _currentXmlDoc.Load(ofd.FileName);
                        enableRestore.Enabled = true;
                        enableSaveFile.Enabled = true;
                        var root = _currentXmlDoc.DocumentElement;
                        status.Text = $"✅ 已載入 XML: {Path.GetFileName(ofd.FileName)}\n原始檔名: {root?.GetAttribute("FileName")}\n大小: {root?.GetAttribute("Length")} bytes";
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"XML 載入失敗: {ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }

        private void RestoreFile(Button enableSaveFile, Label status)
        {
            using (var sfd = new SaveFileDialog 
            { 
                Filter = "所有檔案|*.*", 
                Title = "儲存還原的檔案",
                FileName = _currentXmlDoc.DocumentElement?.GetAttribute("FileName") ?? "RestoredFile"
            })
            {
                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        byte[] data = FileXmlConverter.XmlToBytes(_currentXmlDoc);
                        File.WriteAllBytes(sfd.FileName, data);
                        enableSaveFile.Enabled = true;
                        status.Text += $"\n✅ 檔案已還原: {sfd.FileName} ({data.Length:N0} bytes)";
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"還原失敗: {ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }

        private void SaveFileDialog(Label status)
        {
            // 已在 RestoreFile 中處理另存邏輯
            status.Text += "\n💡 請使用「還原檔案」按鈕選擇儲存位置";
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

進階應用場景與擴展

1. Web Service / API 傳輸

// WCF / Web API 契約範例
[DataContract]
public class FileTransferDto
{
    [DataMember] public string FileName { get; set; }
    [DataMember] public string ContentBase64 { get; set; }
    [DataMember] public long Length { get; set; }
    [DataMember] public string ContentType { get; set; } // MIME type
    [DataMember] public DateTime CreatedUtc { get; set; }
}

// 用戶端呼叫
public async Task UploadFileAsync(string filePath)
{
    var dto = new FileTransferDto
    {
        FileName = Path.GetFileName(filePath),
        ContentBase64 = Convert.ToBase64String(File.ReadAllBytes(filePath)),
        Length = new FileInfo(filePath).Length,
        ContentType = GetMimeType(filePath),
        CreatedUtc = File.GetCreationTimeUtc(filePath)
    };
    await _client.UploadAsync(dto);
}

2. 大檔案分塊傳輸 (避免記憶體壓力)

public static IEnumerable FileToBase64Chunks(string filePath, int chunkSize = 1024 * 1024) // 1MB per chunk
{
    using (var fs = File.OpenRead(filePath))
    {
        byte[] buffer = new byte[chunkSize];
        int bytesRead;
        int chunkIndex = 0;
        while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
        {
            byte[] chunk = new byte[bytesRead];
            Array.Copy(buffer, chunk, bytesRead);
            yield return Convert.ToBase64String(chunk);
            chunkIndex++;
        }
    }
}

// XML 結構調整為多節點
// 
//   base64...
//   base64...
// 

3. 資料庫儲存 (VARBINARY vs Base64 XML)

方案 優點 缺點 適用場景
VARBINARY/BLOB 空間效率高、原生二進位、資料庫原生支援 跨資料庫移植性差、需特定驅動、難以在 XML/JSON 中直接表達 單一資料庫系統、高效能讀寫
Base64 XML/JSON 平台無關、文字化、易於除錯、支援中繼資料 空間 +33%、編解碼 CPU 開銷、大檔案記憶體壓力 異構整合、Web API、訊息佇列、日誌稽核
檔案系統 + 路徑存 DB 資料庫輕量、檔案系統優化、支援串流 備份/同步複雜、路徑失效風險、權限管理分離 大量大檔案、雲端儲存 (Blob/S3)

效能與限制考量

記憶體使用量估算

// 100 MB 檔案
// 讀取位元組陣列: ~100 MB
// Base64 字串: ~133 MB (字元 = 2 bytes in .NET string) = ~266 MB 記憶體
// XmlDocument DOM 樹: 額外開銷
// 總計: 可能超過 400-500 MB 記憶體

// 解決方案:大檔案使用串流式處理
public static void LargeFileToXmlStream(string inputPath, string outputPath)
{
    using (var input = File.OpenRead(inputPath))
    using (var output = File.CreateText(outputPath))
    using (var writer = XmlWriter.Create(output, new XmlWriterSettings { Indent = true }))
    {
        writer.WriteStartElement("FileData");
        writer.WriteAttributeString("FileName", Path.GetFileName(inputPath));
        writer.WriteAttributeString("Length", input.Length.ToString());
        
        writer.WriteStartElement("Content");
        
        const int BufferSize = 81920; // 80KB -> Base64 ~108KB 字串
        byte[] buffer = new byte[BufferSize];
        int bytesRead;
        while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            byte[] chunk = new byte[bytesRead];
            Array.Copy(buffer, chunk, bytesRead);
            writer.WriteString(Convert.ToBase64String(chunk));
        }
        
        writer.WriteEndElement(); // Content
        writer.WriteEndElement(); // FileData
    }
}

安全性考量

  • Base64 非加密:僅編碼,任何人可解碼。機敏資料需先加密 (AES) 再 Base64
  • XML 外部實體 (XXE) 攻擊:載入不信任 XML 時,停用 DTD 處理:xmlDoc.XmlResolver = null
  • 大檔案 DoS:限制上傳大小、使用串流處理、避免完整載入 DOM

總結

  • **核心技術**:Convert.ToBase64String / Convert.FromBase64String 實現二進位 ↔ 文字無損轉換
  • **XML 結構設計**:根節點攜帶中繼資料 (檔名、時間、大小),CDATA 區段包裹 Base64 內容
  • **實務選擇**:小檔案 (≤10MB) 直接 DOM 處理;大檔案改用 XmlWriter / XmlReader 串流模式
  • **現代替代方案**:JSON + Base64 (較輕量)、gRPC/Protobuf 二進位序列化 (高效能)、雲端 Blob 儲存 + 參考傳遞 (架構級)

延伸閱讀:.NET 串流式 XML 處理 | Base64 與 Base64Url 差異 | 大檔案分塊上傳與斷點續傳

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved