In .NET world storing data into xml configuration files is very common scenario. While, web apps (Asp.Net) use web.config, desktops use app.config (renamed to <FullExecutableFilename>.exe.config at runtime).
Every application sooner or later needs to collect, persist and restore some data (settings) from (to) end user. While web apps store these information almost always into database, desktops app store these settings frequently to XML based configuration files.
Problem.
For one of my prototypes (POC) I just wanted to create simple, persistence agnostic settings service. Having some difficulties playing together .NET Core and System.Configuration
I decided to go my own way and create simple persisting data service. Let’s start!
Simple settings service.
My simple settings service definition.
1 2 3 4 5 6 7 |
public interface ISettings { string PersonalAccessToken { get; set; } string OrganizationName { get; set; } void Store(); void Load(); } |
And default implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using Newtonsoft.Json; using System.IO; namespace Jenx.AzureDevOps.WpfClient.Settings { public class AppSettingsService : ISettings { private readonly string _configFilePath; public AppSettingsService(string configFilePath) { _configFilePath = configFilePath; } public string PersonalAccessToken { get; set; } public string OrganizationName { get; set; } public void Store() { try { var configFolder = Path.GetDirectoryName(_configFilePath); if (!Directory.Exists(configFolder)) Directory.CreateDirectory(configFolder); File.WriteAllText(_configFilePath, JsonConvert.SerializeObject(this)); } catch { //log and rethrow } } public void Load() { try { var data = JsonConvert.DeserializeObject<AppSettingsService>(File.ReadAllText(_configFilePath)); OrganizationName = data.OrganizationName; PersonalAccessToken = data.PersonalAccessToken; } catch { //log and rethrow } } } } |
I almost always use Prism library to easily decouple my app into smaller more maintainable peaces. So, in my Prism registration section I just register my settings service, similar as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public partial class App { // removed for bravity protected override Window CreateShell() { return (Window)Container.Resolve<IShellView>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { // other registrations, removed for bravity containerRegistry.RegisterInstance<ISettings>( new AppSettingsService( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Jenx.Azure.DevOps.WpfClient", "appSettings.json"))); // other registrations, removed for bravity containerRegistry.Register<IShellView, ShellView>(); } // other app class code } |
And I could use my settings cross application, like:
1 2 3 4 5 6 7 |
private void LoadSettings() { // load settings first var settings = Container.Resolve<ISettings>(); settings.Load(); // etc.... } |
or in any ViewModel by DI pattern, like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
using Jenx.AzureDevOps.Client; using Jenx.AzureDevOps.WpfClient.Logger; using Jenx.AzureDevOps.WpfClient.Services; using Jenx.AzureDevOps.WpfClient.Settings; using Prism.Commands; using Prism.Events; using Prism.Regions; using System.Windows.Input; namespace Jenx.AzureDevOps.WpfClient.ViewModels { public class SettingsViewModel : BaseViewModel, INavigationAware { private readonly ISettings _settings; private readonly IAzureDevOpsSettings _azureDevOpsSettings; public SettingsViewModel( IRegionManager regionManager, ISettings settings, IAzureDevOpsSettings azureDevOpsSettings, IDialogService dialogService, IEventAggregator eventAggregator, IMessageService messageService, IApplicationLogger logger) : base(regionManager, dialogService, eventAggregator, messageService) { _settings = settings; _azureDevOpsSettings = azureDevOpsSettings; } #region Commands public ICommand SaveSettingsCommand => new DelegateCommand(SaveSettings); #endregion Commands #region Properties private string _organizationName; public string OrganizationName { get => _organizationName; set { _organizationName = value; RaisePropertyChanged(); } } private string _token; public string Token { get => _token; set { _token = value; RaisePropertyChanged(); } } #endregion Properties #region Privates private void SaveSettings() { try { IsBusy = true; _settings.OrganizationName = OrganizationName; _settings.PersonalAccessToken = Token; _settings.Store(); _azureDevOpsSettings.OrganizationName = _settings.OrganizationName; _azureDevOpsSettings.PersonalAccessToken = _settings.PersonalAccessToken; ShowMessage("Settings saved"); } catch { ShowMessage("Failed saving settings"); } finally { IsBusy = false; } } #endregion Privates public void OnNavigatedTo(NavigationContext navigationContext) { try { IsBusy = true; OrganizationName = _settings.OrganizationName; Token = _settings.PersonalAccessToken; } finally { IsBusy = false; } } public bool IsNavigationTarget(NavigationContext navigationContext) { return false; } public void OnNavigatedFrom(NavigationContext navigationContext) { } } } |
Result, from GUI perspective, looks like:
Settings (json file) storage looks like:
1 2 3 4 |
{ "PersonalAccessToken": "mlx4sa5uiq2uc6me7ajhwzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "OrganizationName": "jenx-----" } |
It makes sense to put this file into Environment.SpecialFolder.LocalApplicationData
location, because this is end-user working location where he/she can read write files without problems.
Conclusion.
Application settings are very important part of any application.
Because of architectural specifics, Asp.Net web application almost always only read config settings from web.config and store any user settings into database.
In contrary, desktop apps very often use read/write settings files to persist user settings. Very often, .settings files were used to store application of user settings.
In this blog post I wanted to create simple but generic settings service to store end-user settings and persist it in json file.