diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d67f583
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+/.vs
+/Swan/obj
+/Swan/bin
+/Swan.Lite/bin
+/Swan.Lite/obj
+/Unosquare.RaspberryIO/bin
+/Unosquare.RaspberryIO/obj
+/Unosquare.RaspberryIO.Abstractions/bin
+/Unosquare.RaspberryIO.Abstractions/obj
+/Unosquare.WiringPi/bin
+/Unosquare.WiringPi/obj
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8875e49
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+MIT License
+
+Copyright (c) 2016 Unosquare
+
+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.
+
+This software contains a compiled, unmodified version of the WiringPi library
+WiringPi is a GPIO access library written in C for the BCM2835 used in the
+Raspberry Pi. It’s released under the GNU LGPLv3 license and is usable from C
+and C++ and many other languages with suitable wrappers. A program that contains
+no derivative of any portion of the Library, but is designed to work with
+the Library by being compiled or linked with it, is called a "work that uses
+the Library". Such a work, in isolation, is not a derivative work of the Library,
+and therefore falls outside the scope of this License. Raspberry IO is then,
+by definition, "work that uses the Library"
diff --git a/README.md b/README.md
index cd3c86f..67d2b88 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,5 @@
-# RaspberryIO_26
-
+# UnoSquare
+## RaspberryIO
+Based on [Unosquare.RaspberryIO v0.26.0](https://github.com/unosquare/raspberryio)
+## SWAN
+Based on https://github.com/unosquare/swan
\ No newline at end of file
diff --git a/Swan.Lite/Collections/CollectionCacheRepository.cs b/Swan.Lite/Collections/CollectionCacheRepository.cs
new file mode 100644
index 0000000..496f410
--- /dev/null
+++ b/Swan.Lite/Collections/CollectionCacheRepository.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.Collections
+{
+ ///
+ /// A thread-safe collection cache repository for types.
+ ///
+ /// The type of member to cache.
+ public class CollectionCacheRepository
+ {
+ private readonly Lazy>> _data =
+ new Lazy>>(() =>
+ new ConcurrentDictionary>(), true);
+
+ ///
+ /// Determines whether the cache contains the specified key.
+ ///
+ /// The key.
+ /// true if the cache contains the key, otherwise false.
+ public bool ContainsKey(Type key) => _data.Value.ContainsKey(key);
+
+ ///
+ /// Retrieves the properties stored for the specified type.
+ /// If the properties are not available, it calls the factory method to retrieve them
+ /// and returns them as an array of PropertyInfo.
+ ///
+ /// The key.
+ /// The factory.
+ ///
+ /// An array of the properties stored for the specified type.
+ ///
+ ///
+ /// key
+ /// or
+ /// factory.
+ ///
+ /// type.
+ public IEnumerable Retrieve(Type key, Func> factory)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (factory == null)
+ throw new ArgumentNullException(nameof(factory));
+
+ return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
+ }
+ }
+}
diff --git a/Swan.Lite/Collections/ComponentCollection`1.cs b/Swan.Lite/Collections/ComponentCollection`1.cs
new file mode 100644
index 0000000..7421c09
--- /dev/null
+++ b/Swan.Lite/Collections/ComponentCollection`1.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Swan.Configuration;
+
+namespace Swan.Collections
+{
+ ///
+ /// Implements a collection of components.
+ /// Each component in the collection may be given a unique name for later retrieval.
+ ///
+ /// The type of components in the collection.
+ ///
+ public class ComponentCollection : ConfiguredObject, IComponentCollection
+ {
+ private readonly List _components = new List();
+
+ private readonly List<(string, T)> _componentsWithSafeNames = new List<(string, T)>();
+
+ private readonly Dictionary _namedComponents = new Dictionary();
+
+ ///
+ public int Count => _components.Count;
+
+ ///
+ public IReadOnlyDictionary Named => _namedComponents;
+
+ ///
+ public IReadOnlyList<(string SafeName, T Component)> WithSafeNames => _componentsWithSafeNames;
+
+ ///
+ public T this[int index] => _components[index];
+
+ ///
+ public T this[string key] => _namedComponents[key];
+
+ ///
+ public IEnumerator GetEnumerator() => _components.GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_components).GetEnumerator();
+
+ ///
+ /// The collection is locked.
+ public void Add(string name, T component)
+ {
+ EnsureConfigurationNotLocked();
+
+ if (name != null)
+ {
+ if (name.Length == 0)
+ throw new ArgumentException("Component name is empty.", nameof(name));
+
+ if (_namedComponents.ContainsKey(name))
+ throw new ArgumentException("Duplicate component name.", nameof(name));
+ }
+
+ if (component == null)
+ throw new ArgumentNullException(nameof(component));
+
+ if (_components.Contains(component))
+ throw new ArgumentException("Component has already been added.", nameof(component));
+
+ _components.Add(component);
+ _componentsWithSafeNames.Add((name ?? $"<{component.GetType().Name}>", component));
+
+ if (name != null)
+ _namedComponents.Add(name, component);
+ }
+
+ ///
+ /// Locks the collection, preventing further additions.
+ ///
+ public void Lock() => LockConfiguration();
+ }
+}
diff --git a/Swan.Lite/Collections/ConcurrentDataDictionary`2.cs b/Swan.Lite/Collections/ConcurrentDataDictionary`2.cs
new file mode 100644
index 0000000..58fa75a
--- /dev/null
+++ b/Swan.Lite/Collections/ConcurrentDataDictionary`2.cs
@@ -0,0 +1,304 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.Collections
+{
+ ///
+ /// Represents a thread-safe collection of key/value pairs that does not store null values
+ /// and can be accessed by multiple threads concurrently.
+ ///
+ /// The type of keys in the dictionary. This must be a reference type.
+ /// The type of values in the dictionary. This must be a reference type.
+ ///
+ public sealed class ConcurrentDataDictionary : IDataDictionary
+ where TKey : class
+ where TValue : class
+ {
+ #region Private data
+
+ private readonly ConcurrentDictionary _dictionary;
+
+ #endregion
+
+ #region Instance management
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is empty, has the default concurrency level, has the default initial capacity,
+ /// and uses the default comparer for .
+ ///
+ ///
+ public ConcurrentDataDictionary()
+ {
+ _dictionary = new ConcurrentDictionary();
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that contains elements copied from the specified , has the default concurrency level,
+ /// has the default initial capacity, and uses the default comparer for .
+ ///
+ /// The whose elements are copied
+ /// to the new .
+ /// is .
+ ///
+ /// Since does not store null values,
+ /// key/value pairs whose value is will not be copied from .
+ ///
+ ///
+ public ConcurrentDataDictionary(IEnumerable> collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _dictionary = new ConcurrentDictionary(collection.Where(pair => pair.Value != null));
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is empty, has the default concurrency level and capacity, and uses the specified .
+ ///
+ /// The equality comparison implementation to use when comparing keys.
+ /// is .
+ ///
+ public ConcurrentDataDictionary(IEqualityComparer comparer)
+ {
+ _dictionary = new ConcurrentDictionary(comparer);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that contains elements copied from the specified , has the default concurrency level,
+ /// has the default initial capacity, and uses the specified .
+ ///
+ /// The whose elements are copied
+ /// to the new .
+ /// The equality comparison implementation to use when comparing keys.
+ ///
+ /// Since does not store null values,
+ /// key/value pairs whose value is will not be copied from .
+ ///
+ ///
+ /// is .
+ /// - or -.
+ /// is .
+ ///
+ ///
+ public ConcurrentDataDictionary(IEnumerable> collection, IEqualityComparer comparer)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _dictionary = new ConcurrentDictionary(collection.Where(pair => pair.Value != null), comparer);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is empty, has the specified concurrency level and capacity, and uses the default comparer for the key type.
+ ///
+ /// The estimated number of threads that will update
+ /// the concurrently.
+ /// The initial number of elements that the can contain.
+ ///
+ /// is less than 1.
+ /// - or -.
+ /// is less than 0.
+ ///
+ ///
+ public ConcurrentDataDictionary(int concurrencyLevel, int capacity)
+ {
+ _dictionary = new ConcurrentDictionary(concurrencyLevel, capacity);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that contains elements copied from the specified , has the specified concurrency level,
+ /// has the default initial capacity, and uses the specified .
+ ///
+ /// The estimated number of threads that will update
+ /// the concurrently.
+ /// The whose elements are copied
+ /// to the new .
+ /// The equality comparison implementation to use when comparing keys.
+ ///
+ /// Since does not store null values,
+ /// key/value pairs whose value is will not be copied from .
+ ///
+ ///
+ /// is .
+ /// - or -.
+ /// is .
+ ///
+ /// is less than 1.
+ ///
+ public ConcurrentDataDictionary(int concurrencyLevel, IEnumerable> collection, IEqualityComparer comparer)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _dictionary = new ConcurrentDictionary(
+ concurrencyLevel,
+ collection.Where(pair => pair.Value != null),
+ comparer);
+ }
+
+ #endregion
+
+ #region Public APIs
+
+ ///
+ public int Count => _dictionary.Count;
+
+ ///
+ public bool IsEmpty => _dictionary.IsEmpty;
+
+ ///
+ public ICollection Keys => _dictionary.Keys;
+
+ ///
+ public ICollection Values => _dictionary.Values;
+
+ ///
+ public TValue? this[TKey key]
+ {
+ get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null;
+ set
+ {
+ if (value != null)
+ {
+ _dictionary[key] = value;
+ }
+ else
+ {
+ _dictionary.TryRemove(key, out _);
+ }
+ }
+ }
+
+ ///
+ public void Clear() => _dictionary.Clear();
+
+ ///
+ public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
+
+ ///
+ public TValue? GetOrAdd(TKey key, TValue value)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (value != null)
+ return _dictionary.GetOrAdd(key, value);
+
+ return _dictionary.TryGetValue(key, out var retrievedValue) ? retrievedValue : null;
+ }
+
+ ///
+ public bool Remove(TKey key) => _dictionary.TryRemove(key, out _);
+
+ ///
+ public bool TryAdd(TKey key, TValue value)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+
+ return value == null || _dictionary.TryAdd(key, value);
+ }
+
+ ///
+ public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
+
+ ///
+ public bool TryRemove(TKey key, out TValue value) => _dictionary.TryRemove(key, out value);
+
+ ///
+ public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+
+ return newValue != null && comparisonValue != null && _dictionary.TryUpdate(key, newValue, comparisonValue);
+ }
+
+ #endregion
+
+ #region Implementation of IDictionary
+
+ ///
+ void IDictionary.Add(TKey key, TValue value)
+ {
+ if (value != null)
+ {
+ ((IDictionary)_dictionary).Add(key, value);
+ }
+ else
+ {
+ _dictionary.TryRemove(key, out _);
+ }
+ }
+
+ #endregion
+
+ #region Implementation of IReadOnlyDictionary
+
+ ///
+ IEnumerable IReadOnlyDictionary.Keys => _dictionary.Keys;
+
+ ///
+ IEnumerable IReadOnlyDictionary.Values => _dictionary.Values;
+
+ #endregion
+
+ #region Implementation of ICollection>
+
+ ///
+ ///
+ /// This property is always for a .
+ ///
+ bool ICollection>.IsReadOnly => false;
+
+ ///
+ void ICollection>.Add(KeyValuePair item)
+ {
+ if (item.Value != null)
+ {
+ ((ICollection>)_dictionary).Add(item);
+ }
+ else
+ {
+ _dictionary.TryRemove(item.Key, out _);
+ }
+ }
+
+ ///
+ bool ICollection>.Contains(KeyValuePair item)
+ => ((ICollection>)_dictionary).Contains(item);
+
+ ///
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ => ((ICollection>)_dictionary).CopyTo(array, arrayIndex);
+
+ ///
+ bool ICollection>.Remove(KeyValuePair item)
+ => ((ICollection>)_dictionary).Remove(item);
+
+ #endregion
+
+ #region Implementation of IEnumerable>
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator() => _dictionary.GetEnumerator();
+
+ #endregion
+
+ #region Implementation of IEnumerable
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
+
+ #endregion
+ }
+}
diff --git a/Swan.Lite/Collections/DataDictionary`2.cs b/Swan.Lite/Collections/DataDictionary`2.cs
new file mode 100644
index 0000000..24f5143
--- /dev/null
+++ b/Swan.Lite/Collections/DataDictionary`2.cs
@@ -0,0 +1,341 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.Collections
+{
+ ///
+ /// Represents a non-thread-safe collection of key/value pairs that does not store null values.
+ ///
+ /// The type of keys in the dictionary. This must be a reference type.
+ /// The type of values in the dictionary. This must be a reference type.
+ ///
+ public sealed class DataDictionary : IDataDictionary
+ where TKey : class
+ where TValue : class
+ {
+ #region Private data
+
+ private readonly Dictionary _dictionary;
+
+ #endregion
+
+ #region Instance management
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is empty, has the default initial capacity,
+ /// and uses the default comparer for .
+ ///
+ ///
+ public DataDictionary()
+ {
+ _dictionary = new Dictionary();
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that contains elements copied from the specified ,
+ /// has the default initial capacity, and uses the default comparer for .
+ ///
+ /// The whose elements are copied
+ /// to the new .
+ /// is .
+ ///
+ /// Since does not store null values,
+ /// key/value pairs whose value is will not be copied from .
+ ///
+ ///
+ public DataDictionary(IEnumerable> collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _dictionary = new Dictionary();
+ foreach (var pair in collection.Where(pair => pair.Value != null))
+ {
+ _dictionary.Add(pair.Key, pair.Value);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is empty, has the default capacity, and uses the specified .
+ ///
+ /// The equality comparison implementation to use when comparing keys.
+ /// is .
+ ///
+ public DataDictionary(IEqualityComparer comparer)
+ {
+ _dictionary = new Dictionary(comparer);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that contains elements copied from the specified ,
+ /// has the default initial capacity, and uses the specified .
+ ///
+ /// The whose elements are copied
+ /// to the new .
+ /// The equality comparison implementation to use when comparing keys.
+ ///
+ /// Since does not store null values,
+ /// key/value pairs whose value is will not be copied from .
+ ///
+ ///
+ /// is .
+ /// - or -.
+ /// is .
+ ///
+ ///
+ public DataDictionary(IEnumerable> collection, IEqualityComparer comparer)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _dictionary = new Dictionary(comparer);
+ foreach (var pair in collection.Where(pair => pair.Value != null))
+ {
+ _dictionary.Add(pair.Key, pair.Value);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is empty, has the specified capacity, and uses the default comparer for the key type.
+ ///
+ /// The initial number of elements that the can contain.
+ /// is less than 0.
+ ///
+ public DataDictionary(int capacity)
+ {
+ _dictionary = new Dictionary(capacity);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that contains elements copied from the specified ,
+ /// has the specified capacity, and uses the specified .
+ ///
+ /// The initial number of elements that the can contain.
+ /// The whose elements are copied
+ /// to the new .
+ /// The equality comparison implementation to use when comparing keys.
+ ///
+ /// Since does not store null values,
+ /// key/value pairs whose value is will not be copied from .
+ ///
+ ///
+ /// is .
+ /// - or -.
+ /// is .
+ ///
+ /// is less than 0.
+ ///
+ public DataDictionary(int capacity, IEnumerable> collection, IEqualityComparer comparer)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _dictionary = new Dictionary(capacity, comparer);
+ foreach (var pair in collection.Where(pair => pair.Value != null))
+ {
+ _dictionary.Add(pair.Key, pair.Value);
+ }
+ }
+
+ #endregion
+
+ #region Public APIs
+
+ ///
+ public int Count => _dictionary.Count;
+
+ ///
+ public bool IsEmpty => _dictionary.Count == 0;
+
+ ///
+ public ICollection Keys => _dictionary.Keys;
+
+ ///
+ public ICollection Values => _dictionary.Values;
+
+ ///
+ public TValue? this[TKey key]
+ {
+ get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null;
+ set
+ {
+ if (value != null)
+ {
+ _dictionary[key] = value;
+ }
+ else
+ {
+ _dictionary.Remove(key);
+ }
+ }
+ }
+
+ ///
+ public void Clear() => _dictionary.Clear();
+
+ ///
+ public bool ContainsKey(TKey key)
+ {
+ // _dictionary.ContainsKey will take care of throwing on a null key.
+ return _dictionary.ContainsKey(key);
+ }
+
+ ///
+ public TValue? GetOrAdd(TKey key, TValue value)
+ {
+ // _dictionary.TryGetValue will take care of throwing on a null key.
+ if (_dictionary.TryGetValue(key, out var result))
+ return result;
+
+ if (value == null)
+ return null;
+
+ _dictionary.Add(key, value);
+ return value;
+ }
+
+ ///
+ public bool Remove(TKey key)
+ {
+ // _dictionary.Remove will take care of throwing on a null key.
+ return _dictionary.Remove(key);
+ }
+
+ ///
+ public bool TryAdd(TKey key, TValue value)
+ {
+ // _dictionary.ContainsKey will take care of throwing on a null key.
+ if (_dictionary.ContainsKey(key))
+ return false;
+
+ if (value != null)
+ _dictionary.Add(key, value);
+
+ return true;
+ }
+
+ ///
+ public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
+
+ ///
+ public bool TryRemove(TKey key, out TValue value)
+ {
+ // TryGetValue will take care of throwing on a null key.
+ if (!_dictionary.TryGetValue(key, out value))
+ return false;
+
+ _dictionary.Remove(key);
+ return true;
+ }
+
+ ///
+ public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
+ {
+ // TryGetValue will take care of throwing on a null key.
+ if (!_dictionary.TryGetValue(key, out var value))
+ return false;
+
+ if (value != comparisonValue)
+ return false;
+
+ _dictionary[key] = newValue;
+ return true;
+ }
+
+ #endregion
+
+ #region Implementation of IDictionary
+
+ ///
+ void IDictionary.Add(TKey key, TValue value)
+ {
+ // Validating the key seems redundant, because both Add and Remove
+ // will throw on a null key.
+ // This way, though, the code path on null key does not depend on value.
+ // Without this validation, there should be two unit tests for null key,
+ // one with a null value and one with a non-null value,
+ // which makes no sense.
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (value != null)
+ {
+ _dictionary.Add(key, value);
+ }
+ else
+ {
+ _dictionary.Remove(key);
+ }
+ }
+
+ #endregion
+
+ #region Implementation of IReadOnlyDictionary
+
+ ///
+ IEnumerable IReadOnlyDictionary.Keys => _dictionary.Keys;
+
+ ///
+ IEnumerable IReadOnlyDictionary.Values => _dictionary.Values;
+
+ #endregion
+
+ #region Implementation of ICollection>
+
+ ///
+ ///
+ /// This property is always for a .
+ ///
+ bool ICollection>.IsReadOnly => false;
+
+ ///
+ void ICollection>.Add(KeyValuePair item)
+ {
+ if (item.Value != null)
+ {
+ ((ICollection>)_dictionary).Add(item);
+ }
+ else
+ {
+ _dictionary.Remove(item.Key);
+ }
+ }
+
+ ///
+ bool ICollection>.Contains(KeyValuePair item)
+ => ((ICollection>)_dictionary).Contains(item);
+
+ ///
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ => ((ICollection>)_dictionary).CopyTo(array, arrayIndex);
+
+ ///
+ bool ICollection>.Remove(KeyValuePair item)
+ => ((ICollection>) _dictionary).Remove(item);
+
+ #endregion
+
+ #region Implementation of IEnumerable>
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator() => _dictionary.GetEnumerator();
+
+ #endregion
+
+ #region Implementation of IEnumerable
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
+
+ #endregion
+ }
+}
diff --git a/Swan.Lite/Collections/DisposableComponentCollection`1.cs b/Swan.Lite/Collections/DisposableComponentCollection`1.cs
new file mode 100644
index 0000000..4f42fa6
--- /dev/null
+++ b/Swan.Lite/Collections/DisposableComponentCollection`1.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Swan.Collections
+{
+ ///
+ /// Implements a collection of components that automatically disposes each component
+ /// implementing .
+ /// Each component in the collection may be given a unique name for later retrieval.
+ ///
+ /// The type of components in the collection.
+ ///
+ ///
+ public class DisposableComponentCollection : ComponentCollection, IDisposable
+ {
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~DisposableComponentCollection()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ ///
+ /// to release both managed and unmanaged resources; to release only unmanaged resources.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposing) return;
+
+ foreach (var component in this)
+ {
+ if (component is IDisposable disposable)
+ disposable.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swan.Lite/Collections/IComponentCollection`1.cs b/Swan.Lite/Collections/IComponentCollection`1.cs
new file mode 100644
index 0000000..cf16760
--- /dev/null
+++ b/Swan.Lite/Collections/IComponentCollection`1.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+namespace Swan.Collections
+{
+ ///
+ /// Represents a collection of components.
+ /// Each component in the collection may be given a unique name for later retrieval.
+ ///
+ /// The type of components in the collection.
+ public interface IComponentCollection : IReadOnlyList
+ {
+ ///
+ /// Gets an interface representing the named components.
+ ///
+ ///
+ /// The named components.
+ ///
+ IReadOnlyDictionary Named { get; }
+
+ ///
+ /// Gets an interface representing all components
+ /// associated with safe names.
+ /// The safe name of a component is never .
+ /// If a component's unique name if , its safe name
+ /// will be some non- string somehow identifying it.
+ /// Note that safe names are not necessarily unique.
+ ///
+ ///
+ /// A list of s, each containing a safe name and a component.
+ ///
+ IReadOnlyList<(string SafeName, T Component)> WithSafeNames { get; }
+
+ ///
+ /// Gets the component with the specified name.
+ ///
+ ///
+ /// The component.
+ ///
+ /// The name.
+ /// The component with the specified .
+ /// is null.
+ /// The property is retrieved and is not found.
+ T this[string name] { get; }
+
+ ///
+ /// Adds a component to the collection,
+ /// giving it the specified if it is not .
+ ///
+ /// The name given to the module, or .
+ /// The component.
+ void Add(string name, T component);
+ }
+}
\ No newline at end of file
diff --git a/Swan.Lite/Collections/IDataDictionary`2.cs b/Swan.Lite/Collections/IDataDictionary`2.cs
new file mode 100644
index 0000000..800a7b0
--- /dev/null
+++ b/Swan.Lite/Collections/IDataDictionary`2.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+
+namespace Swan.Collections
+{
+ ///
+ /// Represents a generic collection of key/value pairs that does not store
+ /// null values.
+ ///
+ /// The type of keys in the dictionary. This must be a reference type.
+ /// The type of values in the dictionary. This must be a reference type.
+ public interface IDataDictionary : IDictionary, IReadOnlyDictionary
+ where TKey : class
+ where TValue : class
+ {
+ ///
+ /// Gets a value that indicates whether the is empty.
+ ///
+ ///
+ /// if the is empty; otherwise, .
+ ///
+ bool IsEmpty { get; }
+
+ ///
+ /// Attempts to remove and return the value that has the specified key from the .
+ ///
+ /// The key of the element to remove and return.
+ /// When this method returns, the value removed from the ,
+ /// if the key is found; otherwise, . This parameter is passed uninitialized.
+ /// if the value was removed successfully; otherwise, .
+ /// is .
+ bool TryRemove(TKey key, out TValue value);
+ }
+}
diff --git a/Swan.Lite/Configuration/ConfiguredObject.cs b/Swan.Lite/Configuration/ConfiguredObject.cs
new file mode 100644
index 0000000..1855c81
--- /dev/null
+++ b/Swan.Lite/Configuration/ConfiguredObject.cs
@@ -0,0 +1,77 @@
+using System;
+
+namespace Swan.Configuration
+{
+ ///
+ /// Base class for objects whose configuration may be locked,
+ /// thus becoming read-only, at a certain moment in their lifetime.
+ ///
+ public abstract class ConfiguredObject
+ {
+ private readonly object _syncRoot = new object();
+ private bool _configurationLocked;
+
+ ///
+ /// Gets a value indicating whether s configuration has already been locked
+ /// and has therefore become read-only.
+ ///
+ ///
+ /// if the configuration is locked; otherwise, .
+ ///
+ ///
+ protected bool ConfigurationLocked
+ {
+ get
+ {
+ lock (_syncRoot)
+ {
+ return _configurationLocked;
+ }
+ }
+ }
+
+ ///
+ /// Locks this instance's configuration, preventing further modifications.
+ ///
+ ///
+ /// Configuration locking must be enforced by derived classes
+ /// by calling at the start
+ /// of methods and property setters that could change the object's
+ /// configuration.
+ /// Immediately before locking the configuration, this method calls
+ /// as a last chance to validate configuration data, and to lock the configuration of contained objects.
+ ///
+ ///
+ protected void LockConfiguration()
+ {
+ lock (_syncRoot)
+ {
+ if (_configurationLocked)
+ return;
+
+ OnBeforeLockConfiguration();
+ _configurationLocked = true;
+ }
+ }
+
+ ///
+ /// Called immediately before locking the configuration.
+ ///
+ ///
+ protected virtual void OnBeforeLockConfiguration()
+ {
+ }
+
+ ///
+ /// Checks whether a module's configuration has become read-only
+ /// and, if so, throws an .
+ ///
+ /// The configuration is locked.
+ ///
+ protected void EnsureConfigurationNotLocked()
+ {
+ if (ConfigurationLocked)
+ throw new InvalidOperationException($"Configuration of this {GetType().Name} instance is locked.");
+ }
+ }
+}
diff --git a/Swan.Lite/Configuration/PropertyDisplayAttribute.cs b/Swan.Lite/Configuration/PropertyDisplayAttribute.cs
new file mode 100644
index 0000000..2f45af5
--- /dev/null
+++ b/Swan.Lite/Configuration/PropertyDisplayAttribute.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace Swan.Configuration
+{
+ ///
+ /// An attribute used to include additional information to a Property for serialization.
+ ///
+ /// Previously we used DisplayAttribute from DataAnnotation.
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class PropertyDisplayAttribute : Attribute
+ {
+ ///
+ /// Gets or sets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the description.
+ ///
+ ///
+ /// The description.
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Gets or sets the name of the group.
+ ///
+ ///
+ /// The name of the group.
+ ///
+ public string GroupName { get; set; }
+
+ ///
+ /// Gets or sets the default value.
+ ///
+ ///
+ /// The default value.
+ ///
+ public object DefaultValue { get; set; }
+
+ ///
+ /// Gets or sets the format string to call with method ToString.
+ ///
+ ///
+ /// The format.
+ ///
+ public string Format { get; set; }
+ }
+}
diff --git a/Swan.Lite/Configuration/SettingsProvider.cs b/Swan.Lite/Configuration/SettingsProvider.cs
new file mode 100644
index 0000000..6e1020b
--- /dev/null
+++ b/Swan.Lite/Configuration/SettingsProvider.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Swan.Formatters;
+using Swan.Reflection;
+
+namespace Swan.Configuration
+{
+ ///
+ /// Represents a provider to save and load settings using a plain JSON file.
+ ///
+ ///
+ /// The following example shows how to save and load settings.
+ ///
+ /// using Swan.Configuration;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// // get user from settings
+ /// var user = SettingsProvider<Settings>.Instance.Global.User;
+ ///
+ /// // modify the port
+ /// SettingsProvider<Settings>.Instance.Global.Port = 20;
+ ///
+ /// // if we want these settings to persist
+ /// SettingsProvider<Settings>.Instance.PersistGlobalSettings();
+ /// }
+ ///
+ /// public class Settings
+ /// {
+ /// public int Port { get; set; } = 9696;
+ ///
+ /// public string User { get; set; } = "User";
+ /// }
+ /// }
+ ///
+ ///
+ /// The type of settings model.
+ public sealed class SettingsProvider
+ : SingletonBase>
+ {
+ private readonly object _syncRoot = new object();
+
+ private T _global;
+
+ ///
+ /// Gets or sets the configuration file path. By default the entry assembly directory is used
+ /// and the filename is 'appsettings.json'.
+ ///
+ ///
+ /// The configuration file path.
+ ///
+ public string ConfigurationFilePath { get; set; } =
+ Path.Combine(SwanRuntime.EntryAssemblyDirectory, "appsettings.json");
+
+ ///
+ /// Gets the global settings object.
+ ///
+ ///
+ /// The global settings object.
+ ///
+ public T Global
+ {
+ get
+ {
+ lock (_syncRoot)
+ {
+ if (Equals(_global, default(T)))
+ ReloadGlobalSettings();
+
+ return _global;
+ }
+ }
+ }
+
+ ///
+ /// Reloads the global settings.
+ ///
+ public void ReloadGlobalSettings()
+ {
+ if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0)
+ {
+ ResetGlobalSettings();
+ return;
+ }
+
+ lock (_syncRoot)
+ _global = Json.Deserialize(File.ReadAllText(ConfigurationFilePath));
+ }
+
+ ///
+ /// Persists the global settings.
+ ///
+ public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true));
+
+ ///
+ /// Updates settings from list.
+ ///
+ /// The list.
+ ///
+ /// A list of settings of type ref="ExtendedPropertyInfo".
+ ///
+ /// propertyList.
+ public List RefreshFromList(List> propertyList)
+ {
+ if (propertyList == null)
+ throw new ArgumentNullException(nameof(propertyList));
+
+ var changedSettings = new List();
+ var globalProps = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties();
+
+ foreach (var property in propertyList)
+ {
+ var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
+
+ if (propertyInfo == null) continue;
+
+ var originalValue = propertyInfo.GetValue(Global);
+ var isChanged = propertyInfo.PropertyType.IsArray
+ ? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast