appSettings太簡單,為每個程式自定義配置節點太複雜,因此要解決app.config&web.config自定義配置的複用問題。
1.讀取不依賴SectionName,根節點可以定義為任何名稱。
2.足夠簡單,配置項採用name value的形式;足夠複雜,採用樹型結構,每個節點都可以有多個配置項和子節點。
3.使用簡單,採用路徑簡化配置項的讀取。如: config.Get<string>("root.sub.item-test")。
一、呼叫方式:
1.配置檔案:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="node" type="Onion.Configuration.AppConfig.ConfigSection,Onion.Configuration" /> </configSections> <node name="root"> <items> <item name="version" value="1.0.0.1" /> </items> <nodes> <node name="runtime"> <items> <item name="debug" value="false" /> <item name="ioc" value="IoC.Contianer.StructureMapIoC" /> </items> </node> <node name="upload"> <items> <item name="auth" value="true" /> <item name="path" value="~/upload" /> <item name="url" value="~/Upload/Index" /> </items> </node> <node name="captcha"> <items> <item name="timeout" value="3000" /> <item name="url" value="~/Captcha/Index" /> </items> </node> <node name="oauth2"> <items> <item name="disabled" value ="false" /> <item name="callback" value ="/Home/ExternalLoginCallBack?ProviderName=" /> </items> <nodes> <node name="qqclient"> <items> <item name="disabled" value="false" /> <item name="method" value="get" /> <item name="key" value="9233e24d" /> <item name="secret" value="1ac35907-7cfa-4079-975c-959b98d23a95" /> </items> </node> <node name="weiboclient"> <items> <item name="disabled" value="true" /> <item name="method" value="post" /> <item name="key" value="0cdea8f3" /> <item name="secret" value="dc679dbb-7e75-44f7-a99e-5359259fc94b" /> </items> </node> </nodes> </node> </nodes> </node> </configuration>
2.呼叫程式碼:
[Fact] public void Tests() { var config = new AppConfigAdapter(); Assert.True(config.Get<string>("version") == "1.0.0.1"); Assert.True(config.Get<bool>("runtime.debug") == false); Assert.True(config.Get<string>("runtime.ioc") == "IoC.Contianer.StructureMapIoC"); Assert.True(config.Get<bool>("upload.auth") == true); Assert.True(config.Get<string>("upload.path") == "~/upload"); Assert.True(config.Get<string>("upload.url") == "~/Upload/Index"); Assert.True(config.Get<int>("captcha.timeout") == 3000); Assert.True(config.Get<string>("captcha.url") == "~/Captcha/Index"); Assert.True(config.Get<bool>("oauth2.disabled") == false); Assert.True(config.Get<string>("oauth2.callback") == "/Home/ExternalLoginCallBack?ProviderName="); Assert.True(config.GetNode("oauth2").Nodes.Any(o => o.GetItem<bool>("disabled"))); foreach (var node in config.GetNode("oauth2").Nodes) { if (node.Name == "qqclient") { Assert.True(node.GetItem<bool>("disabled") == false); Assert.True(node.GetItem<string>("method") == "get"); Assert.True(node.GetItem<string>("key") == "9233e24d"); Assert.True(node.GetItem<string>("secret") == "1ac35907-7cfa-4079-975c-959b98d23a95"); } else if (node.Name == "weiboclient") { Assert.True(node.GetItem<bool>("disabled") == true); Assert.True(node.GetItem<string>("method") == "post"); Assert.True(node.GetItem<string>("key") == "0cdea8f3"); Assert.True(node.GetItem<string>("secret") == "dc679dbb-7e75-44f7-a99e-5359259fc94b"); } } }
二、介面定義:
1.配置項定義:IItem介面定義最基礎的配置項,只包含Name和Value屬性。
public interface IItem { string Name { get; set; } string Value { get; set; } }
2.配置節點定義:INode介面定義了配置節點的樹形結構
public interface INode { string Name { get; set; } IEnumerable<IItem> Items { get; set; } IEnumerable<INode> Nodes { get; set; } string GetItem(string itemName); T GetItem<T>(string itemName); }
3.讀取介面定義:IConfig介面定義了配置節點和配置項的讀取
public interface IConfig { INode GetNode(string nodeName); string Get(string nameOrPath); T Get<T>(string nameOrPath); }
以上3個介面定義了所有的邏輯。
三、介面實現:
1.自定義ItemElement(IItem)和ItemElementCollection用於實現單個節點的配置項讀取。
public class ItemElement : ConfigurationElement, IItem { [ConfigurationProperty("name", IsRequired = true)] public string Name { get { return Convert.ToString(this["name"]); } set { this["name"] = value; } } [ConfigurationProperty("value", IsRequired = true)] public string Value { get { return Convert.ToString(this["value"]); } set { this["value"] = value; } } } public class ItemElementCollection : ConfigurationElementCollection, IEnumerable<IItem> { protected override ConfigurationElement CreateNewElement() { return new ItemElement(); } protected override object GetElementKey(ConfigurationElement element) { return ((ItemElement)element).Name; } public new IEnumerator<IItem> GetEnumerator() { for (int i = 0; i < base.Count; i++) { yield return base.BaseGet(i) as IItem; } } }
2.自定義NodeElement(INode)和NodeElementCollection用於實現節點樹功能。
public class NodeElement : ConfigurationElement, INode { [ConfigurationProperty("name", IsRequired = true)] public string Name { get { return Convert.ToString(this["name"]); } set { this["name"] = value; } } [ConfigurationProperty("items")] [ConfigurationCollection(typeof(ItemElementCollection), AddItemName = "item")] public ItemElementCollection ItemElements { get { return this["items"] as ItemElementCollection; } set { this["items"] = value; } } [ConfigurationProperty("nodes")] [ConfigurationCollection(typeof(NodeElementCollection), AddItemName = "node")] public NodeElementCollection NodeElements { get { return this["nodes"] as NodeElementCollection; } set { this["nodes"] = value; } } public IEnumerable<IItem> Items { get { return this["items"] as ItemElementCollection; } set { this["items"] = value; } } public IEnumerable<INode> Nodes { get { return this["nodes"] as NodeElementCollection; } set { this["nodes"] = value; } } public string GetItem(string itemName) { return this.Items.FirstOrDefault(o => o.Name == itemName)?.Value; } public T GetItem<T>(string itemName) { return (T)Convert.ChangeType(this.GetItem(itemName), typeof(T)); } } public class NodeElementCollection : ConfigurationElementCollection, IEnumerable<INode> { protected override ConfigurationElement CreateNewElement() { return new NodeElement(); } protected override object GetElementKey(ConfigurationElement element) { return ((NodeElement)element).Name; } public new IEnumerator<INode> GetEnumerator() { for (int i = 0; i < base.Count; i++) { yield return base.BaseGet(i) as INode; } } }
3.自定義ConfigSection實現配置節點和配置項讀取。
public class ConfigSection : ConfigurationSection, INode { [ConfigurationProperty("name", IsRequired = true)] public string Name { get { return Convert.ToString(this["name"]); } set { this["name"] = value; } } [ConfigurationProperty("items")] [ConfigurationCollection(typeof(ItemElementCollection), AddItemName = "item")] public ItemElementCollection ItemElements { get { return this["items"] as ItemElementCollection; } set { this["items"] = value; } } [ConfigurationProperty("nodes")] [ConfigurationCollection(typeof(NodeElementCollection), AddItemName = "node")] public NodeElementCollection NodeElements { get { return (NodeElementCollection)this["nodes"]; } set { this["nodes"] = value; } } public IEnumerable<IItem> Items { get { return this["items"] as ItemElementCollection; } set { this["items"] = value; } } public IEnumerable<INode> Nodes { get { return (NodeElementCollection)this["nodes"]; } set { this["nodes"] = value; } } public string GetItem(string itemName) { return this.Items.FirstOrDefault(o => o.Name == itemName)?.Value; } public T GetItem<T>(string itemName) { return (T)Convert.ChangeType(this.GetItem(itemName), typeof(T)); } }
4.自定義AppConfigAdapter實現IConfig介面。
public class AppConfigAdapter : IConfig { private INode _section; public AppConfigAdapter() { var sectionName = (HostingEnvironment.IsHosted ? WebConfigurationManager.OpenWebConfiguration("~") : ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)) .Sections.Cast<ConfigurationSection>() .FirstOrDefault(o => o.SectionInformation.Type.IndexOf("Onion.Configuration.AppConfig.ConfigSection") != -1) .SectionInformation.Name ?? "Node"; _section = (INode)ConfigurationManager.GetSection(sectionName); } public INode GetNode(string nodeName) { return this.GetNode(nodeName, this._section); } public string Get(string nameOrPath) { if (nameOrPath.IndexOf('.') == -1) { return this._section.Items.FirstOrDefault(o => o.Name == nameOrPath)?.Value; } var nodeItemPath = nameOrPath.Split('.'); var node = this.GetNode(nodeItemPath.FirstOrDefault()); var nodeNameList = nodeItemPath.Skip(1).Take(nodeItemPath.Length - 2); if (node != null) { foreach (var item in nodeNameList) { if (node.Nodes.Any(o => o.Name == item)) { node = node.Nodes.FirstOrDefault(o => o.Name == item); } else { throw new System.ArgumentException(string.Format("node name {0} error", item)); } } return node.Items.FirstOrDefault(o => o.Name == nodeItemPath.LastOrDefault()).Value; } return null; } public T Get<T>(string nameOrPath) { var value = this.Get(nameOrPath); return (T)Convert.ChangeType(value, typeof(T)); } #region private private INode GetNode(string nodeName, INode node) { INode result = null; if (node.Name == nodeName) { return node; } else if (node.Nodes.Any()) { foreach (var item in node.Nodes) { result = GetNode(nodeName, item); if (result != null) { break; } } } return result; } #endregion private }
Nuget:https://www.nuget.org/packages/Onion.Configuration/