Skip to main content

Example 07: Comprehensive Settings Management

Overview

The dashboard implements a multi-tiered settings system that manages user preferences, company configurations, and system-wide settings through a unified interface with proper scoping and persistence.

Settings Architecture

Scope Hierarchy

  1. Program: Global settings for all users
  2. Company: Company-specific settings
  3. CompanyOperator: Company + user combination
  4. Operator: User-specific preferences

Settings Service Implementation

// MepSettingsService.cs
public class MepSettingsService : IMepSettingsService
{
public async Task InitializeDefaultAsync()
{
// Load settings definition
string settingsJson = ResourceHelper.GetResourceFileText("MepSettings.json");
Settings = JsonConvert.DeserializeObject<List<MepSettings>>(settingsJson);

foreach (var setting in Settings)
{
// Determine scope
var scope = await GetSettingScope(setting.SettingName);
setting.SettingScope = scope ?? setting.SettingScope;

// Load or set value
var value = await GetValueByScope(setting.SettingName, setting.SettingScope);
if (string.IsNullOrEmpty(value))
{
await SetValueByScope(setting.SettingName,
setting.SettingValue, setting.SettingScope);
}
else
{
setting.SettingValue = value;
}
}
}
}

Settings Categories

Ordering Settings

{
"SettingName": "OrderByWarehouse",
"SettingValue": "true",
"SettingScope": "Company",
"Category": "Ordering",
"DisplayName": "Group Orders by Warehouse",
"Description": "When enabled, purchase orders are grouped by warehouse"
}

Performance Settings

{
"SettingName": "PageSize",
"SettingValue": "50",
"SettingScope": "Operator",
"Category": "Performance",
"DisplayName": "Grid Page Size",
"Description": "Number of rows to display per page",
"DataType": "Integer",
"MinValue": "10",
"MaxValue": "500"
}

Integration Settings

{
"SettingName": "SysproTimeout",
"SettingValue": "30",
"SettingScope": "Program",
"Category": "Integration",
"DisplayName": "SYSPRO Timeout (seconds)",
"Description": "Maximum wait time for SYSPRO operations"
}

Settings UI

Settings Dialog Implementation

public class SettingsDialogViewModel : BaseViewModel
{
public ObservableCollection<SettingCategoryViewModel> Categories { get; }

public async Task LoadSettingsAsync()
{
var settings = await _mepSettingsService.GetAllSettingsAsync();

// Group by category
var grouped = settings.GroupBy(s => s.Category ?? "General");

foreach (var group in grouped)
{
var category = new SettingCategoryViewModel
{
Name = group.Key,
Settings = new ObservableCollection<SettingViewModel>()
};

foreach (var setting in group)
{
var vm = CreateSettingViewModel(setting);
category.Settings.Add(vm);
}

Categories.Add(category);
}
}

private SettingViewModel CreateSettingViewModel(MepSettings setting)
{
return setting.DataType switch
{
"Boolean" => new BooleanSettingViewModel(setting),
"Integer" => new IntegerSettingViewModel(setting),
"String" => new StringSettingViewModel(setting),
"List" => new ListSettingViewModel(setting),
_ => new GenericSettingViewModel(setting)
};
}
}

Dynamic Control Generation

<DataTemplate DataType="{x:Type vm:BooleanSettingViewModel}">
<CheckBox IsChecked="{Binding Value}"
Content="{Binding DisplayName}"/>
</DataTemplate>

<DataTemplate DataType="{x:Type vm:IntegerSettingViewModel}">
<StackPanel>
<Label Content="{Binding DisplayName}"/>
<xctk:IntegerUpDown Value="{Binding Value}"
Minimum="{Binding MinValue}"
Maximum="{Binding MaxValue}"/>
</StackPanel>
</DataTemplate>

<DataTemplate DataType="{x:Type vm:ListSettingViewModel}">
<StackPanel>
<Label Content="{Binding DisplayName}"/>
<ComboBox ItemsSource="{Binding ListValues}"
SelectedValue="{Binding Value}"
DisplayMemberPath="Display"
SelectedValuePath="Value"/>
</StackPanel>
</DataTemplate>

Validation and Constraints

Setting Validation

public class SettingValidator
{
public ValidationResult Validate(MepSettings setting, object value)
{
var errors = new List<string>();

switch (setting.DataType)
{
case "Integer":
var intVal = (int)value;
if (setting.MinValue.HasValue && intVal < setting.MinValue)
errors.Add($"Value must be at least {setting.MinValue}");
if (setting.MaxValue.HasValue && intVal > setting.MaxValue)
errors.Add($"Value cannot exceed {setting.MaxValue}");
break;

case "String":
var strVal = value?.ToString() ?? "";
if (!string.IsNullOrEmpty(setting.ValidationRegex))
{
if (!Regex.IsMatch(strVal, setting.ValidationRegex))
errors.Add($"Value does not match required format");
}
if (setting.MaxLength.HasValue && strVal.Length > setting.MaxLength)
errors.Add($"Value exceeds maximum length of {setting.MaxLength}");
break;
}

return new ValidationResult
{
IsValid = !errors.Any(),
Errors = errors
};
}
}

Import/Export

Settings Backup

public async Task<string> ExportSettingsAsync()
{
var export = new SettingsExport
{
ExportDate = DateTime.Now,
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(),
Settings = Settings.Select(s => new ExportedSetting
{
Name = s.SettingName,
Value = s.SettingValue,
Scope = s.SettingScope
}).ToList()
};

return JsonConvert.SerializeObject(export, Formatting.Indented);
}

public async Task ImportSettingsAsync(string json)
{
var import = JsonConvert.DeserializeObject<SettingsExport>(json);

foreach (var setting in import.Settings)
{
await SetValueByScope(setting.Name, setting.Value, setting.Scope);
}
}

Benefits

  • Flexible configuration without code changes
  • User-specific preferences
  • Company-wide standardization
  • Easy backup and migration
  • Validation and constraints
  • Categorized organization