上一篇文章直接就被移除首頁了,這次來點大家都能懂的干貨.
需求
之前做一個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協議,文件中既包含元數據的說明,又包含數據本身.真個元數據變成也是一個做設計時候一個重要思想。
