Файл конфигурации
При разработке приложения появляется необходимость сохранять и восстанавливать некоторые настройки приложения. Например, при работе с базами данных появляется желание не «зашивать» в код путь к базе данных, а хранить к каком-нибудь файле, чтобы его можно было изменить не пересобирая приложение, или сохранить имя пользователя, который входил в систему последний раз, чтобы потом его автоматически выбрать.
С одно стороны для этого можно используя сериализацию в XML. Но как показала практика, этот метод имеет некоторые недостатки. К одному из таких недостатков можно отнести невозможность сохранение private полей и свойств.
При увеличении размера файла конфигурации, появляется желание с группировать параметры по какому-то принципу и оставить комментарий, за что отвечает данный параметр. Таким образом получаем в итоге легко читаемый файл конфигурации, а при необходимости в него можно легко внести изменение.
Задачи для решения
Какие задачи необходимо решить для удобной работы с файлом конфигурации как программисту, так и пользователю:
- должен позволять легко сохранять и восстанавливать настройки
- должна быть возможность комментировать параметры
- должна быть возможность группировать параметры
- должен быть простой механизм для программирования
Решение поставленных задач
Файл конфигурации будет иметь следующий вид название параметра = значение. Если строка является комментарием, то в начале строки должен стоять символ решетка (#).
Для указания какие именно свойства сохранять в файл конфигурации, было решено использовать атрибуты. Комментирование и группирование реализовать через тот же атрибут. Атрибуты не мешают программисту при разработке и не плохо выглядит код.
Для описания полей и свойств класса, которые подлежат сохранения реализован класс ConfigOptionAttribute, который является наследником от Attribute
/// /// Атрибут для автоматического сохрнения данных в файл конфигурации
/// Используется совмесно с потомками класса Colib.BaseConfig
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class ConfigOptionAttribute : Attribute
{
/// /// Название опции
///
public string Name { get; set; }
/// /// Коментарий
///
public string Comment { get; set; }
/// /// Шифровать при записи
///
public bool Security { get; set; }
/// /// Группа параметров
///
public string Group { get; set; }
/// /// Атрибут для автоматического сохрнения данных в файл конфигурации
///
/// Название опции
public ConfigOptionAttribute(string name)
{
Name = name;
Comment = "";
Security = false;
Group = " Global";
}
/// /// Преобразет строку в указаный тип. Если преобразование не поддерживается, то генерируется ошибка
///
/// строка значеия
/// в какой тип преобразовать
/// Объект типа type
internal static object ConvertTo(string value, Type type)
{
if (type == typeof (string))
{
return value;
}
if (type == typeof(int))
{
return int.Parse(value);
}
if (type == typeof(double))
{
return double .Parse(value);
}
if (type == typeof(bool))
{
return bool.Parse(value);
}
throw new Exception("Type to convert not supported");
}
}
Методы для работы с атрибутом определены в классе ConfigOptionAttribute (полный код).
Для сохранения настроек определен метод public void SaveToFile(Stream stream), который получает список всех полей и свойств класса, которые в описании имеют атрибут ConfigOptionAttribute, и сортирует их по группам:
List options = obj.GetType().GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(n => Attribute.IsDefined(n, typeof(ConfigOptionAttribute))).OrderBy(n => ((ConfigOptionAttribute)Attribute.GetCustomAttribute(n, typeof(ConfigOptionAttribute))).Group).ToList();
Далее для каждого элемента списка options в файл конфигурации записывается комментарий название группы, комментарий описание параметра и пара название параметра = значение:
string group = "";
foreach (MemberInfo info in options)
{
// получение атрибута метода
ConfigOptionAttribute attribute = (ConfigOptionAttribute)Attribute.GetCustomAttribute(info, typeof(ConfigOptionAttribute));
if (group != attribute.Group)
{
group = attribute.Group;
if (!string.IsNullOrEmpty(group))
{
sw.WriteLine("# " + group.Trim());
}
else
{
sw.WriteLine("##");
}
}
// запись коментария метода, если он есть
if (!string.IsNullOrEmpty(attribute.Comment))
{
sw.WriteLine(" # " + attribute.Comment);
}
// Запись значение поля или свойства
FieldInfo fieldInfo = info as FieldInfo;
if (fieldInfo != null)
{
sw.WriteLine(string.Format(" {0} = {1}", attribute.Name, fieldInfo.GetValue(obj)));
}
else
{
PropertyInfo propertyInfo = (PropertyInfo)info;
sw.WriteLine(string.Format(" {0} = {1}", attribute.Name, propertyInfo.GetValue(obj, null)));
}
}
Для загрузки параметров из файла конфигурации определен метод public void LoadFromFile(Stream stream), который считывает из файла конфигурации пару название параметра = значение, находит поле или свойство класса, у которому принадлежит атрибут с именем "название параметра" и присваивает ему "значение".
Для работы с файлом конфигурации необходимо сделать класс потомок от абстрактного класса BaseConfig:
class Config : BaseConfig
{
// Первый параметр всегда название параметра
[ConfigOption("Attribute 1", Group = "Группа 1")]
private string _attr1;
// будет добавлен в группу "Global"
[ConfigOption("Proprty 1", Comment = "Свойство 1")]
public string Attr1
{
get { return _attr1; }
set { _attr1 = value; }
}
// будет добавлен в группу "Global"
[ConfigOption("Attribute 2", Comment = "Тестовый атрибут")]
public string _attr2;
public string Attr2
{
get { return _attr2; }
set { _attr2 = value; }
}
[ConfigOption("BoolOption", Group = "Группа 2")]
public bool BoolOpt { get; set; }
[ConfigOption("IntOptions", Group = "Группа 2")]
public int IntOpt { get; set; }
}
Пример создания и сохранения файла конфигурации:
Config config = new Config() { Attr1 = "text attr 1", IntOpt = 345, BoolOpt = true };
var stream = new FileInfo("test2.conf").OpenWrite();
config.SaveToFile(stream);
stream.Close();
Close();
В результате будет создан файл test2.conf, который будет иметь следующий вид:
# Global
# Свойство 1
Proprty 1 = text attr 1
# Тестовый атрибут
Attribute 2 =
# Группа 1
Attribute 1 = text attr 1
# Группа 2
BoolOption = True
IntOptions = 345