Squashed 'FSI.Lib/' content from commit 6aa4846

git-subtree-dir: FSI.Lib
git-subtree-split: 6aa48465a834a7bfdd9cbeae8d2e4f769d0c0ff8
This commit is contained in:
maier_S
2022-03-23 10:15:26 +01:00
commit a0095a0516
62 changed files with 8405 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FSI.Lib.WinSettings
{
internal static class ArrayToString
{
/// <summary>
/// Represents the given array as a single string.
/// </summary>
/// <param name="array">String array to encode.</param>
/// <returns>The encoded string.</returns>
public static string Encode(string[] array)
{
StringBuilder builder = new StringBuilder();
if (array == null)
return string.Empty;
for (int i = 0; i < array.Length; i++)
{
if (i > 0)
builder.Append(',');
if (array[i].IndexOfAny(new[] { ',', '"', '\r', '\n' }) >= 0)
builder.Append(string.Format("\"{0}\"", array[i].Replace("\"", "\"\"")));
else
builder.Append(array[i]);
}
return builder.ToString();
}
/// <summary>
/// Decodes a string created with <see cref="Encode"></see> back to an array.
/// </summary>
/// <param name="s">String to decode.</param>
/// <returns>The decoded array.</returns>
public static string[] Decode(string s)
{
List<string> list = new List<string>();
int pos = 0;
if (s == null)
return Array.Empty<string>();
while (pos < s.Length)
{
if (s[pos] == '\"')
{
// Parse quoted value
StringBuilder builder = new StringBuilder();
// Skip starting quote
pos++;
while (pos < s.Length)
{
if (s[pos] == '"')
{
// Skip quote
pos++;
// One quote signifies end of value
// Two quote signifies single quote literal
if (pos >= s.Length || s[pos] != '"')
break;
}
builder.Append(s[pos++]);
}
list.Add(builder.ToString());
// Skip delimiter
pos = s.IndexOf(',', pos);
if (pos == -1)
pos = s.Length;
pos++;
}
else
{
// Parse value
int start = pos;
pos = s.IndexOf(',', pos);
if (pos == -1)
pos = s.Length;
list.Add(s.Substring(start, pos - start));
// Skip delimiter
pos++;
}
}
return list.ToArray();
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// This attribute specifies that this property should be encrypted
/// when it is saved.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class EncryptedSettingAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// Attribute specifies that this property does not represent a setting and
/// should not be saved.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ExcludedSettingAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,421 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// Represents one name/value pair in an INI file.
/// </summary>
internal class IniSetting
{
/// <summary>
/// The name of this INI setting.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The value of this INI setting.
/// </summary>
public string Value { get; set; }
}
/// <summary>
/// Class to create and read INI files.
/// </summary>
internal class IniFile
{
/// <summary>
/// Section used for settings not under any section header (within [])
/// </summary>
public const string DefaultSectionName = "General";
/// <summary>
/// Represents an entire INI file section.
/// </summary>
private class IniSection
{
public string Name { get; set; }
public Dictionary<string, IniSetting> Settings { get; private set; }
public IniSection()
{
Name = string.Empty;
Settings = new Dictionary<string, IniSetting>(StringComparer.OrdinalIgnoreCase);
}
}
private readonly Dictionary<string, IniSection> Sections = new Dictionary<string, IniSection>(StringComparer.OrdinalIgnoreCase);
#region File functions
/// <summary>
/// Loads an INI settings file.
/// </summary>
/// <param name="filename">Path of file to load.</param>
public void Load(string filename)
{
Sections.Clear();
// Default section
IniSection section = new IniSection { Name = DefaultSectionName };
Sections.Add(section.Name, section);
string line;
#if NET472
using (StreamReader file = new StreamReader(filename))
{
while ((line = file.ReadLine()) != null)
{
line = line.TrimStart();
if (line.Length > 0)
{
if (line[0] == ';')
{
// Ignore comments
}
else if (line[0] == '[')
{
// Parse section header
int pos = line.IndexOf(']', 1);
if (pos == -1)
pos = line.Length;
string name = line.Substring(1, pos - 1).Trim();
if (name.Length > 0)
{
if (!Sections.TryGetValue(name, out section))
{
section = new IniSection { Name = name };
Sections.Add(section.Name, section);
}
}
}
else
{
// Parse setting name and value
string name, value;
int pos = line.IndexOf('=');
if (pos == -1)
{
name = line.Trim();
value = string.Empty;
}
else
{
name = line.Substring(0, pos).Trim();
value = line.Substring(pos + 1);
}
if (name.Length > 0)
{
if (section.Settings.TryGetValue(name, out IniSetting setting))
{
setting.Value = value;
}
else
{
setting = new IniSetting { Name = name, Value = value };
section.Settings.Add(name, setting);
}
}
}
}
}
}
#elif NET6_0_OR_GREATER
using StreamReader file = new StreamReader(filename);
while ((line = file.ReadLine()) != null)
{
line = line.TrimStart();
if (line.Length > 0)
{
if (line[0] == ';')
{
// Ignore comments
}
else if (line[0] == '[')
{
// Parse section header
int pos = line.IndexOf(']', 1);
if (pos == -1)
pos = line.Length;
string name = line.Substring(1, pos - 1).Trim();
if (name.Length > 0)
{
if (!Sections.TryGetValue(name, out section))
{
section = new IniSection { Name = name };
Sections.Add(section.Name, section);
}
}
}
else
{
// Parse setting name and value
string name, value;
int pos = line.IndexOf('=');
if (pos == -1)
{
name = line.Trim();
value = string.Empty;
}
else
{
name = line.Substring(0, pos).Trim();
value = line.Substring(pos + 1);
}
if (name.Length > 0)
{
if (section.Settings.TryGetValue(name, out IniSetting setting))
{
setting.Value = value;
}
else
{
setting = new IniSetting { Name = name, Value = value };
section.Settings.Add(name, setting);
}
}
}
}
}
#endif
}
/// <summary>
/// Writes the current settings to an INI file. If the file already exists, it is overwritten.
/// </summary>
/// <param name="filename">Path of file to write to.</param>
public void Save(string filename)
{
#if NET472
using (StreamWriter file = new StreamWriter(filename, false))
{
bool firstLine = true;
foreach (IniSection section in Sections.Values)
{
if (firstLine)
firstLine = false;
else
file.WriteLine();
if (section.Settings.Any())
{
file.WriteLine("[{0}]", section.Name);
foreach (IniSetting setting in section.Settings.Values)
file.WriteLine("{0}={1}", setting.Name, setting.Value);
}
}
}
#elif NET6_0_OR_GREATER
using StreamWriter file = new StreamWriter(filename, false);
bool firstLine = true;
foreach (IniSection section in Sections.Values)
{
if (firstLine)
firstLine = false;
else
file.WriteLine();
if (section.Settings.Any())
{
file.WriteLine("[{0}]", section.Name);
foreach (IniSetting setting in section.Settings.Values)
file.WriteLine("{0}={1}", setting.Name, setting.Value);
}
}
#endif
}
#endregion
#region Read values
/// <summary>
/// Returns the value of an INI setting.
/// </summary>
/// <param name="section">The INI file section.</param>
/// <param name="setting">The INI setting name.</param>
/// <param name="defaultValue">The value to return if the setting was not found.</param>
/// <returns>Returns the specified setting value.</returns>
public string GetSetting(string section, string setting, string defaultValue = null)
{
if (Sections.TryGetValue(section, out IniSection iniSection))
{
if (iniSection.Settings.TryGetValue(setting, out IniSetting iniSetting))
return iniSetting.Value;
}
return defaultValue;
}
/// <summary>
/// Returns the value of an INI setting as an integer value.
/// </summary>
/// <param name="section">The INI file section.</param>
/// <param name="setting">The INI setting name.</param>
/// <param name="defaultValue">The value to return if the setting was not found,
/// or if it could not be converted to a integer value.</param>
/// <returns>Returns the specified setting value as an integer value.</returns>
public int GetSetting(string section, string setting, int defaultValue)
{
if (int.TryParse(GetSetting(section, setting), out int value))
return value;
return defaultValue;
}
/// <summary>
/// Returns the value of an INI setting as a double value.
/// </summary>
/// <param name="section">The INI file section.</param>
/// <param name="setting">The INI setting name.</param>
/// <param name="defaultValue">The value to return if the setting was not found,
/// or if it could not be converted to a double value.</param>
/// <returns>Returns the specified setting value as a double value.</returns>
public double GetSetting(string section, string setting, double defaultValue)
{
if (double.TryParse(GetSetting(section, setting), out double value))
return value;
return defaultValue;
}
/// <summary>
/// Returns the value of an INI setting as a Boolean value.
/// </summary>
/// <param name="section">The INI file section.</param>
/// <param name="setting">The INI setting name.</param>
/// <param name="defaultValue">The value to return if the setting was not found,
/// or if it could not be converted to a Boolean value.</param>
/// <returns>Returns the specified setting value as a Boolean.</returns>
public bool GetSetting(string section, string setting, bool defaultValue)
{
if (ConvertToBool(GetSetting(section, setting), out bool value))
return value;
return defaultValue;
}
/// <summary>
/// Returns all settings in the given INI section.
/// </summary>
/// <param name="section">The section that contains the settings to be retrieved.</param>
/// <returns>Returns the settings in the given INI section.</returns>
public IEnumerable<IniSetting> GetSectionSettings(string section)
{
if (Sections.TryGetValue(section, out IniSection iniSection))
{
foreach (var setting in iniSection.Settings)
yield return setting.Value;
}
}
#endregion
#region Write values
/// <summary>
/// Sets an INI file setting. The setting is not written to disk until
/// <see cref="Save"/> is called.
/// </summary>
/// <param name="section">The INI-file section.</param>
/// <param name="setting">The name of the INI-file setting.</param>
/// <param name="value">The value of the INI-file setting</param>
public void SetSetting(string section, string setting, string value)
{
if (!Sections.TryGetValue(section, out IniSection iniSection))
{
iniSection = new IniSection { Name = section };
Sections.Add(iniSection.Name, iniSection);
}
if (!iniSection.Settings.TryGetValue(setting, out IniSetting iniSetting))
{
iniSetting = new IniSetting { Name = setting };
iniSection.Settings.Add(iniSetting.Name, iniSetting);
}
iniSetting.Value = value;
}
/// <summary>
/// Sets an INI file setting with an integer value.
/// </summary>
/// <param name="section">The INI-file section.</param>
/// <param name="setting">The name of the INI-file setting.</param>
/// <param name="value">The value of the INI-file setting</param>
public void SetSetting(string section, string setting, int value)
{
SetSetting(section, setting, value.ToString());
}
/// <summary>
/// Sets an INI file setting with a double value.
/// </summary>
/// <param name="section">The INI-file section.</param>
/// <param name="setting">The name of the INI-file setting.</param>
/// <param name="value">The value of the INI-file setting</param>
public void SetSetting(string section, string setting, double value)
{
SetSetting(section, setting, value.ToString());
}
/// <summary>
/// Sets an INI file setting with a Boolean value.
/// </summary>
/// <param name="section">The INI-file section.</param>
/// <param name="setting">The name of the INI-file setting.</param>
/// <param name="value">The value of the INI-file setting</param>
public void SetSetting(string section, string setting, bool value)
{
SetSetting(section, setting, value.ToString());
}
#endregion
#region Boolean parsing
private readonly string[] TrueStrings = { "true", "yes", "on" };
private readonly string[] FalseStrings = { "false", "no", "off" };
private bool ConvertToBool(string s, out bool value)
{
if (s == null)
value = false;
if (TrueStrings.Any(s2 => string.Compare(s, s2, true) == 0))
value = true;
else if (FalseStrings.Any(s2 => string.Compare(s, s2, true) == 0))
value = false;
else if (int.TryParse(s, out int i))
value = i != 0;
else
{
value = false;
return false;
}
return true;
}
#endregion
//public void Dump()
//{
// foreach (IniSection section in Sections.Values)
// {
// Debug.WriteLine(string.Format("[{0}]", section.Name));
// foreach (IniSetting setting in section.Settings.Values)
// Debug.WriteLine("[{0}]=[{1}]", setting.Name, setting.Value);
// }
//}
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// The <see cref="IniSettings"/> class makes it very easy to save your application
/// settings to an INI file.
/// </summary>
/// <remarks>
/// <para>
/// To use the class, simply derive your own settings class from
/// <see cref="IniSettings" /> and add the public properties that you want to be
/// saved as settings. You can then call the <see cref="Settings.Load"/> and
/// <see cref="Settings.Save"/> methods to read or write those settings to an INI
/// file.
/// </para>
/// <para>
/// Your derived class' constructor should initialize your settings properties to
/// their default values.
/// </para>
/// <para>
/// Two attributes are available for public properties in your derived class. The
/// first is <see cref="EncryptedSettingAttribute" />. Use this attribute if you
/// want the setting to be encrypted when saved to file. When using this attribute on
/// any property, you must provide a valid encryption password to the
/// <see cref="IniSettings" /> constructor.
/// </para>
/// <para>
/// The second is the <see cref="ExcludedSettingAttribute"/>. Use this attribute
/// on any properties that are used internally by your code and should not saved to
/// file.
/// </para>
/// <para>
/// All public properties without the <see cref="ExcludedSettingAttribute"></see>
/// attribute must be of one of the supported data types. This includes all the basic
/// data types as well as <see cref="string[]"></see> and <see cref="byte[]"></see>.
/// All other types will raise an exception. In addition, INI files do not support
/// strings that contain newlines unless those strings are encrypted.
/// </para>
/// </remarks>
/// <example>
/// The following example creates a settings class called <c>MySettings</c> with
/// several properties, two of which are encrypted when saved to file.
/// <code>
/// public class MySettings : IniSettings
/// {
/// // Define properties to be saved to file
/// public string EmailHost { get; set; }
/// public int EmailPort { get; set; }
///
/// // The following properties will be encrypted
/// [EncryptedSetting]
/// public string UserName { get; set; }
/// [EncryptedSetting]
/// public string Password { get; set; }
///
/// // The following property will not be saved to file
/// // Non-public properties are also not saved to file
/// [ExcludedSetting]
/// public DateTime Created { get; set; }
///
/// public MySettings(string filename)
/// : base(filename, "Password123")
/// {
/// // Set initial, default property values
/// EmailHost = string.Empty;
/// EmailPort = 0;
/// UserName = string.Empty;
/// Password = string.Empty;
///
/// Created = DateTime.Now;
/// }
/// }
/// </code>
/// </example>
/// <seealso cref="RegistrySettings"/>
/// <seealso cref="XmlSettings"/>
public abstract class IniSettings : Settings
{
/// <summary>
/// Gets or sets the name of the INI settings file.
/// </summary>
[ExcludedSetting]
public string FileName { get; set; }
/// <summary>
/// Constructs an instance of the <c>XmlSettings</c> class.
/// </summary>
/// <param name="filename">Name of the settings file.</param>
/// <param name="password">Encryption password. May be <c>null</c>
/// if no settings use the <see cref="EncryptedSettingAttribute" />
/// attribute.</param>
public IniSettings(string filename, string password = null)
: base(password)
{
if (string.IsNullOrWhiteSpace(filename))
throw new ArgumentException("A valid path and file name is required.", nameof(filename));
FileName = filename;
}
/// <summary>
/// Performs internal load operations.
/// </summary>
/// <param name="settings">Settings to be loaded.</param>
public override void OnLoadSettings(IEnumerable<Setting> settings)
{
if (File.Exists(FileName))
{
// Load INI file
IniFile iniFile = new IniFile();
iniFile.Load(FileName);
// Read settings
foreach (Setting setting in settings)
{
string value = iniFile.GetSetting(IniFile.DefaultSectionName, setting.Name, null);
if (value != null)
setting.SetValueFromString(value);
}
}
}
/// <summary>
/// Performs internal save operations.
/// </summary>
/// <param name="settings">Settings to be saved.</param>
public override void OnSaveSettings(IEnumerable<Setting> settings)
{
// Create INI file
IniFile iniFile = new IniFile();
// Write settings
foreach (Setting setting in settings)
{
string value = setting.GetValueAsString();
if (value != null)
iniFile.SetSetting(IniFile.DefaultSectionName, setting.Name, value);
}
iniFile.Save(FileName);
}
}
}

View File

@@ -0,0 +1,180 @@
# WinSettings
[![NuGet version (SoftCircuits.WinSettings)](https://img.shields.io/nuget/v/SoftCircuits.WinSettings.svg?style=flat-square)](https://www.nuget.org/packages/SoftCircuits.WinSettings/)
```
Install-Package SoftCircuits.WinSettings
```
## Overview
WinSettings is a .NET class library that makes it easy to save and retrieve application settings on Windows. It includes three settings classes: `IniSettings`, which stores the settings to an INI file; `XmlSettings`, which stores the settings to an XML file, and `RegistrySettings`, which stores the settings to the Windows registry. In addition, it makes it easy to define your own settings type.
Settings can be encrypted just by adding a property attribute. There is also an attribute to exclude a particular property when the property is used internally and does not represent an application setting.
To use a settings class, simply derive your own settings class from one of the ones described above and add public properties that you want to be saved. Your class' constructor should set any default values. Then call the `Save()` and `Load()` methods to save the settings in your class.
## IniSettings Class
The <see cref="IniSettings"/> class makes it very easy to save your application settings to an INI file.
To use the class, simply derive your own settings class from `IniSettings` and add the public properties that you want to be saved as settings. You can then call the `Load()` and `Save()` methods to read or write those settings to an INI file.
Your derived class' constructor should initialize your settings properties to their default values.
Two attributes are available for public properties in your derived class. The first is `EncryptedSettingAttribute`. Use this attribute if you want the setting to be encrypted when saved to file. When using this attribute on any property, you must provide a valid encryption password to the `IniSettings` constructor.
The second is the `ExcludedSettingAttribute`. Use this attribute on any properties that are used internally by your code and should not saved to file.
All public properties without the `ExcludedSettingAttribute` attribute must be of one of the supported data types. This includes all the basic data types as well as `string[]` and `byte[]`. All other types will raise an exception. In addition, INI files do not support strings that contain newlines unless those strings are encrypted.
#### Example
The following example creates a settings class called `MySettings` with several properties, two of which are encrypted when saved to file.
```cs
public class MySettings : IniSettings
{
// Define properties to be saved to file
public string EmailHost { get; set; }
public int EmailPort { get; set; }
// The following properties will be encrypted
[EncryptedSetting]
public string UserName { get; set; }
[EncryptedSetting]
public string Password { get; set; }
// The following property will not be saved to file
// Non-public properties are also not saved to file
[ExcludedSetting]
public DateTime Created { get; set; }
public MySettings(string filename)
: base(filename, "Password123")
{
// Set initial, default property values
EmailHost = string.Empty;
EmailPort = 0;
UserName = string.Empty;
Password = string.Empty;
Created = DateTime.Now;
}
}
```
## XmlSettings Class
The <see `XmlSettings` class makes it very easy to save your application settings to an XML file.
To use the class, simply derive your own settings class from `XmlSettings` and add the public properties that you want to be saved as settings. You can then call the `Load()` and `Save()` methods to read or write those settings to an XML file.
Your derived class' constructor should initialize your settings properties to their default values.
Two attributes are available for public properties in your derived class. The first is `EncryptedSettingAttribute`. Use this attribute if you want the setting to be encrypted when saved to file. When using this attribute on any property, you must provide a valid encryption password to the `XmlSettings` constructor.
The second is the `ExcludedSettingAttribute` Use this attribute on any properties that are used internally by your code and should not saved to file.
All public properties without the `ExcludedSettingAttribute` attribute must be of one of the supported data types. This includes all the basic data types `string[]` and `byte[]`. All other types will raise an exception.
#### Example
The following example creates a settings class called `MySettings` with several properties, two of which are encrypted when saved to file.
```cs
public class MySettings : XmlSettings
{
// Define properties to be saved to file
public string EmailHost { get; set; }
public int EmailPort { get; set; }
// The following properties will be encrypted
[EncryptedSetting]
public string UserName { get; set; }
[EncryptedSetting]
public string Password { get; set; }
// The following property will not be saved to file
// Non-public properties are also not saved to file
[ExcludedSetting]
public DateTime Created { get; set; }
public MySettings(string filename)
: base(filename, "Password123")
{
// Set initial, default property values
EmailHost = string.Empty;
EmailPort = 0;
UserName = string.Empty;
Password = string.Empty;
Created = DateTime.Now;
}
}
```
## RegistrySettings Class
The `RegistrySettings` class makes it very easy to save your application settings to the system registry.
To use the class, simply derive your own settings class from `RegistrySettings` and add the public properties that you want to be
saved as settings. You can then call the `Load()` and `Save()` methods to read or write those settings to the system registry.
Your derived class' constructor should initialize your settings properties to their default values.
Two attributes are available for public properties in your derived class. The first is `EncryptedSettingAttribute`. Use this attribute if you want the setting to be encrypted when saved to file. When using this attribute on any property, you must provide a valid encryption password to the `RegistrySettings` constructor.
The second is the `ExcludedSettingAttribute`. Use this attribute on any properties that are used internally by your code and should not saved to the registry.
All public properties without the `ExcludedSettingAttribute` attribute must be of one of the supported data types. This includes all the basic data types as well as `string[]` and `byte[]`. All other types will raise an exception.
#### Example
The following example creates a settings class called `MySettings` with several properties, two of which are encrypted when saved to file.
```cs
public class MySettings : RegistrySettings
{
// Define properties to be saved to file
public string EmailHost { get; set; }
public int EmailPort { get; set; }
// The following properties will be encrypted
[EncryptedSetting]
public string UserName { get; set; }
[EncryptedSetting]
public string Password { get; set; }
// The following property will not be saved to file
// Non-public properties are also not saved to file
[ExcludedSetting]
public DateTime Created { get; set; }
public MySettings(string companyName, string applicationName, RegistrySettingsType settingsType)
: base(companyName, applicationName, settingsType, "Password123")
{
// Set initial, default property values
EmailHost = string.Empty;
EmailPort = 0;
UserName = string.Empty;
Password = string.Empty;
Created = DateTime.Now;
}
}
```
## Settings Class
The `Settings` class is the base class for the `IniSettings`, `XmlSettings` and `RegistrySettings` classes. You don't need this class but you could use it to create your own type of custom settings class.
To do this, create your own `static`, `abstract` class that derives from `Settings` and override the virtual `OnSaveSettings()` and `OnLoadSettings()` methods.
As the name suggests, `OnSaveSettings()` is called when the settings are being saved. This method is passed a collection of `Setting` objects. Your handler needs to write these settings to your custom data store. The `Setting.Name` property contains the setting name. Use the `Setting.GetValue()` method to get the value. Or use the `Setting.GetValueAsString()` instead if your data store only supports string values.
The steps to override `OnLoadSettings()` is similar. This method is also passed a collection of `Setting` objects. Your handler needs to read each named setting from your custom data store. You can then set that value using the `Setting.SetValue()` or `Setting.SetValueFromString()` methods.
## Dependencies
This project requires the NuGet packages [SoftCircuits.EasyEncryption](https://www.nuget.org/packages/SoftCircuits.EasyEncryption/) and Microsoft.Win32.Registry.

View File

@@ -0,0 +1,154 @@
using Microsoft.Win32;
using System.Collections.Generic;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// Specifies the location within the registry where <see cref="RegistrySettings"/> stores
/// settings.
/// </summary>
public enum RegistrySettingsType
{
/// <summary>
/// Stores settings in the Windows registry base key HKEY_CURRENT_USER, normally
/// used for storing information about the current user preferences.
/// </summary>
CurrentUser,
/// <summary>
/// Stores settings in thee Windows registry base key HKEY_LOCAL_MACHINE, normally
/// use for storing configuration data for the local machine.
/// </summary>
LocalMachine
}
/// <summary>
/// The <see cref="RegistrySettings"/> class makes it very easy to save your application
/// settings to the system registry.
/// </summary>
/// <remarks>
/// <para>
/// To use the class, simply derive your own settings class from
/// <see cref="RegistrySettings" /> and add the public properties that you want to be
/// saved as settings. You can then call the <see cref="Settings.Load"/> and
/// <see cref="Settings.Save"/> methods to read or write those settings to the
/// system registry.
/// </para>
/// <para>
/// Your derived class' constructor should initialize your settings properties to
/// their default values.
/// </para>
/// <para>
/// Two attributes are available for public properties in your derived class. The
/// first is <see cref="EncryptedSettingAttribute" />. Use this attribute if you
/// want the setting to be encrypted when saved to file. When using this attribute on
/// any property, you must provide a valid encryption password to the
/// <see cref="RegistrySettings" /> constructor.
/// </para>
/// <para>
/// The second is the <see cref="ExcludedSettingAttribute"/>. Use this attribute
/// on any properties that are used internally by your code and should not saved to
/// the registry.
/// </para>
/// <para>
/// All public properties without the <see cref="ExcludedSettingAttribute"></see>
/// attribute must be of one of the supported data types. This includes all the basic
/// data types as well as <see cref="string[]"></see> and <see cref="byte[]"></see>.
/// All other types will raise an exception.
/// </para>
/// </remarks>
/// <example>
/// The following example creates a settings class called <c>MySettings</c> with
/// several properties, two of which are encrypted when saved to file.
/// <code>
/// public class MySettings : RegistrySettings
/// {
/// // Define properties to be saved to file
/// public string EmailHost { get; set; }
/// public int EmailPort { get; set; }
///
/// // The following properties will be encrypted
/// [EncryptedSetting]
/// public string UserName { get; set; }
/// [EncryptedSetting]
/// public string Password { get; set; }
///
/// // The following property will not be saved to file
/// // Non-public properties are also not saved to file
/// [ExcludedSetting]
/// public DateTime Created { get; set; }
///
/// public MySettings(string companyName, string applicationName, RegistrySettingsType settingsType)
/// : base(companyName, applicationName, settingsType, "Password123")
/// {
/// // Set initial, default property values
/// EmailHost = string.Empty;
/// EmailPort = 0;
/// UserName = string.Empty;
/// Password = string.Empty;
///
/// Created = DateTime.Now;
/// }
/// }
/// </code>
/// </example>
/// <seealso cref="IniSettings"/>
/// <seealso cref="XmlSettings"/>
public abstract class RegistrySettings : Settings
{
private readonly string SubKeyPath;
private readonly RegistryKey RegistryKey;
/// <summary>
/// Constructs a new <c>RegistrySettings</c> instance.
/// </summary>
/// <param name="companyName">Company name entry in registry.</param>
/// <param name="applicationName">Application name entry in registration.</param>
/// <param name="settingsType">Section to store entries in registry.</param>
/// <param name="password">Encryption password. May be <c>null</c> if no settings
/// use the <see cref="EncryptedSettingAttribute" /> attribute.</param>
public RegistrySettings(string companyName, string applicationName, RegistrySettingsType settingsType, string password = null)
: base(password)
{
SubKeyPath = string.Format("Software\\{0}\\{1}", companyName, applicationName);
RegistryKey = settingsType == RegistrySettingsType.CurrentUser ? Registry.CurrentUser : Registry.LocalMachine;
}
/// <summary>
/// Performs internal save operations.
/// </summary>
/// <param name="settings">Settings to be saved.</param>
public override void OnSaveSettings(IEnumerable<Setting> settings)
{
using (RegistryKey registryKey = RegistryKey.CreateSubKey(SubKeyPath, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
foreach (var setting in settings)
{
var value = setting.GetValue();
if (value != null)
registryKey.SetValue(setting.Name, value);
}
}
}
/// <summary>
/// Performs internal load operations.
/// </summary>
/// <param name="settings">Settings to be loaded.</param>
public override void OnLoadSettings(IEnumerable<Setting> settings)
{
using (RegistryKey registryKey = RegistryKey.OpenSubKey(SubKeyPath))
{
if (registryKey != null)
{
foreach (var setting in settings)
{
object value = registryKey.GetValue(setting.Name);
if (value != null)
setting.SetValue(value);
}
}
}
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// Represents a single setting for <see cref="Settings"></see>-derived classes.
/// </summary>
public class Setting
{
private readonly Settings Settings;
private readonly PropertyInfo PropertyInfo;
private readonly bool Encrypted;
/// <summary>
/// Constructs a new instance of the <see cref="Setting"></see> class.
/// </summary>
/// <param name="settings">The <see cref="Settings"/> class that contains
/// this property (setting).</param>
/// <param name="propertyInfo">The <see cref="PropertyInfo"></see> for this
/// property.</param>
/// <param name="encrypted">Indicates whether or not this setting is
/// encrypted.</param>
public Setting(Settings settings, PropertyInfo propertyInfo, bool encrypted)
{
Settings = settings;
PropertyInfo = propertyInfo;
Encrypted = encrypted;
}
/// <summary>
/// Gets the name of this setting.
/// </summary>
public string Name => PropertyInfo.Name;
/// <summary>
/// Gets the type of this setting.
/// </summary>
public Type Type => Encrypted ? typeof(string) : PropertyInfo.PropertyType;
/// <summary>
/// Gets the value of this setting.
/// </summary>
/// <returns>Returns the value of this setting.</returns>
public object GetValue()
{
object value = PropertyInfo.GetValue(Settings);
if (value != null && Encrypted && Settings.Encryption != null)
return Settings.Encryption.Encrypt(value);
return value;
}
/// <summary>
/// Sets the value of this setting.
/// </summary>
/// <param name="value">The value this setting should be set to.</param>
public void SetValue(object value)
{
// Leave property value unmodified if no value or error
if (value != null)
{
try
{
if (Encrypted && Settings.Encryption != null)
{
// Ecrypted values stored as string
if (value is string s)
PropertyInfo.SetValue(Settings, Settings.Encryption.Decrypt(s, PropertyInfo.PropertyType));
}
else PropertyInfo.SetValue(Settings, Convert.ChangeType(value, Type));
}
catch (Exception) { Debug.Assert(false); }
}
}
/// <summary>
/// Gets this setting's value as a string.
/// </summary>
/// <returns>Returns this setting's value as a string.</returns>
public string GetValueAsString()
{
object value = GetValue();
if (value == null)
return string.Empty;
else if (value is byte[] byteArray)
return ArrayToString.Encode(byteArray.Select(b => b.ToString()).ToArray());
else if (value is string[] stringArray)
return ArrayToString.Encode(stringArray);
else
return value?.ToString() ?? string.Empty;
}
/// <summary>
/// Sets this setting's value from a string.
/// </summary>
/// <param name="value">A string that represents the value this setting should be set to.</param>
public void SetValueFromString(string value)
{
if (value != null)
{
try
{
if (Type == typeof(byte[]))
SetValue(ArrayToString.Decode(value).Select(s => byte.Parse(s)).ToArray());
else if (Type == typeof(string[]))
SetValue(ArrayToString.Decode(value));
else
SetValue(value);
}
catch (Exception) { Debug.Assert(false); }
}
}
}
}

View File

@@ -0,0 +1,106 @@
using FSI.Lib.EasyEncryption;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// Provides an abstract base class for specialized settings classes.
/// </summary>
/// <remarks>
/// <para>
/// Specialized classes should inherit from this class and implement specific storage and
/// retrieval logic for each settings by overriding the
/// <see cref="OnSaveSettings(IEnumerable{Setting})"/> and
/// <see cref="OnLoadSettings(IEnumerable{Setting})"/> methods.
/// </para>
/// <para>
/// Ultimately, the specialized classes will then be overridden by each application's
/// settings class. The public properties in that class will become the settings that
/// are saved by classes that derive from this class.
/// </para>
/// </remarks>
public abstract class Settings
{
private IEnumerable<Setting> SettingsList { get; }
/// <summary>
/// Abstract method called when the settings should be saved. Allows
/// the derived class to save those settings in a specialized way.
/// </summary>
/// <param name="settings">The list of settings to be saved.</param>
public abstract void OnSaveSettings(IEnumerable<Setting> settings);
/// <summary>
/// Abstract method called when the settings sould be loaded. Allows
/// the derived class to load those settings in a specialized way.
/// </summary>
/// <param name="settings">The list of settings to be loaded.</param>
public abstract void OnLoadSettings(IEnumerable<Setting> settings);
/// <summary>
/// Gets the <c>Encryption</c> instance associated with this <c>Settings</c>
/// instance.
/// </summary>
[ExcludedSetting]
public Encryption Encryption { get; }
/// <summary>
/// Constructs a new <see cref="Settings"></see> instance.
/// </summary>
/// <remarks>
/// An exception is thrown if password is null but one or more properties have the
/// <see cref="EncryptedSettingAttribute"></see> attribute.
/// </remarks>
/// <param name="password">Encryption password. Can be <c>null</c> if no
/// properties have the <see cref="EncryptedSettingAttribute"></see>
/// attribute.</param>
public Settings(string password = null)
{
if (password == null)
password = GetType().Namespace.ToString();
SettingsList = BuildSettingsList();
Encryption = password != null ?
new Encryption(password, EncryptionAlgorithm.TripleDes) :
null;
}
/// <summary>
/// Saves all settings.
/// </summary>
public void Save()
{
OnSaveSettings(SettingsList);
}
/// <summary>
/// Loads all settings.
/// </summary>
public void Load()
{
OnLoadSettings(SettingsList);
}
private IEnumerable<Setting> BuildSettingsList()
{
// Iterate through all public instance properties
foreach (PropertyInfo prop in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
// Ignore properties with ExcludedSetting attribute
if (!Attribute.IsDefined(prop, typeof(ExcludedSettingAttribute)))
{
// Test for supported data type (same types as for Encryption class)
if (!Encryption.IsTypeSupported(prop.PropertyType))
throw new Exception(string.Format("Settings property '{0}' is an unsupported data type '{1}'. Change property type or use ExcludedSetting attribute.",
prop.Name, prop.PropertyType.ToString()));
bool encrypted = Attribute.IsDefined(prop, typeof(EncryptedSettingAttribute));
if (encrypted && Encryption == null)
throw new InvalidOperationException("Encryption password cannot be null if any settings have the EncryptedSetting attribute.");
yield return new Setting(this, prop, encrypted);
}
}
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
namespace FSI.Lib.WinSettings
{
/// <summary>
/// The <see cref="XmlSettings"/> class makes it very easy to save your application
/// settings to an XML file.
/// </summary>
/// <remarks>
/// <para>
/// To use the class, simply derive your own settings class from
/// <see cref="XmlSettings" /> and add the public properties that you want to be
/// saved as settings. You can then call the <see cref="Settings.Load"/> and
/// <see cref="Settings.Save"/> methods to read or write those settings to an XML
/// file.
/// </para>
/// <para>
/// Your derived class' constructor should initialize your settings properties to
/// their default values.
/// </para>
/// <para>
/// Two attributes are available for public properties in your derived class. The
/// first is <see cref="EncryptedSettingAttribute" />. Use this attribute if you
/// want the setting to be encrypted when saved to file. When using this attribute on
/// any property, you must provide a valid encryption password to the
/// <see cref="XmlSettings" /> constructor.
/// </para>
/// <para>
/// The second is the <see cref="ExcludedSettingAttribute"/>. Use this attribute
/// on any properties that are used internally by your code and should not saved to
/// file.
/// </para>
/// <para>
/// All public properties without the <see cref="ExcludedSettingAttribute"></see>
/// attribute must be of one of the supported data types. This includes all the basic
/// data types as well as <see cref="string[]"></see> and <see cref="byte[]"></see>.
/// All other types will raise an exception.
/// </para>
/// </remarks>
/// <example>
/// The following example creates a settings class called <c>MySettings</c> with
/// several properties, two of which are encrypted when saved to file.
/// <code>
/// public class MySettings : XmlSettings
/// {
/// // Define properties to be saved to file
/// public string EmailHost { get; set; }
/// public int EmailPort { get; set; }
///
/// // The following properties will be encrypted
/// [EncryptedSetting]
/// public string UserName { get; set; }
/// [EncryptedSetting]
/// public string Password { get; set; }
///
/// // The following property will not be saved to file
/// // Non-public properties are also not saved to file
/// [ExcludedSetting]
/// public DateTime Created { get; set; }
///
/// public MySettings(string filename)
/// : base(filename, "Password123")
/// {
/// // Set initial, default property values
/// EmailHost = string.Empty;
/// EmailPort = 0;
/// UserName = string.Empty;
/// Password = string.Empty;
///
/// Created = DateTime.Now;
/// }
/// }
/// </code>
/// </example>
/// <seealso cref="IniSettings"/>
/// <seealso cref="RegistrySettings"/>
public abstract class XmlSettings : Settings
{
/// <summary>
/// Gets or sets the name of the XML settings file.
/// </summary>
[ExcludedSetting]
public string FileName { get; set; }
/// <summary>
/// Constructs an instance of the <see cref="XmlSettings"></see> class.
/// </summary>
/// <param name="filename">Name of the settings file.</param>
/// <param name="password">Encryption password. May be <c>null</c> if
/// no settings use the <see cref="EncryptedSettingAttribute" />
/// attribute.</param>
public XmlSettings(string filename, string password = null)
: base(password)
{
if (string.IsNullOrWhiteSpace(filename))
throw new ArgumentException("A valid file name is required.", nameof(filename));
FileName = filename;
}
/// <summary>
/// Performs internal load operations.
/// </summary>
/// <param name="settings">Settings to be loaded.</param>
public override void OnLoadSettings(IEnumerable<Setting> settings)
{
if (File.Exists(FileName))
{
// Load XML document
XmlDocument doc = new XmlDocument();
doc.Load(FileName);
// Read settings
foreach (Setting setting in settings)
{
XmlNode node = doc.DocumentElement?.SelectSingleNode(setting.Name);
if (node != null)
setting.SetValueFromString(node.InnerText);
}
}
}
/// <summary>
/// Performs internal save operations.
/// </summary>
/// <param name="settings">Settings to be saved.</param>
public override void OnSaveSettings(IEnumerable<Setting> settings)
{
// Create settings document
XmlDocument doc = new XmlDocument();
doc.AppendChild(doc.CreateXmlDeclaration("1.0", "UTF-8", null));
doc.AppendChild(doc.CreateElement("Settings"));
// Write settings
foreach (Setting setting in settings)
{
string value = setting.GetValueAsString();
if (value != null)
{
XmlElement element = doc.CreateElement(setting.Name);
element.InnerText = value;
#if NET472
doc.DocumentElement.AppendChild(element);
#elif NET6_0
doc.DocumentElement!.AppendChild(element);
#endif
}
}
doc.Save(FileName);
}
}
}