using Common.Logging; using System.Xml.Serialization; namespace Common.AppSettings; public class XmlAppSettingsProvider : IAppSettingsProvider { private readonly string settingsFilePath; private readonly XmlSerializer serializer; private List settings; public event EventHandler? LogWorthyEvent; public XmlAppSettingsProvider(string settingsFilePath) { // check for file path validity // TODO create file if it does not exist yet if (String.IsNullOrEmpty(settingsFilePath)) { throw new ArgumentNullException(nameof(settingsFilePath)); } this.settingsFilePath = settingsFilePath; this.settings = new List(); this.serializer = new XmlSerializer(typeof(List)); } public void Load() { // read file by creating a stream. this will throw an exception when the file does not exist. using var fileStream = new FileStream(this.settingsFilePath, FileMode.Open); // deserialize and cast to List if not null var deserializedObj = this.serializer.Deserialize(fileStream); if(deserializedObj != null) { // create log entry if there were already some settings loaded if(this.settings.Count > 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(Load)}'() deserialization overwrote previously loaded settings.")); } this.settings = (List)deserializedObj; } else { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(Load)}'() deserialization resulted in null object. Did not change loaded settings.")); } } public void Save() { // TODO write log entries for when something goes wrong TextWriter writer = new StreamWriter(this.settingsFilePath); this.serializer.Serialize(writer, this.settings); writer.Close(); } public bool WriteSetting(string key, string value) { // guard empty key or value if(String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); // get all settings with this key var foundSettings = this.settings.FindAll(x => x.Key == key); // check if the key already exists multiple times if(foundSettings.Count > 1) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(WriteSetting)}'() cancelled because {foundSettings.Count} settings with the key '{key}' were found. Expected none or exactly one.")); return false; // cancel } // if exactly one is found => remove existing app setting else if(foundSettings.Count == 1) { this.settings.Remove(this.settings.Find(x => x.Key == key)!); } // none existing yet => inform via event else if(foundSettings.Count == 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Info, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(WriteSetting)}'() initialized new setting with key '{key}' and value '{value}'.")); } // add new setting this.settings.Add(new AppSetting(key, value)); return true; } public bool TryReadSetting(string key, out string value) { // guard empty key if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); // get all settings with this key var foundSettings = this.settings.FindAll(x => x.Key == key); // too many values found if (foundSettings.Count > 1) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(TryReadSetting)}'() did find {foundSettings.Count} settings with the key '{key}'. Expected only one.")); value = string.Empty; return false; } // no value found if (foundSettings.Count == 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(TryReadSetting)}'() did not find a setting with the key '{key}'.")); value = string.Empty; return false; } // extract correct setting when found exactly one var setting = foundSettings[0]; if(setting.Value != null) { value = setting.Value; } // if value is null it was probably an empty string or similar => set it to empty string else { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(TryReadSetting)}'() value of retrieved setting with key '{key}' was null. Returned empty string instead.")); value = string.Empty; } return true; } public bool TryReadSettingOrAddDefault(string key, out string value, string defaultValue) { // guard empty key or defaultValue if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); if (String.IsNullOrEmpty(defaultValue)) throw new ArgumentNullException(nameof(defaultValue)); // get all settings with this key var foundSettings = this.settings.FindAll(x => x.Key == key); // too many values found if (foundSettings.Count > 1) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(TryReadSettingOrAddDefault)}'() did find {foundSettings.Count} settings with the key '{key}'. Expected only one.")); value = string.Empty; return false; } // no value found => initialize with the default value if (foundSettings.Count == 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Info, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(TryReadSettingOrAddDefault)}'() did not find a setting with the key '{key}'. Adding setting with value '{defaultValue}'.")); this.WriteSetting(key, defaultValue); value = defaultValue; return true; } // extract correct setting when found exactly one var setting = foundSettings[0]; if (setting.Value != null) { value = setting.Value; } // if value is null it was probably an empty string or similar => set it to empty string else { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), message: $"'{nameof(TryReadSetting)}'() value of retrieved setting with key '{key}' was null. Returned empty string instead.")); value = string.Empty; } return true; } }