文章目录
- 前言
- 选择Xml
- 简单的Xml使用
- 测试用例
- 简单的写
- 简单的读
- 简单的生成配置
- 修改配置类
- 测试用例
- 运行结果对比
- 代码逻辑封装
- 逻辑示意
- 封装好的代码
- 测试生成
- 配置文件格式错误测试
- 使用默认值覆盖来解决问题
- 配置文件人为修改错误如何解决
- 解决方案
- 代码
- 测试用例
- 运行结果
- 代码封装总结
- 总结
前言
一般我们代码生成了之后,就不会动了。而可动的参数一般写在配置文件里面。配置语言的格式一般有一下几种
优点 | 缺点 | |
---|---|---|
xml | 扩展性强,歧义性小 | 对于人来说过于冗长 |
Json | 可读性强 | 无法添加注释 |
yaml | 可读取强 | 缩进地狱,手动修改时极其容易出现问题 |
选择Xml
首先Xml的文件不是我们自己生成的,而是机器自己主动生成的。因为我们一般的使用逻辑是
对于我们配置人员来说,修改的部分是比较少的,而且由于其极强的拓展性,可以添加许多的注释。所以我打算使用Xml来生成对应的配置文档。而Json由于其修改时容易出错和扩展性的问题,我暂时就不用了。
简单的Xml使用
微软其实已经帮我们封装好了Xml的操控类。这里直接用序列化对象就行了
微软 XML 序列化示例。
测试用例
public class MyConfigService
{
public string Name { get; set; }
public string Description { get; set; }
public int Id { get; set; }
public bool IsEnabled { get; set; }
public enum MyKeys { Apple,Banana,Pear}
public MyKeys SettingKey { get; set; }
public MyConfigService() { }
}
简单的写
MyConfigService myConfigService = new MyConfigService() {
Name = "坤坤",
Description = "偶像练习生",
Id = 114514,
IsEnabled = false,
SettingKey = MyConfigService.MyKeys.Pear
};
var xmlHelper = new XmlSerializer(typeof(MyConfigService));
StreamWriter xmlWriter = new StreamWriter("MyConfig.xml");
xmlHelper.Serialize(xmlWriter, myConfigService);
xmlWriter.Close();
简单的读
static void Main(string[] args)
{
MyConfigService myConfigService = new MyConfigService()
{
Name = "坤坤",
Description = "偶像练习生",
Id = 114514,
IsEnabled = false,
SettingKey = MyConfigService.MyKeys.Pear
};
var xmlHelper = new XmlSerializer(typeof(MyConfigService));
//StreamWriter xmlWriter = new StreamWriter("MyConfig.xml");
//xmlHelper.Serialize(xmlWriter, myConfigService);
//xmlWriter.Close();
StreamReader xmlReader = new StreamReader("MyConfig.xml");
var res = xmlHelper.Deserialize(xmlReader);
Console.WriteLine(JsonConvert.SerializeObject(res));
Console.WriteLine("运行完成!");
Console.ReadKey();
}
简单的生成配置
C# XML序列化/反序列化参考
修改配置类
/// <summary>
/// 重命名根节点
/// </summary>
[XmlRoot("RootTest")]
public class MyConfigService
{
/// <summary>
/// 重命名,从Name变成extra
/// </summary>
[XmlElement("extra")]
public string Name { get; set; }
public string Description { get; set; }
public int Id { get; set; }
public bool IsEnabled { get; set; }
public enum MyKeys { Apple,Banana,Pear}
public MyKeys SettingKey { get; set; }
/// <summary>
/// 以Default属性的形式加载到根节点上面
/// </summary>
[XmlAttribute()]
public string Default = "描述";
public MyConfigService() { }
}
测试用例
static void Main(string[] args)
{
MyConfigService myConfigService = new MyConfigService()
{
Name = "坤坤",
Description = "偶像练习生",
Id = 114514,
IsEnabled = false,
SettingKey = MyConfigService.MyKeys.Pear
};
var xmlHelper = new XmlSerializer(typeof(MyConfigService));
StreamWriter xmlWriter = new StreamWriter("MyConfig.xml");
//去掉烦人的命名空间
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
xmlHelper.Serialize(xmlWriter, myConfigService,ns);
xmlWriter.Close();
//StreamReader xmlReader = new StreamReader("MyConfig.xml");
//var res = xmlHelper.Deserialize(xmlReader);
//Console.WriteLine(JsonConvert.SerializeObject(res));
Console.WriteLine("运行完成!");
Console.ReadKey();
}
运行结果对比
代码逻辑封装
逻辑示意
封装好的代码
public class MyXmlConfigHelper<T>
{
public T Setting { get; set; }
public string FileName { get; set; } = "MyConfig.xml";
public string DirectoryPath
{
get
{
var regex = new Regex(@"\\(\w+)\.(\w+)$");
return regex.Split(FullPath)[0];
}
}
public string DebugPath { get => Directory.GetCurrentDirectory(); }
public string FullPath { get => DebugPath + "\\" + FileName; }
public bool IsFileExist { get => File.Exists(FullPath); }
public bool IsDirectoryExist { get => Directory.Exists(DirectoryPath); }
public Action<string> ShowMsg { get; set; } = (msg)=>Console.WriteLine(msg);
public MyXmlConfigHelper()
{
}
public MyXmlConfigHelper(string filename)
{
FileName = filename;
if (!IsDirectoryExist)
{
DirectoryInfo directoryInfo = new DirectoryInfo(DirectoryPath);
directoryInfo.Create();
}
}
public MyXmlConfigHelper(T setting ,string filename):this(filename)
{
Setting = setting;
}
/// <summary>
/// 创建文件
/// </summary>
public void Init()
{
if(IsFileExist)
{
try
{
Read();
}
catch (Exception ex)
{
ShowMsg(ex.ToString());
throw new Exception("文件读取失败!请确认是否配置文件格式是否正确");
}
}
else
{
Write();
}
}
/// <summary>
/// 覆盖文件
/// </summary>
public void ReInit()
{
ShowMsg("正在覆盖配置文件:" + FullPath);
Write();
}
/// <summary>
/// 写入配置类
/// </summary>
private void Write()
{
ShowMsg("正在生成配置文件:" + FullPath);
var xmlHelper = new XmlSerializer(typeof(T));
using (StreamWriter xmlWriter = new StreamWriter(FullPath))
{
//去掉烦人的命名空间
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
xmlHelper.Serialize(xmlWriter, Setting, ns);
xmlWriter.Close();
}
}
/// <summary>
/// 读取配置类
/// </summary>
private void Read()
{
ShowMsg("正在读取配置文件:"+FullPath);
var xmlHelper = new XmlSerializer(typeof(T));
using (StreamReader xmlReader = new StreamReader(FullPath))
{
Setting = (T)xmlHelper.Deserialize(xmlReader);
xmlReader.Close();
}
}
}
测试生成
static void Main(string[] args)
{
var config = new MyConfigService() {
Name = "小坤",
Description="爱坤",
Default = "鲲鲲",
SettingKey = MyConfigService.MyKeys.Banana,
Id = 80086,
IsEnabled = true,
};
var xmlHelper = new MyXmlConfigHelper<MyConfigService>(config, "Config\\MyConfig.xml");
xmlHelper.Init();
Console.WriteLine("运行完成!");
Console.ReadKey();
}
我还做了判断,如果不存在,则生成默认,如果存在,则读取的判断
配置文件格式错误测试
使用默认值覆盖来解决问题
static void Main(string[] args)
{
var config = new MyConfigService() {
Name = "小坤",
Description="爱坤",
Default = "鲲鲲",
SettingKey = MyConfigService.MyKeys.Banana,
Id = 80086,
IsEnabled = true,
};
var xmlHelper = new MyXmlConfigHelper<MyConfigService>(config, "Config\\MyConfig.xml");
try
{
xmlHelper.Init();
}
catch (Exception ex)
{
//如果出错,则使用默认值覆盖
Console.WriteLine(ex.ToString());
xmlHelper.ReInit();
}
Console.WriteLine("运行完成!");
Console.ReadKey();
}
配置文件人为修改错误如何解决
解决方案有以下几种
- 不解决,使用默认值,一直到人为修改回去
- 手动解决,但是现场人员不一定了解配置信息
- 重新生成覆盖,但是这样会丢失以前配置的数据
- 从缓存数据库中读取上传成功运行的代码,回复到最初的状态
理论上来说,第4个是最好的,因为我们现场人员就算修改出现问题了,也能回滚到程序之前的配置。但是C# 默认是没有缓存这个东西的。缓存是需要存在一个地方。我个人认为最好的存储中介就是Sqlite数据库。Sqlite本身体积小,性能强,不需要安装。对于1G以下,100万条以下的数据最好的存储中介。
挖个坑,后面研究一下基于Sqlite的缓存数据库
解决方案
生成两个配置文件,一个是主配置文件,一个是备份配置文件。
代码
public class MyXmlConfigAutoHelper<T>
{
public T Setting { get; set; }
public string FileName { get; set; } = "MyConfig.xml";
public string BackupName
{
get
{
var regex = new Regex(@"(\w+)\.(\w+)$");
var filename = regex.Match(FileName).Value;
var backName = (new Regex(@"\.(\w+)$")).Split(filename)[0];
var newBackName = backName + "_back";
return (new Regex(backName)).Replace(FileName, newBackName);
}
}
/// <summary>
/// 备份
/// </summary>
private MyXmlConfigHelper<T> backupXml { get; set; }
private MyXmlConfigHelper<T> settingXml { get; set; }
public MyXmlConfigAutoHelper()
{
}
public MyXmlConfigAutoHelper(string fileName)
{
FileName = fileName;
}
public MyXmlConfigAutoHelper(string fileName, T setting)
{
Setting = setting;
FileName = fileName;
}
/// <summary>
/// 实例化
/// </summary>
public void AutoInit()
{
settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
//如果备份也损坏了,就GG了
var settingFlag = true;
var backupFlag = true;
try
{
settingXml.Init();
}
catch (Exception ex)
{
Console.WriteLine("主文件读取失败");
Console.WriteLine(ex.Message);
settingFlag = false;
}
try
{
backupXml.Init();
}
catch (Exception ex)
{
Console.WriteLine("备份文件读取失败");
Console.WriteLine(ex.Message);
backupFlag = false;
}
Console.WriteLine($"文件完整性:setting[{settingFlag},backup[{backupFlag}]]");
if (!backupFlag && !settingFlag)
{
Console.WriteLine("主要和备份文件完全破损,默认值覆盖");
backupXml.ReInit();
settingXml.ReInit();
}
else if (!backupFlag)
{
Console.WriteLine("备份文件完全破损,主要文件覆盖");
backupXml.Setting = settingXml.Setting;
backupXml.ReInit();
}
else if (!settingFlag)
{
Console.WriteLine("主要文件完全破损,备份文件覆盖");
settingXml.Setting = backupXml.Setting;
settingXml.ReInit();
}
else
{
Console.WriteLine("主要和备份文件正常,主要文件覆盖备份文件");
backupXml.Setting = settingXml.Setting;
backupXml.ReInit();
}
Setting = settingXml.Setting;
}
public void ReInit()
{
settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
settingXml.ReInit();
backupXml.ReInit();
}
}
测试用例
static void Main(string[] args)
{
var config = new MyConfigService() {
Name = "小坤",
Description="爱坤",
Default = "鲲鲲",
SettingKey = MyConfigService.MyKeys.Banana,
Id = 80086,
IsEnabled = true,
};
var xmlAutoHelper = new MyXmlConfigAutoHelper<MyConfigService>("resource\\Myconfig.xml", config);
xmlAutoHelper.AutoInit();
//Console.WriteLine(xmlAutoHelper.BackupName);
Console.WriteLine("运行完成!");
Console.ReadKey();
}
运行结果
由于测试步骤过于复杂,情况比较多,这里就不放截图了。简单来说就是尽可能的使用已有的数据进行还原,如果两个文件都损坏直接使用默认值替换
代码封装总结
public class MyXmlConfigAutoHelper<T>
{
public T Setting { get; set; }
public string FileName { get; set; } = "MyConfig.xml";
public string BackupName
{
get
{
var regex = new Regex(@"(\w+)\.(\w+)$");
var filename = regex.Match(FileName).Value;
var backName = (new Regex(@"\.(\w+)$")).Split(filename)[0];
var newBackName = backName + "_back";
return (new Regex(backName)).Replace(FileName, newBackName);
}
}
/// <summary>
/// 备份
/// </summary>
private MyXmlConfigHelper<T> backupXml { get; set; }
private MyXmlConfigHelper<T> settingXml { get; set; }
public MyXmlConfigAutoHelper()
{
}
public MyXmlConfigAutoHelper(string fileName)
{
FileName = fileName;
}
public MyXmlConfigAutoHelper(string fileName, T setting)
{
Setting = setting;
FileName = fileName;
}
/// <summary>
/// 实例化
/// </summary>
public void AutoInit()
{
settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
//如果备份也损坏了,就GG了
var settingFlag = true;
var backupFlag = true;
try
{
settingXml.Init();
}
catch (Exception ex)
{
Console.WriteLine("主文件读取失败");
Console.WriteLine(ex.Message);
settingFlag = false;
}
try
{
backupXml.Init();
}
catch (Exception ex)
{
Console.WriteLine("备份文件读取失败");
Console.WriteLine(ex.Message);
backupFlag = false;
}
Console.WriteLine($"文件完整性:setting[{settingFlag},backup[{backupFlag}]]");
if (!backupFlag && !settingFlag)
{
Console.WriteLine("主要和备份文件完全破损,默认值覆盖");
backupXml.ReInit();
settingXml.ReInit();
}
else if (!backupFlag)
{
Console.WriteLine("备份文件完全破损,主要文件覆盖");
backupXml.Setting = settingXml.Setting;
backupXml.ReInit();
}
else if (!settingFlag)
{
Console.WriteLine("主要文件完全破损,备份文件覆盖");
settingXml.Setting = backupXml.Setting;
settingXml.ReInit();
}
else
{
Console.WriteLine("主要和备份文件正常,主要文件覆盖备份文件");
backupXml.Setting = settingXml.Setting;
backupXml.ReInit();
}
Setting = settingXml.Setting;
}
public void ReInit()
{
settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
settingXml.ReInit();
backupXml.ReInit();
}
}
public class MyXmlConfigHelper<T>
{
public T Setting { get; set; }
public string FileName { get; set; } = "MyConfig.xml";
public string DirectoryPath
{
get
{
var regex = new Regex(@"\\(\w+)\.(\w+)$");
return regex.Split(FullPath)[0];
}
}
public string DebugPath { get => Directory.GetCurrentDirectory(); }
public string FullPath { get => DebugPath + "\\" + FileName; }
public bool IsFileExist { get => File.Exists(FullPath); }
public bool IsDirectoryExist { get => Directory.Exists(DirectoryPath); }
public Action<string> ShowMsg { get; set; } = (msg) => Console.WriteLine(msg);
public MyXmlConfigHelper()
{
}
public MyXmlConfigHelper(string filename)
{
FileName = filename;
if (!IsDirectoryExist)
{
DirectoryInfo directoryInfo = new DirectoryInfo(DirectoryPath);
directoryInfo.Create();
}
}
public MyXmlConfigHelper(T setting, string filename) : this(filename)
{
Setting = setting;
}
/// <summary>
/// 创建文件
/// </summary>
public void Init()
{
if (IsFileExist)
{
try
{
Read();
}
catch (Exception ex)
{
ShowMsg(ex.ToString());
throw new Exception("文件读取失败!请确认是否配置文件格式是否正确");
}
}
else
{
Write();
}
}
/// <summary>
/// 覆盖文件
/// </summary>
public void ReInit()
{
ShowMsg("正在覆盖配置文件:" + FullPath);
Write();
}
/// <summary>
/// 写入配置类
/// </summary>
public void Write()
{
ShowMsg("正在生成配置文件:" + FullPath);
var xmlHelper = new XmlSerializer(typeof(T));
using (StreamWriter xmlWriter = new StreamWriter(FullPath))
{
//去掉烦人的命名空间
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
xmlHelper.Serialize(xmlWriter, Setting, ns);
xmlWriter.Close();
}
}
/// <summary>
/// 读取配置类
/// </summary>
public void Read()
{
ShowMsg("正在读取配置文件:" + FullPath);
var xmlHelper = new XmlSerializer(typeof(T));
using (StreamReader xmlReader = new StreamReader(FullPath))
{
Setting = (T)xmlHelper.Deserialize(xmlReader);
xmlReader.Close();
}
}
}
总结
我这里最后加了个back备份文件,我们平时就修改主要文件的配置即可。如果主要文件损坏,那就备份文件补上。但是这个是主要文件损坏的情况,如果主要文件没损坏,是参数设置错了呢?那我们可以自动生成按照时间戳的备份文件,一次存多个。
- 主要文件
- 备份文件-2024-1-6 17:37:20
- 备份文件-2024-1-6 17:37:30
为了安全考虑的方式是没有上限的。这里就不展开说明了,这里已经写好一个基本的设置文件自动保存,和设置文件自动备份回档的功能,如果想要更高的安全基本可以自己在我的代码上面继续封装。
还有备份文件可以当做缓存文件一样来使用,但是这个是明文存储的。可以自己手动加密一下,反正加密和解密的方法也有很多。可以自己琢磨一下。