一般我们代码生成了之后,就不会动了。而可动的参数一般写在配置文件里面。配置语言的格式一般有一下几种
优点 | 缺点 | |
---|---|---|
xml | 扩展性强,歧义性小 | 对于人来说过于冗长 |
Json | 可读性强 | 无法添加注释 |
yaml | 可读取强 | 缩进地狱,手动修改时极其容易出现问题 |
首先Xml的文件不是我们自己生成的,而是机器自己主动生成的。因为我们一般的使用逻辑是
对于我们配置人员来说,修改的部分是比较少的,而且由于其极强的拓展性,可以添加许多的注释。所以我打算使用Xml来生成对应的配置文档。而Json由于其修改时容易出错和扩展性的问题,我暂时就不用了。
微软其实已经帮我们封装好了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序列化/反序列化参考
///
/// 重命名根节点
///
[XmlRoot("RootTest")]
public class MyConfigService
{
///
/// 重命名,从Name变成extra
///
[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; }
///
/// 以Default属性的形式加载到根节点上面
///
[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;
}
///
/// 创建文件
///
public void Init()
{
if(IsFileExist)
{
try
{
Read();
}
catch (Exception ex)
{
ShowMsg(ex.ToString());
throw new Exception("文件读取失败!请确认是否配置文件格式是否正确");
}
}
else
{
Write();
}
}
///
/// 覆盖文件
///
public void ReInit()
{
ShowMsg("正在覆盖配置文件:" + FullPath);
Write();
}
///
/// 写入配置类
///
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();
}
}
///
/// 读取配置类
///
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);
}
}
///
/// 备份
///
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;
}
///
/// 实例化
///
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);
}
}
///
/// 备份
///
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;
}
///
/// 实例化
///
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;
}
///
/// 创建文件
///
public void Init()
{
if (IsFileExist)
{
try
{
Read();
}
catch (Exception ex)
{
ShowMsg(ex.ToString());
throw new Exception("文件读取失败!请确认是否配置文件格式是否正确");
}
}
else
{
Write();
}
}
///
/// 覆盖文件
///
public void ReInit()
{
ShowMsg("正在覆盖配置文件:" + FullPath);
Write();
}
///
/// 写入配置类
///
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();
}
}
///
/// 读取配置类
///
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备份文件,我们平时就修改主要文件的配置即可。如果主要文件损坏,那就备份文件补上。但是这个是主要文件损坏的情况,如果主要文件没损坏,是参数设置错了呢?那我们可以自动生成按照时间戳的备份文件,一次存多个。
为了安全考虑的方式是没有上限的。这里就不展开说明了,这里已经写好一个基本的设置文件自动保存,和设置文件自动备份回档的功能,如果想要更高的安全基本可以自己在我的代码上面继续封装。
还有备份文件可以当做缓存文件一样来使用,但是这个是明文存储的。可以自己手动加密一下,反正加密和解密的方法也有很多。可以自己琢磨一下。