上一篇文章直接就被移除首页了,这次来点大家都能懂的干货.
需求
之前做一个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;