在異構系統整合、Web Service 傳輸、訊息佇列、資料庫儲存二進位資料等場景中,XML 作為平台無關、文字基礎、自我描述的格式,是二進位檔案跨系統傳輸的理想載體。核心技術是 **Base64 編碼**——將任意二進位資料轉為 ASCII 字串,安全嵌入 XML 節點中。
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);
}
}
}
包含四個按鈕:載入檔案→產生 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());
}
}
}
// 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);
}
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...
//
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 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
}
}
xmlDoc.XmlResolver = nullConvert.ToBase64String / Convert.FromBase64String 實現二進位 ↔ 文字無損轉換XmlWriter / XmlReader 串流模式