using System; using System.Reflection; using System.Threading.Tasks; using System.Xml.Serialization; using ImageCatalog; using ImageCatalog_2.Models; using Microsoft.Extensions.Logging; namespace ImageCatalog_2.Services { /// /// Modern settings service that uses DTO with attributes for serialization /// public class SettingsService : ISettingsService { private readonly ParametriSetup _parametriSetup; private readonly ILogger _logger; public SettingsService(ParametriSetup parametriSetup, ILogger logger) { _parametriSetup = parametriSetup; _logger = logger; } public Task SaveSettingsAsync(string filePath, object settings) { // Use a dedicated ParametriSetup instance for the target settings file so the injected // user preferences store is not modified. This keeps user prefs (singleton) intact. return Task.Run(() => { var fileParams = new ParametriSetup(filePath); // Convert ViewModel to DTO var dto = ViewModelToDto(settings); // Use reflection on DTO properties with XmlElement attribute var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { var xmlAttr = prop.GetCustomAttribute(); if (xmlAttr == null) continue; var xmlName = xmlAttr.ElementName; var value = prop.GetValue(dto); fileParams.AggiornaParametro(xmlName, value); } fileParams.SalvaParametriSetup(); }); } public async Task LoadSettingsAsync(string filePath, object settings) { // Step 1: Load XML into a temporary ParametriSetup so we don't mutate the injected // user-preferences instance (singleton). var dto = await Task.Run(() => { var fileParams = new ParametriSetup(filePath); var loadedDto = new SettingsDto(); var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { // Get the XmlElement attribute to determine the XML property name var xmlAttr = prop.GetCustomAttribute(); if (xmlAttr == null) continue; // Skip properties without XmlElement attribute var xmlName = xmlAttr.ElementName; try { object value; if (prop.PropertyType == typeof(string)) { value = fileParams.LeggiParametroString(xmlName); } else if (prop.PropertyType == typeof(bool)) { value = fileParams.LeggiParametroBoolean(xmlName); } else if (prop.PropertyType.IsEnum) { // Read enum as string from XML and try to parse to the enum type. Fall back to default. var str = fileParams.LeggiParametroString(xmlName); try { var enumValue = Enum.Parse(prop.PropertyType, str ?? string.Empty, true); value = enumValue; } catch { // try numeric var intVal = fileParams.LeggiParametro(xmlName, (int)(prop.GetValue(loadedDto) ?? 0)); value = Enum.ToObject(prop.PropertyType, intVal); } } else if (prop.PropertyType == typeof(int)) { value = fileParams.LeggiParametro(xmlName, (int)(prop.GetValue(loadedDto) ?? 0)); } else if (prop.PropertyType == typeof(double)) { value = fileParams.LeggiParametro(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0)); } else if (prop.PropertyType == typeof(DateTime)) { var strValue = fileParams.LeggiParametroString(xmlName); if (DateTime.TryParse(strValue, out var dateValue)) { value = dateValue; } else { value = (DateTime)(prop.GetValue(loadedDto) ?? DateTime.Now); } } else { continue; } prop.SetValue(loadedDto, value); } catch (Exception ex) { _logger.LogError(ex, "Failed to read property {XmlName} -> {PropertyName}", xmlName, prop.Name); } } return loadedDto; // End of LoadSettingsAsync method }); // Step 2: Apply DTO values to ViewModel on UI thread DtoToViewModel(dto, settings); } /// /// Converts DataModel (ViewModel) to SettingsDto /// private SettingsDto ViewModelToDto(object viewModel) { var vmType = viewModel.GetType(); var dto = new SettingsDto(); var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var dtoProp in dtoProps) { // Find matching property in ViewModel by name var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance); if (vmProp != null && vmProp.CanRead) { var value = vmProp.GetValue(viewModel); dtoProp.SetValue(dto, value); } } return dto; } /// /// Copies values from SettingsDto to DataModel (ViewModel) /// private void DtoToViewModel(SettingsDto dto, object viewModel) { var vmType = viewModel.GetType(); var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var dtoProp in dtoProps) { // Find matching property in ViewModel by name var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance); if (vmProp != null && vmProp.CanWrite) { var value = dtoProp.GetValue(dto); // Don't update if empty string and current value is not empty if (dtoProp.PropertyType == typeof(string) && string.IsNullOrEmpty((string)value)) { var currentValue = vmProp.GetValue(viewModel) as string; if (!string.IsNullOrEmpty(currentValue)) { continue; } } vmProp.SetValue(viewModel, value); _logger.LogDebug("Set {PropertyName} = {Value}", vmProp.Name, value); } } } } }