System.Xml.Linq (LINQ to XML) 是 .NET 3.5 起引入的現代 XML 操作 API,提供比 `XmlDocument` 更直觀、型別安全、可組合的查詢與修改體驗。本文以真實專案中的「子系統設定管理」為例,示範 XML 設定檔的完整增刪改查 (CRUD) 操作。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<OPsystemConfig>
<MemberCenter>
<DomainName>example.com</DomainName>
<ProtocolName>HTTPS</ProtocolName>
<APIKey>xxx</APIKey>
<AESKey>yyy</AESKey>
<AESVI>zzz</AESVI>
</MemberCenter>
<ChildSystems>
<ChildSystem>
<Name>Content</Name>
<ControllerName>ContentManager</ControllerName>
</ChildSystem>
<ChildSystem>
<Name>Image</Name>
<ControllerName>ImageManager</ControllerName>
</ChildSystem>
<ChildSystem>
<Name>Comment</Name>
<ControllerName>CommentManager</ControllerName>
</ChildSystem>
</ChildSystems>
</OPsystemConfig>
</configuration>
public class SystemModuleConfig
{
public string Name { get; set; }
public string ControllerName { get; set; }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
public class SystemModuleConfigService : ISystemModuleConfigService
{
private readonly string _configPath;
public SystemModuleConfigService(string configPath)
{
_configPath = configPath ?? throw new ArgumentNullException(nameof(configPath));
EnsureFileExists();
}
private void EnsureFileExists()
{
if (!File.Exists(_configPath))
{
// 建立預設結構
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("configuration",
new XElement("OPsystemConfig",
new XElement("MemberCenter"),
new XElement("ChildSystems")
)
)
);
doc.Save(_configPath);
}
}
private XDocument LoadDoc() => XDocument.Load(_configPath);
private void SaveDoc(XDocument doc)
{
// 移除唯讀屬性再儲存
var fi = new FileInfo(_configPath);
if (fi.IsReadOnly) fi.IsReadOnly = false;
doc.Save(_configPath);
}
// ---------- 查詢 ----------
public IList GetList()
{
var doc = LoadDoc();
return doc.Descendants("ChildSystem")
.Select(e => new SystemModuleConfig
{
Name = (string)e.Element("Name"),
ControllerName = (string)e.Element("ControllerName")
})
.ToList();
}
public SystemModuleConfig GetModel(string name)
{
var doc = LoadDoc();
var elem = doc.Descendants("ChildSystem")
.FirstOrDefault(e => (string)e.Element("Name") == name);
return elem == null ? null : new SystemModuleConfig
{
Name = (string)elem.Element("Name"),
ControllerName = (string)elem.Element("ControllerName")
};
}
private bool Exists(string name)
{
var doc = LoadDoc();
return doc.Descendants("ChildSystem")
.Any(e => (string)e.Element("Name") == name);
}
// ---------- 新增 ----------
public bool Add(string name, string controllerName)
{
if (Exists(name)) return false; // 已存在
var doc = LoadDoc();
var childSystems = doc.Descendants("ChildSystems").First();
childSystems.Add(
new XElement("ChildSystem",
new XElement("Name", name),
new XElement("ControllerName", controllerName)
)
);
SaveDoc(doc);
return true;
}
// ---------- 修改 ----------
public bool Modify(string name, string controllerName)
{
var doc = LoadDoc();
var elem = doc.Descendants("ChildSystem")
.FirstOrDefault(e => (string)e.Element("Name") == name);
if (elem == null) return false;
elem.Element("ControllerName").Value = controllerName;
SaveDoc(doc);
return true;
}
// ---------- 刪除 ----------
public bool Remove(string name)
{
var doc = LoadDoc();
var elements = doc.Descendants("ChildSystem")
.Where(e => (string)e.Element("Name") == name)
.ToList(); // ToList 避免修改集合時迭代器失效
if (!elements.Any()) return false;
foreach (var el in elements) el.Remove();
SaveDoc(doc);
return true;
}
}
// 推薦:使用显式轉換 (string)element,自動處理 null
string name = (string)element.Element("Name"); // 若元素不存在回傳 null,不拋例外
// 不推薦:element.Element("Name").Value // 若元素不存在拋 NullReferenceException
// 巢狀建構,可讀性高
var newChild = new XElement("ChildSystem",
new XElement("Name", name),
new XElement("ControllerName", controllerName)
);
parentElement.Add(newChild);
// 先查詢收集到 List,再逐一修改/刪除,避免「修改集合時無法迭代」錯誤
var items = doc.Descendants("ChildSystem").Where(...).ToList();
foreach (var item in items) item.Remove();
var fi = new FileInfo(path);
if (fi.IsReadOnly) fi.IsReadOnly = false;
doc.Save(path);
XNamespace ns = "https://example.com/schema";
var query = doc.Descendants(ns + "ChildSystem");
public class SystemModuleConfigServiceTests
{
private readonly string _testFile = Path.Combine(Path.GetTempPath(), "test_config.xml");
private readonly SystemModuleConfigService _service;
public SystemModuleConfigServiceTests()
{
if (File.Exists(_testFile)) File.Delete(_testFile);
_service = new SystemModuleConfigService(_testFile);
}
[Fact]
public void Add_Then_GetList_ShouldContainNewItem()
{
_service.Add("TestModule", "TestController");
var list = _service.GetList();
Assert.Contains(list, x => x.Name == "TestModule" && x.ControllerName == "TestController");
}
[Fact]
public void Modify_ExistingItem_ShouldUpdate()
{
_service.Add("ModTest", "OldCtrl");
_service.Modify("ModTest", "NewCtrl");
var model = _service.GetModel("ModTest");
Assert.Equal("NewCtrl", model.ControllerName);
}
[Fact]
public void Remove_ExistingItem_ShouldDelete()
{
_service.Add("DelTest", "Ctrl");
_service.Remove("DelTest");
Assert.Null(_service.GetModel("DelTest"));
}
}
LINQ to XML 以其函數式建構、強型別查詢、直觀的修改 API,大幅降低 XML 操作的複雜度。掌握 `XElement`/`XAttribute` 的建構、查詢、轉換與儲存模式,即可輕鬆應對設定檔管理、資料交換、報表產出等多種場景。記住:查詢用 LINQ,修改先 ToList,儲存前檔案屬性,是避坑三寶。