.NET下編寫程式的時候經常會使用到配置檔案。配置檔案格式通常有 xml ini json
、
等幾種,操作不同類型配置檔案需要使用不同的方法,操作較為麻煩。特别是針對同時應用不同格式配置檔案的時候,很容易引起混淆。如果使用一個統一的方法對其進行操作,豈不美哉。
技術方案
思路很簡單,就是使用一個基類将配置檔案的内容抽象出來,不同配置檔案有不同的實作,對外統一調用方法。最開始,打算自己寫一個,後來對比ini與xml的時候(最開始沒有把json考慮進來,自己用它來做配置檔案的項目較少),發現xml完全可以替代ini檔案的描述,直接用xml不是更好?
于是方案就變成了用xml作為最基礎的資料對象,其他配置檔案轉換成xml進行操作。
XDocument VS XmlDocment
不解釋,直接看圖。
ini <-> xml
ini檔案構造比較簡單,常見的ini有以下幾條規範:
- 注釋以;開頭,一直到行尾;
- 類别項用
包圍,占據一整行;[]
- 類别項下可以有多個配置項,直到下一個類别項或EOF結束;
- 配置項格式:key=value
ini格式是二級配置結構:類别>key。通過類别和key就可以唯一獲得一個值。
public static XDocument ToXml(this string[] iniStr)
{
//ini沒有根節點是不能直接轉成xml的。
XDocument xdoc = new XDocument(new XElement("G" + Guid.NewGuid().ToString("N")));
XElement node = xdoc.Root;
foreach (var line in iniStr)
{
var cline = line.Trim();
if (string.IsNullOrWhiteSpace(cline)) continue;
switch (line[0])
{
case ';':
node.Add(new XComment(cline.Substring(1)));
break;
case '[':
node = new XElement(cline.Substring(1, line.Length - 2));
xdoc.Root.Add(node);
break;
case '\r':
break;
default:
int index = cline.IndexOf('=');
if (index < 1)
{
throw new InvalidOperationException("Property does not contains '=' operator");
}
node.Add(new XElement(cline.Substring(0, index).Trim(), cline.Substring(index + 1)));
break;
}
}
return xdoc;
}
ini适合比較簡單的配置讀取,檔案可讀性強,讀寫也簡單;而xml具有多級結構,複雜,還有dtd,xslt什麼的,相對來說比較全面。是以,從xml轉成ini,要求xml符合ini二級結構,詳見
源代碼中的IniExtensions類。
xml <-> json
json在前端不要太火,用途廣泛,占用空間小,機器也較好識别。但是由于最開始沒考慮到json,就先上了xml的船,也懶得去想是不是json作為基礎結構更好了,先轉換json到xml吧。
json與xml互轉:直接用
Newtonsoft.Json
就可以了。使用SerializeXNode和DeserializeXNode就可以完成轉換。
注意,xml與json互轉,有一些地方需要小心。
- xml的屬性,轉成json會加上字首“@”
- xml如果帶聲明的話,轉成json就有字首“?”
- xml同級相同名稱的元素在json中會構成一個數組
- xml的某一級隻有一個元素時,如果需要轉成數組,需要加上
屬性
json:Array='true'
考慮轉換都在内部,讀取json->xml->儲存json。這個流程不需要考慮以上問題。
//json沒有根節點是不能直接轉成xml的。
private static XDocument DecorateJson(string jsonStr)
{
return JsonConvert.DeserializeXNode(jsonStr, "G" + Guid.NewGuid().ToString("N"));
}
封裝問題
xml要求必須有且隻有一個根節點,這一點,ini不滿足,json有的不滿足,是以需要添加一個預設根節點來處理這個問題。
為了防止配置項目和節點名稱沖突,使用
Guid.NewGuid()
來得到不重複的值,由于xml要求元素名稱不能以數字開頭,是以人為添加一個“G”字首,感覺不是很優雅,有辦法還請告知~
操作配置檔案
不管檔案類型是什麼,都用代表一個對象代表一個配置檔案。由于使用了XDocment,那麼使用linq是比較直接的選擇。但是考慮到讀取配置的時候,一般使用者都清楚需要讀取的項目,使用查詢反而不是很直覺。
這裡我采用XPath。XPath是一門在 XML 文檔中查找資訊的語言。XPath 可用來在 XML 文檔中對元素和屬性進行周遊。相關資料可以參見http://www.w3school.com.cn/xpath。
XPath的用法很簡單,對簡單的配置項就和檔案路徑一樣。這裡我使用索引器來進行資料讀取配置,也提供了GetValue和SetValue方法。
public void Test1()
{
string str = @";時間機關為ms
[Dynamic]
Interval = 5
Delay = 4000
[Default]
Interval = 5
";
System.IO.File.WriteAllText("test.ini", str);
// e.g. "//config/general/interval" 從任意節點開始,檢索config節點下general節點下的interval節點
// "/config/tick[@type='origin']" 從根節點檢索config節點下,屬性type='origin'的tick節點(隻對XML)
ConfigManager config = new ConfigManager("test.ini");
Assert.Equal("5", config[@"//Default/Interval"]);
Assert.Equal("5", config.GetValue("Default", "Interval"));
}
對以上配置,可以直接使用"//Delay"獲得Delay的值(沒有重名),對層級較深的配置,使用者不需要關心其他細節,直切正題,這麼用還是很爽快的~
完整代碼
完整代碼在github上,可以支援.net framework/.net core/xamarin。
https://github.com/circler3/UnifiedConfig
對應有nuget包,搜尋nuget或者使用
Install-Package UnifiedConfig
即可将引用添加到項目。
特性
- 配置檔案統一操作接口(讀取,修改,儲存)
- 可拓展配置類型支援
- 自動推斷檔案類型
除非特殊說明,本作品由podolski創作,采用知識共享署名 4.0 國際許可協定進行許可。歡迎轉載,轉載請保留原文連結~喜歡的觀衆老爺們可以點下關注或者推薦~