C# 自定义配置文件序列化生成+文件格式错误自动回档

文章目录

  • 前言
  • 选择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();

C# 自定义配置文件序列化生成+文件格式错误自动回档_第1张图片

简单的读

     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# 自定义配置文件序列化生成+文件格式错误自动回档_第2张图片

C# 自定义配置文件序列化生成+文件格式错误自动回档_第3张图片

简单的生成配置

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();
  }

运行结果对比

C# 自定义配置文件序列化生成+文件格式错误自动回档_第4张图片

代码逻辑封装

逻辑示意

xml序列化Helper
默认生成
默认读取
读取解析出错覆盖
文件路径是否存在确认

封装好的代码

   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();
    }

C# 自定义配置文件序列化生成+文件格式错误自动回档_第5张图片
我还做了判断,如果不存在,则生成默认,如果存在,则读取的判断

配置文件格式错误测试

C# 自定义配置文件序列化生成+文件格式错误自动回档_第6张图片
C# 自定义配置文件序列化生成+文件格式错误自动回档_第7张图片
C# 自定义配置文件序列化生成+文件格式错误自动回档_第8张图片

使用默认值覆盖来解决问题

    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();
    }

C# 自定义配置文件序列化生成+文件格式错误自动回档_第9张图片

配置文件人为修改错误如何解决

解决方案有以下几种

  • 不解决,使用默认值,一直到人为修改回去
  • 手动解决,但是现场人员不一定了解配置信息
  • 重新生成覆盖,但是这样会丢失以前配置的数据
  • 从缓存数据库中读取上传成功运行的代码,回复到最初的状态

理论上来说,第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备份文件,我们平时就修改主要文件的配置即可。如果主要文件损坏,那就备份文件补上。但是这个是主要文件损坏的情况,如果主要文件没损坏,是参数设置错了呢?那我们可以自动生成按照时间戳的备份文件,一次存多个。

  • 主要文件
  • 备份文件-2024-1-6 17:37:20
  • 备份文件-2024-1-6 17:37:30

为了安全考虑的方式是没有上限的。这里就不展开说明了,这里已经写好一个基本的设置文件自动保存,和设置文件自动备份回档的功能,如果想要更高的安全基本可以自己在我的代码上面继续封装。

还有备份文件可以当做缓存文件一样来使用,但是这个是明文存储的。可以自己手动加密一下,反正加密和解密的方法也有很多。可以自己琢磨一下。

你可能感兴趣的:(C#,c#,开发语言)