上一篇文章直接就被移除首頁了,這次來點大家都能懂的乾貨. 需求 之前做一個winform的工具時候有以下幾個需求1. 主窗體(或者叫平臺)可以安裝若幹類型的插件。2. 插件關閉時候需要保存狀態。3. 插件載入的時候可以載入上次關閉的配置。4. 插件中的配置可以切換。5. 主窗體本身保存當前插件,並且 ...
上一篇文章直接就被移除首頁了,這次來點大家都能懂的乾貨.
需求
之前做一個winform的工具時候有以下幾個需求
1. 主窗體(或者叫平臺)可以安裝若幹類型的插件。
2. 插件關閉時候需要保存狀態。
3. 插件載入的時候可以載入上次關閉的配置。
4. 插件中的配置可以切換。
5. 主窗體本身保存當前插件,並且可以通過不同的配置文件切換插件
使用上最方便的做法是將配置給平臺來管理。但是平臺本身並不知道插件要保存怎樣的配置。針對以上問題在配置這個上做瞭如下設計
設計
1. 動態類型序列化以滿足插件的任何配置需要
2. 動態類型基本的就是dynamic,那麼我們需用字典作為實現
3. 支持具體的類進行序列化,那麼此時需要用xml保存類的元數據信息
4. 支持介面的序列化,此時也是保存實際類型的元數據信息
5. 支持List序列化
6. 支持Arry序列化
7. 支持Dictionary序列化
介面定義
其中PathOrSourceString 屬性這樣既可以支持文件,也可以直接支持字元串,擴展更加方便.
public interface IConfig { string PathOrSourceString { get; set; } dynamic Data { get; set; } }
動態類型實現
這裡是基於字典,網上有很多類似的代碼。
這裡字典的Value設計成dynamic是為了嵌套。
[Serializable] public class DynamicDictionary : DynamicObject { private Dictionary<string, dynamic> _dictionary = new Dictionary<string, dynamic>(); public override bool TryGetMember(GetMemberBinder binder, out object result) { string name = binder.Name; if (!_dictionary.ContainsKey(name)) { _dictionary.Add(name, new DynamicDictionary()); } return _dictionary.TryGetValue(name, out result); } public override bool TrySetMember(SetMemberBinder binder, object value) { var key = binder.Name; if (_dictionary.ContainsKey(key)) _dictionary[key] = value; else { _dictionary.Add(key, value); } return true; } public Dictionary<string, dynamic> Dictionary { get { return _dictionary; } } public void AddMember(string name, dynamic value) { _dictionary.Add(name, value); } }
配置的載入和保存邏輯(核心)
public static class ConfigManager { public static IConfig LoadFromFile(this IConfig config) { if (config == null || string.IsNullOrEmpty(config.PathOrSourceString)) throw new ArgumentNullException("config"); if (!File.Exists(config.PathOrSourceString)) { return config; } var doc = new XmlDocument(); doc.Load(config.PathOrSourceString); var element = doc["Data"]; config.Data = GetValue(element); return config; } public static IConfig SaveToFile(this IConfig config) { if (config == null || string.IsNullOrEmpty(config.PathOrSourceString) || config.Data == null) throw new ArgumentNullException("config"); var dir = Path.GetDirectoryName(config.PathOrSourceString); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); var doc = new XmlDocument(); doc.AppendChild(GetXml("Data", config.Data, doc)); doc.Save(config.PathOrSourceString); return config; } public static IConfig LoadFromString(this IConfig config) { if (config == null || string.IsNullOrEmpty(config.PathOrSourceString)) throw new ArgumentNullException("config"); var doc = new XmlDocument(); doc.LoadXml(config.PathOrSourceString); var element = doc["Data"]; config.Data = GetValue(element); return config; } public static IConfig SaveToString(this IConfig config) { if (config == null || config.Data == null) throw new ArgumentNullException("config"); var doc = new XmlDocument(); doc.AppendChild(GetXml("Data", config.Data, doc)); config.PathOrSourceString = doc.OuterXml; return config; } #region 解析XmlElement public static dynamic GetValue(XmlElement element) { if (element == null) return null; Classify clasify; Enum.TryParse(element.GetAttribute("Classify"), out clasify); switch (clasify) { case Classify.Sample: return GetSampleValue(element.GetAttribute("Assembly"), element.GetAttribute("Type"), element.InnerText); case Classify.Array: return GetArrayValue(element.GetAttribute("ElementAssembly"), element.GetAttribute("ElementType"), element.GetChidlren()); case Classify.List: return GetListValue(element.GetAttribute("GenericAssembly"), element.GetAttribute("GenericType"), element.GetChidlren()); case Classify.Dictionary: return GetDictionaryValue(element.GetAttribute("KeyGenericAssembly"), element.GetAttribute("KeyGenericType"), element.GetAttribute("ValueGenericAssembly"), element.GetAttribute("ValueGenericType"), element.GetChidlren()); case Classify.Dynamic: return GetDynamicValue(element.GetChidlren()); case Classify.Custom: return GetCustomValue(element.GetAttribute("Assembly"), element.GetAttribute("Type"), element.GetChidlren()); } return null; } public static object GetSampleValue(string assembly, string typeFullName, string value) { var type = Assembly.Load(assembly).GetType(typeFullName); if (type == null) return null; return CoralConvert.Convert(value, type); } public static object GetListValue(string genericAssembly, string genericTypeName, List<XmlElement> elements) { var genericType = Assembly.Load(genericAssembly).GetType(genericTypeName); var type = typeof(List<>).MakeGenericType(genericType); dynamic list = Activator.CreateInstance(type, true); foreach (var element in elements) { list.Add(GetValue(element)); } return list; } public static object GetArrayValue(string elementAssembly, string elementTypeName, List<XmlElement> elements) { var elementType = Assembly.Load(elementAssembly).GetType(elementTypeName); dynamic list = Array.CreateInstance(elementType, elements.Count); for (int i = 0; i < elements.Count; i++) { list[i] = GetValue(elements[i]); } return list; } public static object GetDictionaryValue(string keyAssembly, string keyTypeName, string valueAssembly, string valueTypeName, List<XmlElement> elements) { var keyType = Assembly.Load(keyAssembly).GetType(keyTypeName); var valueType = Assembly.Load(valueAssembly).GetType(valueTypeName); var type = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); dynamic dict = Activator.CreateInstance(type, true); foreach (var element in elements) { dict.Add(GetValue(element["Key"]), GetValue(element["Value"])); } return dict; } public static object GetDynamicValue(List<XmlElement> elements) { var dict = new DynamicDictionary(); foreach (var element in elements) { dict.Dictionary.Add(GetValue(element["Key"]), GetValue(element["Value"])); } return dict; } public static object GetCustomValue(string assemblyFullName, string typeFullName, List<XmlElement> elements) { var type = Assembly.Load(assemblyFullName).GetType(typeFullName); if (type == null) return null; dynamic obj = Activator.CreateInstance(type, true); foreach (var element in elements) { var property = type.GetProperty(element.Name); object value; if (!CoralConvert.Convert(GetValue(element), property.PropertyType, out value)) continue; property.SetValue(obj, value); } return obj; } #endregion #region 創建XmlElement /// <summary> /// 創建xml元素 /// </summary> /// <param name="name"></param> /// <param name="data"></param> /// <param name="doc"></param> /// <returns></returns> public static XmlElement GetXml(string name, object data, XmlDocument doc) { if (data == null) return null; if (data.GetType().IsValueType || data is string) { return GetValueTypeXml(name, data, doc); } var list = data as IList; if (list != null) { return GetIListXml(name, list, doc); } var dict = data as IDictionary; if (dict != null) { return GetIDictionaryXml(name, dict, doc); } var dynamic = data as DynamicDictionary; if (dynamic != null) { return GetDynamicXml(name, dynamic, doc); } return GetCustomXml(name, data, doc); } /// <summary> /// 創建簡單類型的xml元素 /// </summary> /// <param name="name"></param> /// <param name="data"></param> /// <param name="doc"></param> /// <returns></returns> private static XmlElement GetValueTypeXml(string name, object data, XmlDocument doc) { if (data == null) return null; var element = doc.CreateElement(name); element.SetAttribute("Type", data.GetType().FullName); element.SetAttribute("Assembly", MetaDataManager.Assembly.GetAssemblySortName(data.GetType().Assembly)); element.SetAttribute("Classify", Classify.Sample.ToString()); element.InnerText = data.ToString(); return element; } /// <summary> /// 獲取列表類型的xml /// </summary> /// <param name="name"></param> /// <param name="datas"></param> /// <param name="doc"></param> /// <returns></returns> private static XmlElement GetIListXml(string name, object datas, XmlDocument doc) { if (datas == null) return null; var element = doc.CreateElement(name); if (datas.GetType().IsArray) { element.SetAttribute("Type", typeof(Array).FullName); element.SetAttribute("Classify", Classify.Array.ToString()); element.SetAttribute("ElementType", datas.GetType().GetElementType().FullName); element.SetAttribute("ElementAssembly", datas.GetType().GetElementType().Assembly.FullName); } else { element.SetAttribute("Type", typeof(IList).FullName); element.SetAttribute("Classify", Classify.List.ToString()); element.SetAttribute("GenericType", datas.GetType().GenericTypeArguments[0].FullName); element.SetAttribute("GenericAssembly", datas.GetType().GenericTypeArguments[0].Assembly.FullName); } foreach (var data in (IList)datas) { element.AppendChild(GetXml("Element", data, doc)); } return element; } /// <summary> /// 創建動態類型的xml /// </summary> /// <param name="name"></param> /// <param name="data"></param> /// <param name="doc"></param> /// <returns></returns> private static XmlElement GetDynamicXml(string name, dynamic data, XmlDocument doc) { if (data == null) return null; var element = doc.CreateElement(name); element.SetAttribute("Type", "dynamic"); element.SetAttribute("Classify", Classify.Dynamic.ToString()); foreach (DictionaryEntry item in (IDictionary)data.Dictionary) { var child = doc.CreateElement("Element"); child.AppendChild(GetXml("Key", item.Key ?? string.Empty, doc)); child.AppendChild(GetXml("Value", item.Value ?? string.Empty, doc)); element.AppendChild(child); } return element; } /// <summary> /// 創建字典類型的xml /// </summary> /// <param name="name"></param> /// <param name="datas"></param> /// <param name="doc"></param> /// <returns></returns> private static XmlElement GetIDictionaryXml(string name, object datas, XmlDocument doc) { if (datas == null) return null; var element = doc.CreateElement(name); element.SetAttribute("Type", typeof(IDictionary).FullName); element.SetAttribute("Classify", Classify.Dictionary.ToString()); element.SetAttribute("KeyGenericAssembly", datas.GetType().GetGenericArguments()[0].Assembly.FullName); element.SetAttribute("KeyGenericType", datas.GetType().GetGenericArguments()[0].FullName); element.SetAttribute("ValueGenericAssembly", datas.GetType().GetGenericArguments()[1].Assembly.FullName); element.SetAttribute("ValueGenericType", datas.GetType().GetGenericArguments()[1].FullName); foreach (DictionaryEntry data in (IDictionary)datas) { var child = doc.CreateElement("Element"); child.AppendChild(GetXml("Key", data.Key ?? string.Empty, doc)); child.AppendChild(GetXml("Value", data.Value ?? string.Empty, doc)); element.AppendChild(child); } return element; } /// <summary> /// 創建自定義類 /// </summary> /// <param name="name"></param> /// <param name="data"></param> /// <param name="doc"></param> /// <returns></returns> private static XmlElement GetCustomXml(string name, object data, XmlDocument doc) { if (data == null) return null; var element = doc.CreateElement(name); element.SetAttribute("Assembly",MetaDataManager.Assembly.GetAssemblySortName(data.GetType().Assembly)); element.SetAttribute("Type", data.GetType().FullName); element.SetAttribute("Classify", Classify.Custom.ToString()); data.GetType().GetProperties().ForEach(property => { var item = GetXml(property.Name, property.GetValue(data), doc); if (item != null) element.AppendChild(item); }); return element; } #endregion public enum Classify { Sample, List, Array, Dictionary, Dynamic, Custom, } public static List<XmlElement> GetChidlren(this XmlElement element) { return element.Cast<XmlElement>().ToList(); } }
核心思路就是遞歸,充分利用元數據
測試代碼
public class XmlConfig : IConfig { public string PathOrSourceString { get; set; } public dynamic Data { get; set; } } public interface ITestModel { string Name { get; set; } string DataType { get; set; } string Data { get; set; } } public class TestConfig { public ITestModel Model { get; set; } public List<ITestModel> List { get; set; } public Dictionary<string, ITestModel> Dict { get; set; } } public class TestModel: ITestModel { public string Name { get; set; } public string DataType { get; set; } public string Data { get; set; } public List<ITestModel> List { get; set; } public Dictionary<string, ITestModel> Dict { get; set; } } public class ConfigTest { public static void PerformanceTest() { var xmlconfig = new XmlConfig(); xmlconfig.PathOrSourceString = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "test1.Config"); #region list 類,字典測試 var testModel= new TestModel() { Name = "1", DataType = "1", Data = "1", List = new List<ITestModel> { new TestModel { Name = "2", DataType = "2", Data = "2", }, new TestModel { Name = "3", DataType = "3", Data = "3", }, }, Dict = new Dictionary<string, ITestModel> { {"4", new TestModel { Name = "4", DataType = "4", Data = "4", } }, {"5", new TestModel { Name = "5", DataType = "5", Data = "5", } }, } }; #endregion xmlconfig.Data = new TestConfig() { Model = testModel, Dict = new Dictionary<string, ITestModel>() { {"1",testModel }, {"2",testModel } }, List = new List<ITestModel> { testModel,testModel} }; #region 動態類型,類,list,字典總和測試 xmlconfig.Data = new DynamicDictionary(); xmlconfig.Data.Name = "Test1"; xmlconfig.Data.DataType = "Test1"; xmlconfig.Data.List = new List<TestModel> { new TestModel { Name = "2", DataType = "2", Data = "2", }, new TestModel { Name = "3", DataType = "3", Data = "3", }, }; xmlconfig.Data.Dict = new Dictionary<string, TestModel> { { "4", new TestModel { Name = "4", DataType = "4", Data = "4", } }, { "5", new TestModel { Name = "5", DataType = "5", Data = "5", } }, }; xmlconfig.Data.Other.Name = "Test1"; xmlconfig.Data.Other.DataType = "Test1"; #endregion xmlconfig.SaveToFile(); var data = new XmlConfig(); data.PathOrSourceString = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "test1.Config"); data.LoadFromFile(); }
配置文件為
<Data Type="dynamic" Classify="Dynamic"> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Name</Key> <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value> </Element> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">DataType</Key> <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value> </Element> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">List</Key> <Value Type="System.Collections.IList" Classify="List" GenericType="RunnerTest.Common.TestModel" GenericAssembly="RunnerTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <Element Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom"> <Name Type="System.String" Assembly="mscorlib" Classify="Sample">2</Name> <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">2</DataType> <Data Type="System.String" Assembly="mscorlib" Classify="Sample">2</Data> </Element> <Element Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom"> <Name Type="System.String" Assembly="mscorlib" Classify="Sample">3</Name> <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">3</DataType> <Data Type="System.String" Assembly="mscorlib" Classify="Sample">3</Data> </Element> </Value> </Element> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Dict</Key> <Value Type="System.Collections.IDictionary" Classify="Dictionary" KeyGenericAssembly="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" KeyGenericType="System.String" ValueGenericAssembly="RunnerTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" ValueGenericType="RunnerTest.Common.TestModel"> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">4</Key> <Value Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom"> <Name Type="System.String" Assembly="mscorlib" Classify="Sample">4</Name> <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">4</DataType> <Data Type="System.String" Assembly="mscorlib" Classify="Sample">4</Data> </Value> </Element> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">5</Key> <Value Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom"> <Name Type="System.String" Assembly="mscorlib" Classify="Sample">5</Name> <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">5</DataType> <Data Type="System.String" Assembly="mscorlib" Classify="Sample">5</Data> </Value> </Element> </Value> </Element> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Other</Key> <Value Type="dynamic" Classify="Dynamic"> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Name</Key> <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value> </Element> <Element> <Key Type="System.String" Assembly="mscorlib" Classify="Sample">DataType</Key> <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value> </Element> </Value> </Element> </Data>
說明
最大的用處,你拿到一個對象未知的對象,並不需要知道他的實際類型,就可以進行持久化,並且讀取出來之後能夠還原到原始類型。
實現這部分我覺得在於以下幾個點
1. 對元數據的充分理解
2. 對xml結構的充分理解
3. 需要一點寫演算法的能力
我覺得代碼本身並不複雜,只要耐心單步調試都能看懂。
當然這個是有一定限制的:
1. 可讀性不強,所以在需要從文件進行修改配置比較麻煩
2.不可跨系統,文件中類型從程式集載入不到時就會出錯
3.性能不高.性能敏感的部分不太適合
所以這部分功能需要結合業務場景使用,在我這裡,包含作業調度系統,統計系統,介面測試工具中有使用.
這其實特別想WSDL的Soap協議,文件中既包含元數據的說明,又包含數據本身.真個元數據變成也是一個做設計時候一個重要思想。