Konfiguracja

4 Marca 2016

Każda większa aplikacja potrzebuje zapisywać sobie jakieś ustawienia. Aby to ułatwić wymyśliłem interfejs IConfiguration. Stwierdziłem, że niezależnie od implementacji tego interfejsu, będzie potrzebowali dwóch metod:

T GetProperty<T>(string propertyName);
void SetProperty<T>(string propertyName, T propertyValue);

Dodatkowo, aby uprościć dostęp do wszystkich ustawień na raz, interfejs posiada metodę

IEnumerable<KeyValuePair<string, object>> GetAllProperties();

Przykładową implementacją takiej konfiguracji będzie klasa posiadająca prywatne pola/własności, która za pomocą instrukcji switch będzie zwracała/przypisywała odpowiednie wartości, sprawdziwszy poprawność typu T z typem własności.

PropertyBasedConfiguration

Aby ułatwić sobie tworzenie klas konfiguracji (i ponieważ miałem problemy z deserializacją zagnieżdżonych kolekcji) stworzyłem abstrakcyjną klasę PropertyBasedConfiguration, która za pomocą refleksji odwołuje się do swoich własności. Utworzenie nowej klasy konfiguracji jest w tym momencie banalne. Wystarczy utworzyć klasę, która dziedziczy po PropertyBasedConfiguration i dodać potrzebne jej własności, a metody zaimplementowane przez bazę robią resztę.

Implementację możecie zobaczyć tutaj

Zapisywanie konfiguracji

Żeby mieć wolną rękę co do formatu plików konfiguracji (json, xml, yaml, plik binarny, itp.) utworzyłem interfejs IConfigurationFormat, który wymusza implementację następujących metod:

void WriteConfiguration(IConfiguration data, Stream stream);
T ReadConfiguration<T>(Stream stream) where T : IConfiguration, new();
BinaryConfigurationFormat

Jeśli nie potrzebujemy edytować naszej konfiguracji spoza programu możemy zapisać dane w czysto binarnej formie. Taką implementację jest BinaryConfigurationFormat, gdzie używam obiektu BinaryFormatter do jednoznacznego zapisywania danych do strumienia. Kod.

YamlConfigurationFormat

Z drugiej strony jeśli chcemy mieć plik konfiguracji bardziej przyjazny dla oka ludzkiego posłużymy się np. YAMLem. I o ile BinaryFormatter bezproblemowo działał na dowolnych obiektach, zapisując ich typ, to YamlDotNet, którego używam, nie był w stanie domyślić się czym miał być object. Początkowo udało mi się zrobić system rzutowania dla typów podstawowych, ale dla kolekcji to nie działało, bardzo. Więc zmieniłem podejście. Napisałem PropertyBasedConfiguration i ograniczyłem YamlConfigurationFormat do jej pochodnych. Przez to mogę mieć (w dużym stopniu) pewność silnego typowania podczas deserializacji. Jeśli ktoś będzie używał typu object wiedząc, że jest to int, musi się liczyć z deserializacją do stringa. Kod.