Overview
CliverRoutines is a cross-platform C# lib which includes:
- application settings manager superseding .NET
ConfigurationManager
; - logger with threading and session support;
- auxiliary routines;
Compatibility
CliverRoutines has been developed in .NET Standard 2.0 and supposed to run on any platform that adopts C# including .NET, Xamarin, Mono.
A concern on a certain platform might arise due to peculiarities of its file system.
CliverRoutines has been used on:
- Windows 7+ including windows services;
- macOS;
- Android;
Licensing
See the license source.*Notice that CliverRoutines may use third-party software as command line tools or linked libraries which should be licensed independently.
Source code
Open repository.Do not download the latest code from a branch because it may be under development. Instead, use the latest (pre-)release code.
Config
Cliver.Config
is an application settings manager intended as a capable, yet simple in use replacement for .NET ConfigurationManager.
Features:
- cross-platform;
- natively supports types of any complexity;
- settings are easily modified directly in code;
- natively allows polymorphism;
- supports encryption;
- thread-safe;
- serializes data in JSON;
Idea:
Settings types are ordinary C# classes that you define in your code according to your needs thus achieving a great flexibility. Cliver.Config
automatically detects static fields/properties that are of those types and facilitates their serialization/deserialization to/from disk.
While Cliver.Config
was designed primarily as a settings manager, in conjunction with System.Linq
it can be used as a simple nosql database.
To grasp how to use it by example, refer directly to the usage.
Settings type
Settings type is a custom class whose fields and properties are serialized/deserialized according to the needs of the application.While generally a settings class can be considered as a fully functional class, it must comply with the following conditions:
- it must inherit from Cliver.Settings
class. This inheritance can be indirectly via other classes;
- it must either have no explicit constructor or have a public
parameterless one;
There can be any number of settings types defined in your code.
Example of a settings type:
Serializable fields
Serializable fields are fields or properties of a settings class that are (!)public
and non-static.
Serialization/deserialization is performed by Newtonsoft.Json engine. Hence, you can apply attributes provided by Newtonsoft.Json to serializable fields to alter their serialization behavior. To do so, you have to reference Newtonsoft.Json in your project. (!)To avoid compilation problems, make sure that it is the same version that is referenced by CliverRoutines. For more info refer to Newtonsoft.Json documentation.
(!)While a settings class can implement any logic and members, remember that nevertheless there are restrictions for what and how can be serialized. For instance, data which depend on the current application state like events, FieldInfo
types will be lost through serialization.
Cliver.Settings
Cliver.Settings
is a base class for building custom settings types in your code.
It exposes the members that allow managing serialization routine in its derivatives:
- property __Info
which links an instance of a settings type to a certain settings field;
- serialization methods;
- auxiliary methods. Some of them can be redefined in your settings class if needed;
See the API for the complete info.
Usually you will not inherit Cliver.Settings
directly but rather use one of its derivatives provided by CliverRoutines: Cliver.UserSettings
and Cliver.AppSettings
.
If your settings class inherits Cliver.Settings
directly then you have to define the serialization directory in it.
Cliver.UserSettings
Cliver.UserSettings
is a Cliver.Settings
derivative which performs serialization to the current user's application data directory.
Cliver.AppSettings
Cliver.AppSettings
is a Cliver.Settings
derivative which performs serialization to the common (user-irrelevant) application data directory.
(!)Writing to this directory on Windows may require additional permissions which is solved by Cliver.Win.AppSettings
in CliverWinRoutines package.
Settings field
(!)Do not confuse settings fields with serializable fields.
A settings field is a field or property declared in any class of your app, that satisfies the following:
- its type is a settings type;
- it is static
;
A settings field can have any access modifier.
Also, depending on the .NET version, it can be declared as readonly
(check if the used .NET version allows setting readonly variables through reflection).
Settings fields are automatically detected and initialized by Cliver.Config
.
Behavior of a settings field can be influenced by the attributes applied to it or its type.
There can be any number of settings fields in your code.
Usually you need to have only one settings field per settings type, but there is no problem to declare any number of settings fields of the same settings type.
Example of a settings field:
Example of a settings field being a member of its own type:
Initialization
Detection and initialization of settings fields must be triggered from your code. It is done by calling either
Config.Reload()
or Config.Reset()
method at the beginning of the application.
The initialization can be preceded by optional configuration of the Cliver.Config
engine (examples: assembly scope, load order. For the complete options, refer to the API).
If needed, the initialization can be performed more than once.
Assembly scope
(!)When looking for settings fields in an application,
Cliver.Config
searches within the following assemblies by default:
- the assembly that calls one of
Cliver.Config
's initialization methods;
- assemblies that are referenced by the calling assembly;
The search scope is limited this way in order not to load more assemblies which inevitably takes place while enumerating through them.
If needed, the assembly scope can be configured explicitly in your code by setting Cliver.Config.ExplicitlyTrackedAssemblies
before initializing Cliver.Config
engine:
Storage file
A storage file stores serialized data for one settings field. Thus, every settings field has its own storage file.It is (re)created by Save()
method:
Path
Storage file's path is defined as follows: the file directory is predetermined by the settings type and the file name is predetermined by the settings field's full path in the code.
See the example:
The storage file path of settings field Settings.General
will be:
storage file path: | C:\Users\serge\AppData\Local\ | Dupersoft\Test\config\ | Example.Settings.General.json |
predetermined by: | Cliver.UserSettings |
application name 'Test' and company name 'Dupersoft' | the full path of field General in the application |
Initial file
An initial file keeps the data that is to override the values hard-coded in a settings type's definition. Each settings field can have its own initial file and thus can have its own initial value set distinct from the hard-coded one.
It is optional and has the same format as a storage file.
An initial file is (re)read by Reset()
method or, when no storage file exists, also by Reload()
method.
It is supplied in the same directory as the application's entry assembly.
Initial file is used when:
- initial settings data are too big to be kept in code;
- initial settings data are managed better in a file than in code;
- the application has several settings fields of the same type and each of them must be initiated by its own value set;
Name
An initial file's name is predetermined by its settings field's full path in the code.
See the example:
Name of the initial file of the settings field Settings.General
will be 'App.Settings.General.json'.
Loading
Cliver.Config
performs loading of a settings field in the following order:
Reload()
- from one's storage file if exists;
- from one's initial file if exists;
- from the values predefined in one's settings type;
Reset()
- from one's initial file if exists;
- from the values predefined in one's settings type;
Usage
Reference CliverRoutines in your project.Set your project's company name because it co-defines the directory of the storage files.
Define settings types according to your needs like this:
Somewhere, declare a settings field for the defined settings type:
Add this call at the beginning of your application, to make Cliver.Config
detect all settings fields in the code and initialize them:
Now settings are ready to be used:
To find live examples, refer to CliverRoutinesExample project in CliverRoutines solution.
For the complete usage options, review Cliver.Config
API and annotations.
Attached/detached instances
In the trivial use case, settings fields are only initialized by Cliver.Config
which for every settings field creates an instance of the respective settings type and sets it as the value of the field.
But you, too, can create or clone instances of settings types. And if a settings field is non-readonly, you can replace its value with another instance.
See the replacing example.
A settings type instance keeps information about its settings field in __Info
object which is created only by Cliver.Config
. Therefore, an instance that is created by your code has __Info
empty until you set it with the __Info
taken from the respective instance generated by Cliver.Config
.
If an instance of a settings type is the value of the settings field pointed by its __Info
, it is called attached, otherwise, detached.
This distinction is important because detached settings type instances have a restricted functionality because otherwise it would lead to a confusion. So, when replacing the value of a settings field with a new instance, make sure that the latter has __Info
pointing to this settings field.
See the passing example.
Attributes
Class Cliver.SettingsAttributes
exposes attributes which are applicable to settings fields and/or settings types. Those attributes allow altering settings fields features. Attributes of a settings field can be accessed at runtime though its __Info
property. For example:
More examples can be found in the tips. Refer to the API and annotations for the complete attribute list.
Tips
(!)Predefined collections
Pay attention that when you predefine a collection of values in your code, it will be doubled every time when it is saved/restored. It is howNewtonsoft.Json
works by default.
To alter this behavior, add the attribute as in the example below:
Ignored fields/properties
To make a certain field or property not serializable, make itnon-public
. Otherwise, reference Newtonsoft.Json
and add the attribute as in the example:
Cloning and replacing
Sometimes it is handy to modify not a settings field itself but its clone and then replace it:
Or, consider the opposite case: you need to save an edited settings field while its clone is in use and must remain unchanged until the end of the process so that the changes will come into play after restart:
Polymorphism
Being ordinary C# classes, settings types allow polymorphism. That is, you can build many settings types inheriting from the same parent and easily switch between them depending on the application logic. See the example.Define a base settings type and its derivatives:
Now, without regard to the actual derivative, you can use Settings.This
uniformly:
Encrypting
Settings field/type
A whole settings field can be serialized as an encrypted string by adding the attributeEncrypted
either to the settings field or to its type definition:
Settings.Credentials
will be stored in the storage file as a string encrypted by the StringEndec
instance that you provided.
Serializable field
Also, it is possible to encrypt a settings field not as a whole but only its certain member(s) (i.e. serializable fields). How it is done:Now you can use it like this:
With this encryption, the value is kept encrypted almost all the process time, being decrypted only when called explicitly. It is more secure against RAM sniffers.
Passing to another process
You can pass a settings field to another process. It should be done through serialization.In process 1:
In process 2:
In process 1:
Upgrading settings type
Sometimes, when you upgrade an application, a settings type in it is changed that way that the new application cannot deserialize properly a storage file produced by an older application. When it happens,Cliver.Config
engine calls method UnsupportedFormatHandler()
which you can override.
UnsupportedFormatHandler()
is called in the following cases:
- deserialization engine thew an exception meaning that the settings field could not be deserialized and is set to a default value;
- the settings field was deserialized but its type version is still not acceptable meaning that some restored data may be deteriorated and need amending. This can be the case only if attribute TypeVersion
is specified;
TypeVersion attribute
By adding the attribute TypeVersion
to a certain settings type you assign a version to its definition in the code. When the application serializes an instance of this settings type, its version is stored in the storage file as __TypeVersion
property. Each time when you alter the settings type's definition, you should also alter its version specified by the attribute. Thus, when a newer application reads an older storage file or vice versa, an older application reads a newer storage file, the type version in the storage file does not correspond to the version set by the attribute and therefore Cliver.Config
engine will call UnsupportedFormatHandler()
.
No matter why UnsupportedFormatHandler()
was called, within it you can provide a smooth migration of old data to the new format. See the example below:
Load order
Sometimes, if settings types depend on each other, you may need to configure the order in which they are initialized. It can be done by settingInitializationOrderedSettingsTypes
before calling Reload()
:
Log
Cliver.Log
is a logger designed with usability and scalability in mind.
Features:
- cross-platform;
- thread-safe;
- session oriented - an application can write multiple log sessions successively or simultaneously. It is helpful when an application performs multiple independent tasks;
- thread oriented - it can automatically write a log per thread;
- auto-cleanup of old logs;
- diagnostic output;
To grasp how to use it for logging sufficient in most applications, refer directly to the usage.
Session
A session is a collection of logs that is dedicated to a certain logical task. Usually a log belongs to some session (but can be a sessionless log).Depending on Cliver.Log
configuration, it is either:
- all the sessions share the same folder. Log files have their session name in their names;
- or, each session creates its own folder to which all its logs are written;
Session folders can be used to store additional data like caches etc.
Multiple sessions can be open simultaneously.
Using an explicitly created session:
Head session
The default session is a session that is used when no session is called explicitly. It is referenced asHead
and has an empty name.
The default session is used for the trivial logging which is sufficient for the most applications:
Default log
The default log of a session is the log that is called without explicit reference. Depending onCliver.Log
configuration, it can be either the main log (by default) or thread log.
The default log is handy as a shortcut for trivial logging when everything is written to the same file:
Toggle the default log to thread log:
Root directory
All sessions are created within the root directory which is defined at the first logging or by
Log.Initialize()
By default, the root directory is defined in the order by which Cliver.Log
tries to write to:
- the user data folder;
- the common data folder;
- the folder where the application is located;
- the user desktop folder;
- the temp folder;
The root directory definition can be customized while initializing:
Named log
Named log is a log that can be referenced by a custom name.The name of a named log is a part of its file name.
Writing to a named log of the default session:
Writing to a named log of an explicitly created session:
Main log
The main log of a session is a named log that is not created explicitly and is referenced asMain
. It has an empty name.
The main log is handy as a ready-to-use log: Or:
Thread log
A thread log is a log that belongs to only one and the same .NET thread.
Each thread log has a unique ID which is a part of its file name. Depending on configuration, those ID's can be reused or be endlessly incremented for each new thread log.
In this example, each downloading thread writes its own log:
Sessionless log
A sessionless log is a named log that is created within the root directory and does not belong to a session so that it is continued with the next launch of the application.
Example:
Usage
Reference CliverRoutines in your project.Set your project's company name because it co-defines the log directory.
At the beginning of the application, add optional initialization:
Write to log:
To see live examples, refer to CliverRoutinesExample project in CliverRoutines solution.
For the complete usage options, review Cliver.Log
API and annotations.
Miscellaneous
CliverRoutines exposes a number of auxiliary routines. See the API and annotations for details.