Home > Article > Backend Development > Detailed explanation of various methods of reading and writing config files in .net
Today we will talk about various methods of reading and writing config files in .net. In this blog, I will introduce the reading and writing operations of various configuration files. Because the content is relatively intuitive, there are not too many empty theories, only real demonstration codes, and the purpose is only to reproduce various scenarios in actual combat development. Hope you all like it.
Usually, during the .NET development process, we come into contact with two types of configuration files: config files and xml files. Today's blog example will also introduce various operations of these two categories of configuration files. In the config file, I will mainly demonstrate how to create your own custom configuration node, rather than introduce how to use appSetting.
Please note: The config file mentioned in this article specifically refers to app.config or web.config, not a general XML file. In this type of configuration file, since .net framework has defined some configuration nodes for them, we cannot simply read and write it through serialization.
config file - custom configuration node
Why do you need a custom configuration node?
Indeed, many people use appSetting directly when using config files, stuffing all the configuration parameters there. Although this is good, if there are too many parameters, the shortcomings of this approach will be obviously exposed. Come out: The configuration parameter items in appSetting can only be accessed by key name, and cannot support complex hierarchical nodes or strong types. And since they only use this set, you will find that completely irrelevant parameters must also be placed in Together!
Want to get rid of this trouble? Custom configuration nodes would be a feasible way to solve this problem.
First, let’s take a look at how to add a custom configuration node in app.config or web.config. In this blog, I will introduce 4 ways to customize the node configuration. The final configuration file is as follows:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="MySection111" type="RwConfigDemo.MySection1, RwConfigDemo" /> <section name="MySection222" type="RwConfigDemo.MySection2, RwConfigDemo" /> <section name="MySection333" type="RwConfigDemo.MySection3, RwConfigDemo" /> <section name="MySection444" type="RwConfigDemo.MySection4, RwConfigDemo" /> </configSections> <MySection111 username="fish-li" url="http://www.jb51.net/"></MySection111> <MySection222> <users username="fish" password="liqifeng"></users> </MySection222> <MySection444> <add key="aa" value="11111"></add> <add key="bb" value="22222"></add> <add key="cc" value="33333"></add> </MySection444> <MySection333> <Command1> <![CDATA[ create procedure ChangeProductQuantity( @ProductID int, @Quantity int ) as update Products set Quantity = @Quantity where ProductID = @ProductID; ]]> </Command1> <Command2> <![CDATA[ create procedure DeleteCategory( @CategoryID int ) as delete from Categories where CategoryID = @CategoryID; ]]> </Command2> </MySection333> </configuration>
At the same time, I also provide all the sample code (available for download at the end of the article). The interface of the demo program is as follows :
config file - Property
Let’s first look at the simplest custom node. Each configuration value exists in the form of an attribute:
<MySection111 username="fish-li" url="http://www.php.cn/"></MySection111>
The implementation code is as follows:
public class MySection1 : ConfigurationSection { [ConfigurationProperty("username", IsRequired = true)] public string UserName { get { return this["username"].ToString(); } set { this["username"] = value; } } [ConfigurationProperty("url", IsRequired = true)] public string Url { get { return this["url"].ToString(); } set { this["url"] = value; } } }
Summary:
1. Define a class with ConfigurationSection as the base class, and add [ConfigurationProperty] to each attribute. The name string passed in the constructor of ConfigurationProperty will be used in the config file to represent the attribute name of each parameter.
2. To read and write the value of an attribute, you need to call this[], which is saved by the base class. Please do not design a Field to save it yourself.
3. In order to use the configuration node to be parsed, you need to register it in f0c345cb8609a7447fbc7220c375eedc: 1487e1d57d56fba706ac1462b7896715, and pay attention to the name=" MySection111" should correspond to 5a6cbd7e02537b407b228cfe2b9d6d79.
Note: Next, we will introduce three other configuration nodes. Although they are a little more complicated, some basic things are the same as this node, so I will not repeat the explanation later.
config file - Element
Let’s look at something more complicated. Each configuration item exists in the form of XML element:
<MySection222> <users username="fish" password="liqifeng"></users> </MySection222>
The implementation code is as follows:
public class MySection2 : ConfigurationSection { [ConfigurationProperty("users", IsRequired = true)] public MySectionElement Users { get { return (MySectionElement)this["users"]; } } } public class MySectionElement : ConfigurationElement { [ConfigurationProperty("username", IsRequired = true)] public string UserName { get { return this["username"].ToString(); } set { this["username"] = value; } } [ConfigurationProperty("password", IsRequired = true)] public string Password { get { return this["password"].ToString(); } set { this["password"] = value; } } }
Summary:
1 . Customize a class, with ConfigurationSection as the base class. In addition to adding [ConfigurationProperty] to each attribute
2. The type is also customized, and the specific configuration properties are written in the inheritance class of ConfigurationElement.
config file - CDATA
Sometimes the configuration parameters contain long text, such as a SQL script or an HTML code, then a CDATA node is needed. Suppose you want to implement a configuration, including two SQL scripts:
<MySection333> <Command1> <![CDATA[ create procedure ChangeProductQuantity( @ProductID int, @Quantity int ) as update Products set Quantity = @Quantity where ProductID = @ProductID; ]]> </Command1> <Command2> <![CDATA[ create procedure DeleteCategory( @CategoryID int ) as delete from Categories where CategoryID = @CategoryID; ]]> </Command2> </MySection333>
The implementation code is as follows:
public class MySection3 : ConfigurationSection { [ConfigurationProperty("Command1", IsRequired = true)] public MyTextElement Command1 { get { return (MyTextElement)this["Command1"]; } } [ConfigurationProperty("Command2", IsRequired = true)] public MyTextElement Command2 { get { return (MyTextElement)this["Command2"]; } } } public class MyTextElement : ConfigurationElement { protected override void DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey) { CommandText = reader.ReadElementContentAs(typeof(string), null) as string; } protected override bool SerializeElement(System.Xml.XmlWriter writer, bool serializeCollectionKey) { if( writer != null ) writer.WriteCData(CommandText); return true; } [ConfigurationProperty("data", IsRequired = false)] public string CommandText { get { return this["data"].ToString(); } set { this["data"] = value; } } }
Summary:
1. For implementation, you can generally refer to MySection2,
2. We control how to read and write XML for each ConfigurationElement, which means overloading the methods SerializeElement and DeserializeElement
config file - Collection
<MySection444> <add key="aa" value="11111"></add> <add key="bb" value="22222"></add> <add key="cc" value="33333"></add> </MySection444>
This similar configuration method is in ASP.NET's HttpHandler and HttpModule So common, would you like to know how to implement them? The code is as follows:
Summary:
1. Create a derived class that inherits from ConfigurationElement for each parameter item in the collection, please refer to MySection1
2. Create a collection class that inherits from ConfigurationElementCollection for the collection, specifically in The main implementation is to call the base class method.
3. When creating the inherited class of ConfigurationSection, just create a property representing the collection. Pay attention to the parameters of [ConfigurationProperty].
config file - reading and writing
I introduced the four custom configuration node implementation classes one by one. Let’s take a look at how to read and write them.
Read configuration parameters:
MySection1 mySectioin1 = (MySection1)ConfigurationManager.GetSection("MySection111"); txtUsername1.Text = mySectioin1.UserName; txtUrl1.Text = mySectioin1.Url; MySection2 mySectioin2 = (MySection2)ConfigurationManager.GetSection("MySection222"); txtUsername2.Text = mySectioin2.Users.UserName; txtUrl2.Text = mySectioin2.Users.Password; MySection3 mySection3 = (MySection3)ConfigurationManager.GetSection("MySection333"); txtCommand1.Text = mySection3.Command1.CommandText.Trim(); txtCommand2.Text = mySection3.Command2.CommandText.Trim(); MySection4 mySection4 = (MySection4)ConfigurationManager.GetSection("MySection444"); txtKeyValues.Text = string.Join("\r\n", (from kv in mySection4.KeyValues.Cast<MyKeyValueSetting>() let s = string.Format("{0}={1}", kv.Key, kv.Value) select s).ToArray());
Summary: When reading custom nodes, we need to call ConfigurationManager.GetSection() to get the configuration node and convert it into the configuration node class we defined, and then we can follow Strongly typed way to access.
写配置文件:
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); MySection1 mySectioin1 = config.GetSection("MySection111") as MySection1; mySectioin1.UserName = txtUsername1.Text.Trim(); mySectioin1.Url = txtUrl1.Text.Trim(); MySection2 mySection2 = config.GetSection("MySection222") as MySection2; mySection2.Users.UserName = txtUsername2.Text.Trim(); mySection2.Users.Password = txtUrl2.Text.Trim(); MySection3 mySection3 = config.GetSection("MySection333") as MySection3; mySection3.Command1.CommandText = txtCommand1.Text.Trim(); mySection3.Command2.CommandText = txtCommand2.Text.Trim(); MySection4 mySection4 = config.GetSection("MySection444") as MySection4; mySection4.KeyValues.Clear(); (from s in txtKeyValues.Lines let p = s.IndexOf('=') where p > 0 select new MyKeyValueSetting { Key = s.Substring(0, p), Value = s.Substring(p + 1) } ).ToList() .ForEach(kv => mySection4.KeyValues.Add(kv)); config.Save();
小结:在修改配置节点前,我们需要调用ConfigurationManager.OpenExeConfiguration(),然后调用config.GetSection()在得到节点后,转成我们定义的节点类型, 然后就可以按照强类型的方式来修改我们定义的各参数项,最后调用config.Save();即可。
注意:
1. .net为了优化配置节点的读取操作,会将数据缓存起来,如果希望使用修改后的结果生效,您还需要调用ConfigurationManager.RefreshSection(".....")
2. 如果是修改web.config,则需要使用 WebConfigurationManager
读写 .net framework中已经定义的节点
前面一直在演示自定义的节点,那么如何读取.net framework中已经定义的节点呢?
假如我想读取下面配置节点中的发件人。
<system.net> <mailSettings> <smtp from="Fish.Q.Li@newegg.com"> <network /> </smtp> </mailSettings> </system.net>
读取配置参数:
SmtpSection section = ConfigurationManager.GetSection("system.net/mailSettings/smtp") as SmtpSection; labMailFrom.Text = "Mail From: " + section.From;
写配置文件:
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); SmtpSection section = config.GetSection("system.net/mailSettings/smtp") as SmtpSection; section.From = "Fish.Q.Li@newegg.com2"; config.Save();
xml配置文件
前面演示在config文件中创建自定义配置节点的方法,那些方法也只适合在app.config或者web.config中,如果您的配置参数较多, 或者打算将一些数据以配置文件的形式单独保存,那么,直接读写整个XML将会更方便。 比如:我有一个实体类,我想将它保存在XML文件中,有可能是多条记录,也可能是一条。
这次我来反过来说,假如我们先定义了XML的结构,是下面这个样子的,那么我将怎么做呢?
<?xml version="1.0" encoding="utf-8"?> <ArrayOfMyCommand xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <MyCommand Name="InsretCustomer" Database="MyTestDb"> <Parameters> <Parameter Name="Name" Type="DbType.String" /> <Parameter Name="Address" Type="DbType.String" /> </Parameters> <CommandText>insret into .....</CommandText> </MyCommand> </ArrayOfMyCommand>
对于上面的这段XML结构,我们可以在C#中先定义下面的类,然后通过序列化及反序列化的方式来实现对它的读写。
C#类的定义如下:
public class MyCommand { [XmlAttribute("Name")] public string CommandName; [XmlAttribute] public string Database; [XmlArrayItem("Parameter")] public List<MyCommandParameter> Parameters = new List<MyCommandParameter>(); [XmlElement] public string CommandText; } public class MyCommandParameter { [XmlAttribute("Name")] public string ParamName; [XmlAttribute("Type")] public string ParamType; }
有了这二个C#类,读写这段XML就非常容易了。以下就是相应的读写代码:
private void btnReadXml_Click(object sender, EventArgs e) { btnWriteXml_Click(null, null); List<MyCommand> list = XmlHelper.XmlDeserializeFromFile<List<MyCommand>>(XmlFileName, Encoding.UTF8); if( list.Count > 0 ) MessageBox.Show(list[0].CommandName + ": " + list[0].CommandText, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } private void btnWriteXml_Click(object sender, EventArgs e) { MyCommand command = new MyCommand(); command.CommandName = "InsretCustomer"; command.Database = "MyTestDb"; command.CommandText = "insret into ....."; command.Parameters.Add(new MyCommandParameter { ParamName = "Name", ParamType = "DbType.String" }); command.Parameters.Add(new MyCommandParameter { ParamName = "Address", ParamType = "DbType.String" }); List<MyCommand> list = new List<MyCommand>(1); list.Add(command); XmlHelper.XmlSerializeToFile(list, XmlFileName, Encoding.UTF8); }
小结:
1. 读写整个XML最方便的方法是使用序列化反序列化。
2. 如果您希望某个参数以Xml Property的形式出现,那么需要使用[XmlAttribute]修饰它。
3. 如果您希望某个参数以Xml Element的形式出现,那么需要使用[XmlElement]修饰它。
4. 如果您希望为某个List的项目指定ElementName,则需要[XmlArrayItem]
5. 以上3个Attribute都可以指定在XML中的映射别名。
6. 写XML的操作是通过XmlSerializer.Serialize()来实现的。
7. 读取XML文件是通过XmlSerializer.Deserialize来实现的。
8. List或Array项,请不要使用[XmlElement],否则它们将以内联的形式提升到当前类,除非你再定义一个容器类。
XmlHelper的实现如下:
public static class XmlHelper { private static void XmlSerializeInternal(Stream stream, object o, Encoding encoding) { if( o == null ) throw new ArgumentNullException("o"); if( encoding == null ) throw new ArgumentNullException("encoding"); XmlSerializer serializer = new XmlSerializer(o.GetType()); XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; settings.NewLineChars = "\r\n"; settings.Encoding = encoding; settings.IndentChars = " "; using( XmlWriter writer = XmlWriter.Create(stream, settings) ) { serializer.Serialize(writer, o); writer.Close(); } } /// <summary> /// 将一个对象序列化为XML字符串 /// </summary> /// <param name="o">要序列化的对象</param> /// <param name="encoding">编码方式</param> /// <returns>序列化产生的XML字符串</returns> public static string XmlSerialize(object o, Encoding encoding) { using( MemoryStream stream = new MemoryStream() ) { XmlSerializeInternal(stream, o, encoding); stream.Position = 0; using( StreamReader reader = new StreamReader(stream, encoding) ) { return reader.ReadToEnd(); } } } /// <summary> /// 将一个对象按XML序列化的方式写入到一个文件 /// </summary> /// <param name="o">要序列化的对象</param> /// <param name="path">保存文件路径</param> /// <param name="encoding">编码方式</param> public static void XmlSerializeToFile(object o, string path, Encoding encoding) { if( string.IsNullOrEmpty(path) ) throw new ArgumentNullException("path"); using( FileStream file = new FileStream(path, FileMode.Create, FileAccess.Write) ) { XmlSerializeInternal(file, o, encoding); } } /// <summary> /// 从XML字符串中反序列化对象 /// </summary> /// <typeparam name="T">结果对象类型</typeparam> /// <param name="s">包含对象的XML字符串</param> /// <param name="encoding">编码方式</param> /// <returns>反序列化得到的对象</returns> public static T XmlDeserialize<T>(string s, Encoding encoding) { if( string.IsNullOrEmpty(s) ) throw new ArgumentNullException("s"); if( encoding == null ) throw new ArgumentNullException("encoding"); XmlSerializer mySerializer = new XmlSerializer(typeof(T)); using( MemoryStream ms = new MemoryStream(encoding.GetBytes(s)) ) { using( StreamReader sr = new StreamReader(ms, encoding) ) { return (T)mySerializer.Deserialize(sr); } } } /// <summary> /// 读入一个文件,并按XML的方式反序列化对象。 /// </summary> /// <typeparam name="T">结果对象类型</typeparam> /// <param name="path">文件路径</param> /// <param name="encoding">编码方式</param> /// <returns>反序列化得到的对象</returns> public static T XmlDeserializeFromFile<T>(string path, Encoding encoding) { if( string.IsNullOrEmpty(path) ) throw new ArgumentNullException("path"); if( encoding == null ) throw new ArgumentNullException("encoding"); string xml = File.ReadAllText(path, encoding); return XmlDeserialize<T>(xml, encoding); } }
xml配置文件 - CDATA
在前面的演示中,有个不完美的地方,我将SQL脚本以普通字符串的形式输出到XML中了:
<CommandText>insret into .....</CommandText>
显然,现实中的SQL脚本都是比较长的,而且还可能会包含一些特殊的字符,这种做法是不可取的,好的处理方式应该是将它以CDATA的形式保存, 为了实现这个目标,我们就不能直接按照普通字符串的方式来处理了,这里我定义了一个类 MyCDATA:
public class MyCDATA : IXmlSerializable { private string _value; public MyCDATA() { } public MyCDATA(string value) { this._value = value; } public string Value { get { return _value; } } XmlSchema IXmlSerializable.GetSchema() { return null; } void IXmlSerializable.ReadXml(XmlReader reader) { this._value = reader.ReadElementContentAsString(); } void IXmlSerializable.WriteXml(XmlWriter writer) { writer.WriteCData(this._value); } public override string ToString() { return this._value; } public static implicit operator MyCDATA(string text) { return new MyCDATA(text); } }
我将使用这个类来控制CommandText在XML序列化及反序列化的行为,让它写成一个CDATA形式, 因此,我还需要修改CommandText的定义,改成这个样子:
public MyCDATA CommandText;
最终,得到的结果是:
<?xml version="1.0" encoding="utf-8"?> <ArrayOfMyCommand xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <MyCommand Name="InsretCustomer" Database="MyTestDb"> <Parameters> <Parameter Name="Name" Type="DbType.String" /> <Parameter Name="Address" Type="DbType.String" /> </Parameters> <CommandText><![CDATA[insret into .....]]></CommandText> </MyCommand> </ArrayOfMyCommand>
xml文件读写注意事项
通常,我们使用使用XmlSerializer.Serialize()得到的XML字符串的开头处,包含一段XML声明元素:
<?xml version="1.0" encoding="utf-8"?>
由于各种原因,有时候可能不需要它。为了让这行字符消失,我见过有使用正则表达式去删除它的,也有直接分析字符串去删除它的。 这些方法,要么浪费程序性能,要么就要多写些奇怪的代码。总之,就是看起来很别扭。 其实,我们可以反过来想一下:能不能在序列化时,不输出它呢? 不输出它,不就达到我们期望的目的了吗?
在XML序列化时,有个XmlWriterSettings是用于控制写XML的一些行为的,它有一个OmitXmlDeclaration属性,就是专门用来控制要不要输出那行XML声明的。 而且,这个XmlWriterSettings还有其它的一些常用属性。请看以下演示代码:
using( MemoryStream stream = new MemoryStream() ) { XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; settings.NewLineChars = "\r\n"; settings.OmitXmlDeclaration = true; settings.IndentChars = "\t"; XmlWriter writer = XmlWriter.Create(stream, settings);
使用上面这段代码,我可以:
1. 不输出XML声明。
2. 指定换行符。
3. 指定缩进字符。
如果不使用这个类,恐怕还真的不能控制XmlSerializer.Serialize()的行为。
The method of reading and writing XML was introduced earlier, but how to start? Since there is no XML file, the program cannot read it, so how to get an XML in the correct format? The answer is: write the code first, create an object to be read, input some junk data, and then write it to XML (deserialization). Then, we can refer to the specific format of the generated XML file, or add other nodes (list), or modify the junk data mentioned above, and finally get a usable XML file with the correct format.
Recommended way to save configuration parameters
It is often seen that there are many components or frameworks that like to put configuration parameters in config files. Those designers may think that the parameters of their works are more complicated and like to create customized configurations. node. The result is: a lot of configuration parameters in the config file. The most troublesome thing is: next time other projects want to use this thing, you have to continue to configure it!
.net has always advocated XCOPY, but I found that there are not many components or frameworks that comply with this convention. Therefore, I would like to suggest that when designing components or frameworks:
1. Please do not put your parameters in the config file. That kind of configuration is really inconvenient to [reuse].
2. Can you provide configuration files and API interfaces to expose parameters at the same time? It is up to the user to decide how to save the configuration parameters.
The difference between config files and XML files
Essentially, config files are also XML files, but they are a little different, not just because .net framework predefines many configuration sections for config files. For ASP.NET applications, if we put parameters in web.config, then as long as web.config is modified, the website will be restarted. There is a benefit at this time: our code can always be updated with the latest parameters to run. On the other hand, there is also a disadvantage: perhaps due to various reasons, we do not want the website to be restarted. After all, restarting the website will take some time, which will affect the response of the website. For this feature, I can only say that there is no way, web.config is like this.
However, when we use XML, it is obvious that we cannot directly get the above features. Because the XML file is maintained by ourselves.
At this point, have you ever thought about: How can I have those advantages when using XML?
I hope that after the user modifies the configuration file, the program can run with the latest parameters immediately without having to re-website.
All sample codes in this article can be downloaded here. demo
The above is the entire content of this article. I hope it will be helpful to everyone’s learning. I also hope that everyone will support the PHP Chinese website.
For more detailed explanations of various methods of reading and writing config files in .net, please pay attention to the PHP Chinese website for related articles!