Plexdata Settings Serialization
Overview
The Plexdata Settings Serialization represents an alternative to the standard settings serialization mechanism.
Main feature of this library is that users are able to define the path as well as the extension of their settings file.
As extension to the standard settings handling, this library allows to put the settings into the directory of the executable. This is very interesting for so called Portable Apps, which usually manage all their data within one and the same directory.
Another feature of this library is the possibility to control the format of the settings file. At the moment, reading and writing of settings files in JSON format as well as in XML format is supported. Other formats like for example old‑school INI files may follow in a later version.
Licensing
The software has been published under the terms of
MIT License
Copyright © 2023 plexdata.de
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the Software
), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS
, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
Installation
The binary files of the Plexdata Settings Serialization are provided as NuGet package and can be obtained from https://www.nuget.org/packages/Plexdata.Settings.Serialization. How to install this NuGet package manually is explained there.
Using the Plexdata Settings Serialization together with Visual Studio.
- Create a new .NET Framework or a .NET Standard or a .NET Core project.
-
Open menu Tools → NuGet Package Manager and choose command Manage NuGet Packages for Solution….
- Alternatively, simply right‑click the project in the Solution Explorer and choose command Manage NuGet Packages….
-
Switch to Browse page and in the search box just type
plexdata.settings.serialization
. -
From the shown list select
Plexdata.Settings.Serialization
and click button[Install]
at the right. - Confirm the dialog box and that’s it.
Additionally, all releases can be downloaded from GitHub. Please visit page Plexdata Settings Serialization to find all available versions.
Introduction
Visual Studio provides a very powerful handling of managing application settings. But unfortunately it is not really possible to load and save settings that are located in the same folder where the executing assembly is located. This makes it almost impossible to write so‑called Portable Apps, which usually manage all operations within one and the same directory.
Another issue with the standard Visual Studio settings management is the fact that controlling the name of the settings file as well as the format of the settings file is also not really possible.
All these features will become possible with this package.
Locations
In this section please find an overview of possible storage locations and how to affect them.
DefaultLocation
-
The default storage location. In this case the folder of
ExecutableFolder
is used. ExecutableFolder
-
The folder of the executing assembly. This folder is used only if writing files is permitted. In case of writing
is not permitted the folder referenced by
FallbackLocation
is taken instead. FallbackLocation
-
The fallback storage location. This folder is used in case of writing files into chosen folder is not permitted.
The folder of
LocalApplicationData
is used for this purpose. LocalApplicationData
-
The local application data folder. The resulting path should look like:
<drive>\Users\<username>\AppData\Local\<company>\<product>\[<version>\]<filename>.<extension>
ApplicationData
-
The standard application data folder. The resulting path should look like:
<drive>\Users\<username>\AppData\Roaming\<company>\<product>\[<version>\]<filename>.<extension>
CommonApplicationData
-
The common application data folder. The resulting path should look like:
<drive>\ProgramData\<company>\<product>\[<version>\]<filename>.<extension>
The variables <company>
and <product>
in the above example paths come from the executing
assembly. The <version>
instead is optional and must be explicitly enabled via property IsVersionized
of ISettingsOptions
. See below from where these path parts are taken in detail.
<company>
-
The company descriptor comes either from the Assembly Company Attribute or (if not exist) is taken from the first part
of the namespace of the declaring type of the entry point of the executing assembly. This is usually the namespace of class
Program
. <product>
- The product descriptor comes either from the Assembly Product Attribute or (if not exist) represents the file name of the executing assembly but without its file extension.
<version>
-
The version descriptor comes either from the Assembly Version Attribute or (if not exist) from the
Assembly File
Version Attribute
. The version descriptor
0.0.0.0
is taken in case of none of these attributes exists. <filename>
- The filename descriptor is nothing else but the name of the executing assembly but without its extension.
<extension>
-
The extension descriptor comes from property
Extension
ofISettingsOptions
. Its default value is.conf
if it hasn’t be changed explicitly.
Models
Models or better data models are user defined classes that are used as generic parameter for interfaces ISettingsReader
and ISettingsWriter
.
Because of the internal usage of Newtonsoft’s Json.NET package
it becomes possible to tag all own model properties with attribute JsonProperty
. But to use this feature it might be
necessary to include this package in an own project. But keep in mind, the Plexdata Settings Serialization works as well
if this package is not manually included in user projects.
Especially for XML serialization another attribute of Newtonsoft’s Json.NET package is used by
Plexdata Settings Serialization. This is attribute JsonObject
. By using this attribute users can modify the
name of the root object within the resulting XML file.
Below find an example model that can be used together with Plexdata Settings Serialization and which tags its properties with attributes of Newtonsoft’s Json.NET package.
[JsonObject("program-settings")]
public class ProgramSettings
{
[JsonProperty("application-title")]
public String Title { get; set; }
[JsonProperty("main-window-position")]
public Point Location { get; set; }
[JsonProperty("main-window-size")]
public Size Dimension { get; set; }
}
Reading
In this section users may find an interesting example of how to read a settings file. The example implementation below assumes the
existence of data model ProgramSettings
from section above.
class Program
{
static void Main(String[] args)
{
var options = SettingsFactory.Create<ISettingsOptions>(
SettingsPattern.JsonPattern, StorageLocation.ExecutableFolder, "program.settings");
var reader = SettingsFactory.Create<ISettingsReader<ProgramSettings>>();
if (!reader.TryRead(options, out ProgramSettings settings))
{
settings = new ProgramSettings();
}
}
}
As shown above, an instance of ISettingsOptions
is created and configured as first. This configuration assumes a JSON
settings file that is located within the directory of the executable file. Furthermore, this settings file uses a file extension of
program.settings
.
As next an instance of ISettingsReader
is created. Thereafter it is tried to read the settings file and, if it fails,
a new instances of user defined ProgramSettings
is created manually.
Writing
In this section users may find an interesting example of how to write a settings file. The example implementation below assumes the
existence of the same data model ProgramSettings
as used in section above.
class Program
{
static void Main(String[] args)
{
var options = SettingsFactory.Create<ISettingsOptions>(
SettingsPattern.JsonPattern, StorageLocation.ExecutableFolder, "program.settings");
ProgramSettings settings = new ProgramSettings();
var writer = SettingsFactory.Create<ISettingsWriter<ProgramSettings>>();
writer.TryWrite(options, settings);
}
}
As shown above, an instance of ISettingsOptions
is created and configured as first. This configuration assumes a JSON
settings file that has to be saved into the directory of the executable file. Furthermore, this settings file uses a file extension of
program.settings
.
As next an instance of ISettingsWriter
is created. Thereafter it is tried to write the settings file.
DI‑Handling
The handling of dependency injection is indeed a common issue and is discussed in this section. Additionally note, for the example code in here package Unity is used as dependency injection container.
The first code snippet below shows a typical extension method for interface IUnityContainer
to be used to perform all
needed dependency injection registrations.
public static IUnityContainer PerformRegistration(this IUnityContainer container)
{
if (container is null)
{
return container;
}
container.RegisterFactory<ISettingsOptions>("ProgramSettingsOptions",
x => SettingsFactory.Create<ISettingsOptions>(
SettingsPattern.JsonPattern, StorageLocation.ExecutableFolder, "program.settings"
));
container.RegisterFactory<ISettingsReader<ProgramSettings>>("ProgramSettingsReader",
x => SettingsFactory.Create<ISettingsReader<ProgramSettings>>());
container.RegisterFactory<ISettingsWriter<ProgramSettings>>("ProgramSettingsWriter",
x => SettingsFactory.Create<ISettingsWriter<ProgramSettings>>());
container.RegisterSingleton<IProgramSettingsManager, ProgramSettingsManager>();
return container;
}
The first three calls exemplarily show how to configure the factory registrations. Furthermore, each of the factory registrations
uses a special factory name just to ensure the uniqueness of all ProgramSettings
depended classes.
The last call instead registers a Singleton
that must be implemented by user code. Managing all settings related tasks is
the job of this class. Furthermore, this manager class gets all settings related instances via constructor injection. See next code
snippet for such an example implementation.
internal class ProgramSettingsManager : IProgramSettingsManager
{
private readonly ISettingsOptions options = null;
private readonly ISettingsReader<ProgramSettings> reader = null;
private readonly ISettingsWriter<ProgramSettings> writer = null;
private ProgramSettings instance = null;
public ProgramSettingsManager(
[Dependency("ProgramSettingsOptions")] ISettingsOptions options,
[Dependency("ProgramSettingsReader")] ISettingsReader<ProgramSettings> reader,
[Dependency("ProgramSettingsWriter")] ISettingsWriter<ProgramSettings> writer)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.reader = reader ?? throw new ArgumentNullException(nameof(reader));
this.writer = writer ?? throw new ArgumentNullException(nameof(writer));
this.Instance = new ProgramSettings();
}
public ProgramSettings Instance { get; private set; }
public Boolean Load()
{
if (this.reader.TryRead(this.options, out ProgramSettings result))
{
this.Instance = result;
return true;
}
return false;
}
public Boolean Save()
{
return this.writer.TryWrite(this.options, this.Instance);
}
}
In contrast to constructor injection, the way of property injection would be possible as well. The code snippet below demonstrates it.
internal class ProgramSettingsManager : IProgramSettingsManager
{
public ProgramSettingsManager()
{
this.Instance = new ProgramSettings();
}
[Dependency("ProgramSettingsOptions")]
public ISettingsOptions Options { private get; set; }
[Dependency("ProgramSettingsReader")]
public ISettingsReader<ProgramSettings> Reader { private get; set; }
[Dependency("ProgramSettingsWriter")]
public ISettingsWriter<ProgramSettings> Writer { private get; set; }
public ProgramSettings Instance { get; private set; }
public Boolean Load()
{
if (this.Reader.TryRead(this.Options, out ProgramSettings result))
{
this.Instance = result;
return true;
}
return false;
}
public Boolean Save()
{
return this.Writer.TryWrite(this.Options, this.Instance);
}
}
Limitation
Not known at the moment.
Known Issues
Not known at the moment.