美文网首页
使用微软提供的Settings以及自定义SettingsProv

使用微软提供的Settings以及自定义SettingsProv

作者: 勿念情 | 来源:发表于2019-10-14 13:38 被阅读0次

    前言

    在程序中,难免会用到配置文件。如何处理配置文件问题,有许许多多的解决方案。简单的就是直接在app.config里面加一条,然后读取。复杂一些的,可能需要自己去定义格式,存储位置。微软为我们提供了一个方案,就是在项目中的Settings

    Settings

    使用Settings

    Settings为我们提供了设置界面,方便操作。使用起来非常简单。
    如果你新建一个WPF项目,模板中其实默认就带有一个Settings文件。就像上一节中的插图一样,展开Proerpties即可见。你也可以像添加一般的类一样添加新的Settings文件,只需要在添加文件的窗口中找到Settings类型,添加即可。

    添加新的Settings文件
    双击Settings文件,进入可视化编辑界面。
    可视化编辑界面
    类型选择
    作用范围选择

    第一列是设置项的名称。该值作为将来在程序中获取或设置此项设置时的键。
    第二列是设置项的类型。默认有很多的基本类型,注意最有一项,是可以选择自定义类型的。但是也要支持序列化的类型才行,如果是很复杂的类型,需要对其添加序列化和反序列化的方法。
    第三项是设置项的作用范围。只有两种选择,Application和User。如果你选择Application,那么该设置项的值不能被修改。如果你选择User,该设置项可以被修改,但是仅针对于当前计算机用户生效。
    第四项是设置项的默认值。这个没什么好说的,记得默认值要和类型匹配,不然会编译不通过。

    添加完成后,记得点一下保存。VS会帮你生成对应的代码。对应的代码你可以在Settings文件对应的cs文件中找到。

    生成的代码位置
    单项设置对应的代码:
            [global::System.Configuration.UserScopedSettingAttribute()]
            [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
            [global::System.Configuration.DefaultSettingValueAttribute("123")]
            public string Setting {
                get {
                    return ((string)(this["Setting"]));
                }
                set {
                    this["Setting"] = value;
                }
            }
    

    截图中的设置生成的对应代码:

        [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
        internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
            
            private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
            
            public static Settings Default {
                get {
                    return defaultInstance;
                }
            }
            
            [global::System.Configuration.UserScopedSettingAttribute()]
            [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
            [global::System.Configuration.DefaultSettingValueAttribute("123")]
            public string Setting {
                get {
                    return ((string)(this["Setting"]));
                }
                set {
                    this["Setting"] = value;
                }
            }
        }
    

    在CS中使用设置就更简单了。

            static void Main(string[] args)
            {
                //读取设置
                var t = Properties.Settings.Default.Setting;
                Console.WriteLine(t);
                //更新设置
                Properties.Settings.Default.Setting = "6666666";
                //保存设置
                Properties.Settings.Default.Save();
            }
    

    如何使用到此结束。

    工作流程及原理简述

    1. 可视化界面添加需要的设置项。保存时会生成对应的cs文件,同时,会有对应的xml内容写入app.config。
    2. 执行到读取设置时,会根据设置项的作用范围,去读取不同的配置文件。如果没有读取到值,会返回默认值。执行到写入设置时,由于只有User的类型才能写入,系统会调用默认的LocalFileSettingsProvider保存到当前用户的AppData\Local\{ApplicationName}\{Version}\{ApplicationName+LocationHashValue}。

    原理其实非常简单。每次读或写都生成一个SettingsProvider的实例,然后通过这个实例进行读或者写。你可以通过添加属性来指定SettingsProvider的类型。默认会使用LocalFileSettingsProvider。由于LocalFileSettingsProvider没有提供修改保存路径的方法,我们需要自定义SettingsProvider来修改保存路径。需要将[SettingsProvider(typeof(CustomProvider))]添加到生成的cs类上。

    微软表示不提供路径修改是出于安全考虑。具体的内容请自行参阅官方文档。

    自定义SettingsProvider

    直接提供给大家一个可以用的。讲解我放在后面。这个类是拔微软源码做的。

        [
             PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"),
             PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")
        ]
        public class CustomSettingsProvider : SettingsProvider
        {
            private const string UserSettingsGroupName = "userSettings";
            private string _applicationName;
            public override string ApplicationName { get => _applicationName; set => _applicationName = value; }
    
            public override void Initialize(string name, NameValueCollection values)
            {
                if (String.IsNullOrEmpty(name))
                {
                    name = "CustomProvider";
                }
    
                base.Initialize(name, values);
            }
    
            private Configuration configuration;
    
            private void Open()
            {
                var fileMap = new ExeConfigurationFileMap
                {
                    ExeConfigFilename = $"{_applicationName}.exe.config",
                    RoamingUserConfigFilename = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + _applicationName + "\\Settings\\user.config"
                };
                configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.PerUserRoaming);
            }
    
            [
             FileIOPermission(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read),
             PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"),
             PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")
            ]
            public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
            {
                Open();
                var settings = ReadSettingsFromFile(GetSectionName(context));
                var values = new SettingsPropertyValueCollection();
                foreach (SettingsProperty settingProperty in collection)
                {
                    var value = new SettingsPropertyValue(settingProperty);
                    if (settings.Contains(settingProperty.Name))
                    {
                        var ss = (StoredSetting)settings[settingProperty.Name];
                        var valueString = ss.xmlNode.InnerXml;
                        if (ss.serializeAs == SettingsSerializeAs.String)
                        {
                            valueString = Escaper.Unescape(valueString);
                        }
                        value.SerializedValue = valueString;
                    }
                    else if (settingProperty.DefaultValue != null)
                    {
                        value.SerializedValue = settingProperty.DefaultValue;
                    }
    
                    value.IsDirty = false;
                    values.Add(value);
                }
                return values;
            }
    
            private XmlEscaper Escaper = new XmlEscaper();
    
            private IDictionary ReadSettingsFromFile(string sectionName)
            {
                IDictionary settings = new Hashtable();
    
                var sectionGroup = configuration.GetSectionGroup(UserSettingsGroupName);
                var section = sectionGroup.Sections[sectionName] as ClientSettingsSection;
                if (section != null)
                {
                    foreach (SettingElement setting in section.Settings)
                    {
                        settings[setting.Name] = new StoredSetting(setting.SerializeAs, setting.Value.ValueXml);
                    }
                }
    
                return settings;
            }
    
            private string GetSectionName(SettingsContext context)
            {
                string groupName = (string)context["GroupName"];
                string key = (string)context["SettingsKey"];
    
                Debug.Assert(groupName != null, "SettingsContext did not have a GroupName!");
    
                string sectionName = groupName;
    
                if (!String.IsNullOrEmpty(key))
                {
                    sectionName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", sectionName, key);
                }
    
                return XmlConvert.EncodeLocalName(sectionName);
            }
    
            public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
            {
                string sectionName = GetSectionName(context);
                IDictionary userSettings = new Hashtable();
                foreach (SettingsPropertyValue value in collection)
                {
                    SettingsProperty setting = value.Property;
                    if (value.IsDirty)
                    {
                        StoredSetting ss = new StoredSetting(setting.SerializeAs, SerializeToXmlElement(setting, value));
                        userSettings[setting.Name] = ss;
                    }
                }
                WriteSettings(sectionName, userSettings);
            }
    
            private void WriteSettings(string sectionName, IDictionary newSettings)
            {
                Open();
                var section = GetConfigSection(sectionName);
    
                if (section != null)
                {
                    SettingElementCollection sec = section.Settings;
                    foreach (DictionaryEntry entry in newSettings)
                    {
                        SettingElement se = sec.Get((string)entry.Key);
    
                        if (se == null)
                        {
                            se = new SettingElement();
                            se.Name = (string)entry.Key;
                            sec.Add(se);
                        }
    
                        StoredSetting ss = (StoredSetting)entry.Value;
                        se.SerializeAs = ss.serializeAs;
                        se.Value.ValueXml = ss.xmlNode;
                    }
    
                    try
                    {
                        configuration.Save();
                    }
                    catch (ConfigurationErrorsException ex)
                    {
                        // We wrap this in an exception with our error message and throw again.
                        throw new ConfigurationErrorsException($"Save file to {configuration.FilePath} failed", ex);
                    }
                }
                else
                {
                    throw new ConfigurationErrorsException($"Can not find the section {section} in the setting file");
                }
            }
    
            private ClientSettingsSection GetConfigSection(string sectionName)
            {
                Configuration config = configuration;
                string fullSectionName = UserSettingsGroupName + "/" + sectionName;
                ClientSettingsSection section = null;
    
                if (config != null)
                {
                    section = config.GetSection(fullSectionName) as ClientSettingsSection;
    
                    if (section == null)
                    {
                        // Looks like the section isn't declared - let's declare it and try again.
                        DeclareSection(sectionName);
                        section = config.GetSection(fullSectionName) as ClientSettingsSection;
                    }
                }
    
                return section;
            }
    
            // Declares the section handler of a given section in its section group, if a declaration isn't already
            // present. 
            private void DeclareSection(string sectionName)
            {
                Configuration config = configuration;
                ConfigurationSectionGroup settingsGroup = config.GetSectionGroup(UserSettingsGroupName);
    
                if (settingsGroup == null)
                {
                    //Declare settings group
                    ConfigurationSectionGroup group = new UserSettingsGroup();
                    config.SectionGroups.Add(UserSettingsGroupName, group);
                }
    
                settingsGroup = config.GetSectionGroup(UserSettingsGroupName);
    
                Debug.Assert(settingsGroup != null, "Failed to declare settings group");
    
                if (settingsGroup != null)
                {
                    ConfigurationSection section = settingsGroup.Sections[sectionName];
                    if (section == null)
                    {
                        section = new ClientSettingsSection();
                        section.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser;
                        section.SectionInformation.RequirePermission = false;
                        settingsGroup.Sections.Add(sectionName, section);
                    }
                }
            }
    
            private XmlNode SerializeToXmlElement(SettingsProperty setting, SettingsPropertyValue value)
            {
                XmlDocument doc = new XmlDocument();
                XmlElement valueXml = doc.CreateElement("value");
    
                string serializedValue = value.SerializedValue as string;
    
                if (serializedValue == null && setting.SerializeAs == SettingsSerializeAs.Binary)
                {
                    // SettingsPropertyValue returns a byte[] in the binary serialization case. We need to
                    // encode this - we use base64 since SettingsPropertyValue understands it and we won't have
                    // to special case while deserializing.
                    byte[] buf = value.SerializedValue as byte[];
                    if (buf != null)
                    {
                        serializedValue = Convert.ToBase64String(buf);
                    }
                }
    
                if (serializedValue == null)
                {
                    serializedValue = String.Empty;
                }
    
                // We need to escape string serialized values
                if (setting.SerializeAs == SettingsSerializeAs.String)
                {
                    serializedValue = Escaper.Escape(serializedValue);
                }
    
                valueXml.InnerXml = serializedValue;
    
                // Hack to remove the XmlDeclaration that the XmlSerializer adds. 
                XmlNode unwanted = null;
                foreach (XmlNode child in valueXml.ChildNodes)
                {
                    if (child.NodeType == XmlNodeType.XmlDeclaration)
                    {
                        unwanted = child;
                        break;
                    }
                }
                if (unwanted != null)
                {
                    valueXml.RemoveChild(unwanted);
                }
    
                return valueXml;
            }
    
            private class XmlEscaper
            {
                private XmlDocument doc;
                private XmlElement temp;
    
                internal XmlEscaper()
                {
                    doc = new XmlDocument();
                    temp = doc.CreateElement("temp");
                }
    
                internal string Escape(string xmlString)
                {
                    if (String.IsNullOrEmpty(xmlString))
                    {
                        return xmlString;
                    }
    
                    temp.InnerText = xmlString;
                    return temp.InnerXml;
                }
    
                internal string Unescape(string escapedString)
                {
                    if (String.IsNullOrEmpty(escapedString))
                    {
                        return escapedString;
                    }
    
                    temp.InnerXml = escapedString;
                    return temp.InnerText;
                }
            }
        }
        internal class StoredSetting
        {
            public StoredSetting(SettingsSerializeAs serializeAs, XmlNode xmlNode)
            {
                this.serializeAs = serializeAs;
                this.xmlNode = xmlNode;
            }
            internal SettingsSerializeAs serializeAs;
            internal XmlNode xmlNode;
        }
    

    主要的地方在于继承SettingsProvider然后实现两个必须实现的方法,分别是public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)。这两个方法会在读和写的时候被调用。之前也提到过,每次读写,都会生成一个新的SettingsProvider的实例,这点需要注意。其中,ApplicationName是在工程设置里Assmebly Information中的值。
    可能解释得不是很清楚,有什么问题欢迎留言。当然,更推荐去看LocalFileSettingsProvider的源码。

    相关文章

      网友评论

          本文标题:使用微软提供的Settings以及自定义SettingsProv

          本文链接:https://www.haomeiwen.com/subject/zjzlmctx.html