{"id":1524,"date":"2012-02-01T07:52:31","date_gmt":"2012-02-01T13:52:31","guid":{"rendered":"http:\/\/www.wiredprairie.us\/blog\/?p=1524"},"modified":"2012-02-01T07:52:33","modified_gmt":"2012-02-01T13:52:33","slug":"alternative-to-applicationsettings-in-net","status":"publish","type":"post","link":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1524","title":{"rendered":"Alternative to ApplicationSettings in .NET"},"content":{"rendered":"

After dealing with lost settings, an unclear upgrade path, and my own confusion surrounding the magic of Settings<\/a> in a .NET client application, I decided to build my own. <\/p>\n

You\u2019re probably familiar with this UI in Visual Studio. It hasn\u2019t changed much since it was first created:<\/p>\n

\"image\"<\/p>\n

A list of properties, data type, scope and a default value. Admittedly, it makes things simple. However, with my WPF .NET application that I created a few years ago (SnugUp<\/a>), I\u2019ve always been troubled by the magic of the settings. It was too easy to get in a situation where a user would loose their settings doing uninstalls, reinstalls, upgrades. <\/p>\n

While I\u2019m sure it\u2019s possible to make the built in settings classes to work, it wasn\u2019t worth the effort for me to understand them and learn what the nuances of where they\u2019re placed, how to do a decent upgrade, how not to loose them, etc. <\/p>\n

In the SnugUp WPF UI, the code uses a two-way bindings to directly edit the settings of the application (like: "{Binding AppSettings.DebugMode}"). It was simple, and all I needed. It\u2019s handy that ApplicationSettingsBase implements the INotifyPropertyChanged interface which WPF needs for simple two-way data bindings.<\/p>\n

My solution, which I admit is heavier than the original as it requires a large additional assembly is to use JSON.NET<\/a> as the serializer\/deserializer for a new settings class I created.<\/p>\n

So, the basic pattern:<\/p>\n

\n
\n
   1:<\/span> public<\/span> class<\/span> ApplicationSettings : INotifyPropertyChanged<\/pre>\n

<\/p>\n

   2:<\/span> {<\/pre>\n

<\/p>\n

   3:<\/span>     public<\/span> event<\/span> PropertyChangedEventHandler PropertyChanged;<\/pre>\n

<\/p>\n

   4:<\/span>  <\/pre>\n

<\/p>\n

   5:<\/span>     private<\/span> bool<\/span> _debugMode;<\/pre>\n

<\/p>\n

   6:<\/span>     private<\/span> string<\/span> _albumNameFormat;<\/pre>\n

<\/p>\n

   7:<\/span>     private<\/span> string<\/span> _extraFileExtensions;<\/pre>\n

<\/p>\n

   8:<\/span>     private<\/span> bool<\/span> _automaticRun;<\/pre>\n

<\/p>\n

   9:<\/span>     private<\/span> string<\/span> _galleryCreationSubCategory;<\/pre>\n

<\/p>\n

  10:<\/span>     private<\/span> bool<\/span> _filenameOnlyCheck;<\/pre>\n

<\/div>\n<\/div>\n

Properties created the standard way for INotifyPropertyChaged:<\/p>\n

\n
\n
   1:<\/span> private<\/span> DateTime _nextUpdateCheck;<\/pre>\n

<\/p>\n

   2:<\/span> public<\/span> DateTime NextUpdateCheck<\/pre>\n

<\/p>\n

   3:<\/span> {<\/pre>\n

<\/p>\n

   4:<\/span>     get { return<\/span> _nextUpdateCheck; }<\/pre>\n

<\/p>\n

   5:<\/span>     set<\/pre>\n

<\/p>\n

   6:<\/span>     {<\/pre>\n

<\/p>\n

   7:<\/span>         if<\/span> (_nextUpdateCheck != value<\/span>)<\/pre>\n

<\/p>\n

   8:<\/span>         {<\/pre>\n

<\/p>\n

   9:<\/span>             _nextUpdateCheck = value<\/span>;<\/pre>\n

<\/p>\n

  10:<\/span>             RaisePropertyChanged("NextUpdateCheck"<\/span>);<\/pre>\n

<\/p>\n

  11:<\/span>         }<\/pre>\n

<\/p>\n

  12:<\/span>     }<\/pre>\n

<\/p>\n

  13:<\/span> }<\/pre>\n

<\/div>\n<\/div>\n

I wanted a predictable path for storing settings (so it would be easy to document and backup for users). I used the AssemblyCompany attribute and the AssemblyProduct attribute as the folder names:<\/p>\n

\n
\n
   1:<\/span> internal<\/span> static<\/span> string<\/span> GetSettingsDirectory()<\/pre>\n

<\/p>\n

   2:<\/span> {<\/pre>\n

<\/p>\n

   3:<\/span>     string<\/span> path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);<\/pre>\n

<\/p>\n

   4:<\/span>     var attrs = Assembly.GetEntryAssembly().GetCustomAttributes(typeof<\/span>(AssemblyCompanyAttribute), false<\/span>);<\/pre>\n

<\/p>\n

   5:<\/span>     if<\/span> (attrs.Length == 1)<\/pre>\n

<\/p>\n

   6:<\/span>     {<\/pre>\n

<\/p>\n

   7:<\/span>         path = Path.Combine(path, ((AssemblyCompanyAttribute)attrs[0]).Company);<\/pre>\n

<\/p>\n

   8:<\/span>  <\/pre>\n

<\/p>\n

   9:<\/span>     }<\/pre>\n

<\/p>\n

  10:<\/span>     attrs = Assembly.GetEntryAssembly().GetCustomAttributes(typeof<\/span>(AssemblyProductAttribute), false<\/span>);<\/pre>\n

<\/p>\n

  11:<\/span>     if<\/span> (attrs.Length == 1)<\/pre>\n

<\/p>\n

  12:<\/span>     {<\/pre>\n

<\/p>\n

  13:<\/span>         path = Path.Combine(path, ((AssemblyProductAttribute)attrs[0]).Product);<\/pre>\n

<\/p>\n

  14:<\/span>     }<\/pre>\n

<\/p>\n

  15:<\/span>     return<\/span> path;              <\/pre>\n

<\/p>\n

  16:<\/span> }<\/pre>\n

<\/div>\n<\/div>\n

In this WPF application, in the AssemblyInfo.cs file, the attributes are set as follows:<\/p>\n

\n
\n
   1:<\/span> [assembly: AssemblyCompany("WiredPrairie.us"<\/span>)]<\/pre>\n

<\/p>\n

   2:<\/span> [assembly: AssemblyProduct("SnugUp"<\/span>)]<\/pre>\n

<\/div>\n<\/div>\n

On my machine, that maps to this path:<\/p>\n

d:\\Users\\Aaron\\AppData\\Roaming\\WiredPrairie.us\\SnugUp\\<\/strong><\/p>\n

Loading settings then is straightforward using JSON.NET:<\/p>\n

\n
\n
   1:<\/span> public<\/span> static<\/span> ApplicationSettings Load(string<\/span> filename)<\/pre>\n

<\/p>\n

   2:<\/span> {<\/pre>\n

<\/p>\n

   3:<\/span>     ApplicationSettings settings = null<\/span>;<\/pre>\n

<\/p>\n

   4:<\/span>     var directory = GetSettingsDirectory();<\/pre>\n

<\/p>\n

   5:<\/span>     var path = Path.Combine(directory, filename);<\/pre>\n

<\/p>\n

   6:<\/span>  <\/pre>\n

<\/p>\n

   7:<\/span>     if<\/span> (File.Exists(path))<\/pre>\n

<\/p>\n

   8:<\/span>     {<\/pre>\n

<\/p>\n

   9:<\/span>         string<\/span> fileData = File.ReadAllText(path);<\/pre>\n

<\/p>\n

  10:<\/span>         try<\/span><\/pre>\n

<\/p>\n

  11:<\/span>         {<\/pre>\n

<\/p>\n

  12:<\/span>             settings = JsonConvert.DeserializeObject<ApplicationSettings>(fileData, new<\/span> JsonSerializerSettings { });<\/pre>\n

<\/p>\n

  13:<\/span>         }<\/pre>\n

<\/p>\n

  14:<\/span>         catch<\/span> { }<\/pre>\n

<\/p>\n

  15:<\/span>     }<\/pre>\n

<\/p>\n

  16:<\/span>     if<\/span> (settings == null<\/span>)<\/pre>\n

<\/p>\n

  17:<\/span>     {<\/pre>\n

<\/p>\n

  18:<\/span>         settings = new<\/span> ApplicationSettings();<\/pre>\n

<\/p>\n

  19:<\/span>         SetDefaults(settings);<\/pre>\n

<\/p>\n

  20:<\/span>         \/\/ initialize settings once<\/span><\/pre>\n

<\/p>\n

  21:<\/span>         Save(settings, filename);<\/pre>\n

<\/p>\n

  22:<\/span>     }<\/pre>\n

<\/p>\n

  23:<\/span>     return<\/span> settings;<\/pre>\n

<\/p>\n

  24:<\/span> }<\/pre>\n

<\/div>\n<\/div>\n

In my code, if the settings file didn\u2019t exist or fails to serialize into something meaningful, a new settings file is created with a few defaults. (I haven\u2019t decided what to do when there\u2019s an exception when reading the file, hence the empty catch).<\/p>\n

Saving the settings is just as easy:<\/p>\n

\n
\n
   1:<\/span> public<\/span> static<\/span> void<\/span> Save(ApplicationSettings settings, string<\/span> filename)<\/pre>\n

<\/p>\n

   2:<\/span> {<\/pre>\n

<\/p>\n

   3:<\/span>     Debug.Assert(settings != null<\/span>);<\/pre>\n

<\/p>\n

   4:<\/span>     var directory = GetSettingsDirectory();<\/pre>\n

<\/p>\n

   5:<\/span>     var path = Path.Combine(directory, filename);<\/pre>\n

<\/p>\n

   6:<\/span>  <\/pre>\n

<\/p>\n

   7:<\/span>     JsonConvert.SerializeObject(settings);<\/pre>\n

<\/p>\n

   8:<\/span>  <\/pre>\n

<\/p>\n

   9:<\/span>     if<\/span> (!Directory.Exists(directory))<\/pre>\n

<\/p>\n

  10:<\/span>     {<\/pre>\n

<\/p>\n

  11:<\/span>         Directory.CreateDirectory(directory);<\/pre>\n

<\/p>\n

  12:<\/span>     }<\/pre>\n

<\/p>\n

  13:<\/span>  <\/pre>\n

<\/p>\n

  14:<\/span>     var fileData = JsonConvert.SerializeObject(settings, Formatting.Indented, new<\/span> JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });<\/pre>\n

<\/p>\n

  15:<\/span>     try<\/span><\/pre>\n

<\/p>\n

  16:<\/span>     {<\/pre>\n

<\/p>\n

  17:<\/span>         using<\/span> (StreamWriter writer = File.CreateText(path))<\/pre>\n

<\/p>\n

  18:<\/span>         {<\/pre>\n

<\/p>\n

  19:<\/span>             writer.Write(fileData);<\/pre>\n

<\/p>\n

  20:<\/span>             writer.Close();<\/pre>\n

<\/p>\n

  21:<\/span>         }<\/pre>\n

<\/p>\n

  22:<\/span>     }<\/pre>\n

<\/p>\n

  23:<\/span>     catch<\/span> { }<\/pre>\n

<\/p>\n

  24:<\/span> }<\/pre>\n

<\/div>\n<\/div>\n

The SerializeObject method returns a string which is then written to a file using a StreamWriter. <\/p>\n

I added a Save method to the instance of the ApplicationSettings:<\/p>\n

\n
\n
   1:<\/span> public<\/span> void<\/span> Save()<\/pre>\n

<\/p>\n

   2:<\/span> {<\/pre>\n

<\/p>\n

   3:<\/span>     ApplicationSettings.Save(this<\/span>);<\/pre>\n

<\/p>\n

   4:<\/span> }<\/pre>\n

<\/div>\n<\/div>\n

This preserved the functionality that exists in the built in Settings support in .NET (which was being used in my application). <\/p>\n

By keeping all of the property names the same and making a few tweaks to the types of some fields in my application, I had swapped out the entire \u201csettings\u201d infrastructure in about 45 minutes. <\/p>\n

I\u2019m planning some other JSON activities within my application, so the overhead of using JSON.NET is acceptable. <\/p>\n

The best part of this alternative is that there isn\u2019t any magic. It\u2019s all easy to manage. Further, I can easily modify my installer to properly handle\/update, etc., the settings file with just a few clicks. <\/p>\n

I\u2019m not going back to the built-in .NET settings support again. I\u2019ve learned my lesson.  \"Smile\"<\/p>\n

What have you done for \u201cuser\u201d settings?<\/p>\n","protected":false},"excerpt":{"rendered":"

After dealing with lost settings, an unclear upgrade path, and my own confusion surrounding the magic of Settings in a .NET client application, I decided to build my own. You\u2019re probably familiar with this UI in Visual Studio. It hasn\u2019t changed much since it was first created: A list of properties, data type, scope and […]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[4],"tags":[55,67,68,16],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pd5QIe-oA","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":646,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/646","url_meta":{"origin":1524,"position":0},"title":"Announcement: SnugUp updated","date":"January 8, 2009","format":false,"excerpt":"I created a medium sized .NET WPF based application a while back which helps keep a series of folders with images synchronized to SmugMug with one click of a button. I just made a few fixes in the past couple of days and uploaded a new version. Changes: Fixed error\u2026","rel":"","context":"In "General"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2009\/01\/image1.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1449,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1449","url_meta":{"origin":1524,"position":1},"title":".NET API for Nest Thermostat","date":"January 9, 2012","format":false,"excerpt":"I just finished a preliminary read-only (think version 0.1) wrapper around the Nest Thermostat API that is used by their mobile phone and web applications. As Nest doesn\u2019t have a formal API yet, the code could break at any time and may not be suitable for any use. However, it\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"SNAGHTML88bff0b3","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/01\/SNAGHTML88bff0b3_thumb.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":18,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/18","url_meta":{"origin":1524,"position":2},"title":"SnugUp – a SmugMug Mass Uploader","date":"March 19, 2008","format":false,"excerpt":"I put the finishing touches on a web page for a new application I just finished, SnugUp. It's only useful if you have a SmugMug account, which I'd highly recommend if you're serious about photos. Sign up here. It's written using .NET 3.5 -- all WPF (except for a file\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"image","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/image.axd?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":172,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/172","url_meta":{"origin":1524,"position":3},"title":"Silverlight — it ain’t your papa’s WPF","date":"May 1, 2008","format":false,"excerpt":"John Gossman, architect of the WPF team (desktop and subset taht supplies UI framework for Silverlight), discusses a few of the pain points developers and designers are facing today with WPF to Silverlight portability. From his post: The above example is a bit of a special case, and I don't\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":668,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/668","url_meta":{"origin":1524,"position":4},"title":"Silverlight Stars\/Sparkles","date":"January 18, 2009","format":false,"excerpt":"I was in a \u201cstar\u201d mood this afternoon and created this Silverlight 2.0 demonstration. For rendering it uses the CompositionTarget.Rendering method (the easiest way to control dynamic animations such as this). It also uses the VisualStateManager in a variety of places to control the user interface. I\u2019ve become a big\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2009\/01\/image7.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":891,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/891","url_meta":{"origin":1524,"position":5},"title":"Resource Intensive WPF Progress Bar (animation)","date":"January 25, 2010","format":false,"excerpt":"I\u2019m using a progress bar in a small WPF application I\u2019m working on and noticed that the Private Working Set for my application seemed higher than I expected. My application, once simplified down to it\u2019s most basic element, consisted of: Window, Grid, ProgressBar On my Windows 7 x64 machine, running\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1524"}],"collection":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/comments?post=1524"}],"version-history":[{"count":1,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1524\/revisions"}],"predecessor-version":[{"id":1525,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1524\/revisions\/1525"}],"wp:attachment":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/media?parent=1524"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/categories?post=1524"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/tags?post=1524"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}