0

This is just for demo purposes. I will not actually cache passwords this way as I've received advice that Application Settings is not secure.

Problem Statement

I'm creating a login Windows Forms application that persists the string values entered into the Name and Password fields between application sessions using Application Settings. The form should load the Name and Password values that was entered from the previous session.

I ran into a problem where the fields fail to get saved and reloaded if I use Properties.Settings.Default.Save() to save those field values from a private method:

    // Save the current values of each field
    private void SaveSettings()
    {
        Properties.Settings.Default.Name = textBox1.Text;
        Properties.Settings.Default.Password = textBox2.Text;
        Properties.Settings.Default.Save();
    }

When the field values change, this is how `SaveSettings() would be called:

    // Saves ALL field values in the form whenever the field changes. 
    // Warning: This function gets called each time a character is added to the 'Name' field.
    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        SaveSettings();
        UpdateRichTextbox();
    }

    // Saves ALL field values in the form whenever the field changes. 
    // Warning: This function gets called each time a character is added to the 'Password' field.
    private void textBox2_TextChanged(object sender, EventArgs e)
    {
        SaveSettings();
        UpdateRichTextbox();
    }

UpdateRichTextbox() simply writes Name and Password to a rich text box in a formatted manner so that I can see their values:

    // Writes current values of each setting to Rich Textbox
    private void UpdateRichTextbox()
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine($"Name: {Properties.Settings.Default.Name}");
        sb.AppendLine($"Password: {Properties.Settings.Default.Password}");
        richTextBox1.Text = sb.ToString();
    }

Attempt

I made sure to set the scope for both Name and Password setting in Project Settings to 'User'. I tried calling Properties.Settings.Default.Upgrade() after .Save().

Name and Password persisted when I moved the content of SaveSettings() to the callbacks invoked when those fields are changed:

    // Saves ALL field values in the form whenever the field changes. 
    // Warning: This function gets called each time a character is added to the 'Name' field.
    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        Properties.Settings.Default.Name = textBox1.Text;
        Properties.Settings.Default.Save();
        UpdateRichTextbox();
    }

    // Saves ALL field values in the form whenever the field changes. 
    // Warning: This function gets called each time a character is added to the 'Password' field.
    private void textBox2_TextChanged(object sender, EventArgs e)
    {
        Properties.Settings.Default.Password = textBox2.Text;
        Properties.Settings.Default.Save();
        UpdateRichTextbox();
    }

I'm not sure why my first approach didn't work. How did the API devs intend for .Save() to be used?

Full Example

using System;
using System.Text;
using System.Windows.Forms;

namespace LoginForm
{
    public partial class Form1 : Form
    {
        // Called initially when the form application starts up
        public Form1()
        {
            InitializeComponent();
            LoadSettings();
        }

        // Load saved values to their respective field textboxes
        private void LoadSettings()
        {
            textBox1.Text = Properties.Settings.Default.Name;
            textBox2.Text = Properties.Settings.Default.Password;
        }

        // Writes current values of each setting to Rich Textbox
        private void UpdateRichTextbox()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"Name: {Properties.Settings.Default.Name}");
            sb.AppendLine($"Password: {Properties.Settings.Default.Password}");
            richTextBox1.Text = sb.ToString();
        }

        // Saves ALL field values in the form whenever the field changes. 
        // Warning: This function gets called each time a character is added to the 'Name' field.
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.Name = textBox1.Text;
            Properties.Settings.Default.Save();
            UpdateRichTextbox();
        }

        // Saves ALL field values in the form whenever the field changes. 
        // Warning: This function gets called each time a character is added to the 'Password' field.
        private void textBox2_TextChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.Password = textBox2.Text;
            Properties.Settings.Default.Save();
            UpdateRichTextbox();
        }
    }
}
MTV
  • 91
  • 9
  • If you changed the version of you app, go check in `[Drive:]\Users\[CurrentUser]\AppData\Local\[YouAppName]\URL\[Version]` how many `user.config` files you have there. – Jimi May 29 '19 at 16:34
  • I don't know how to change the version of my app. I checked that directory but there isn't a corresponding directory for my application (its name is `LoginForm`). The only config file that exists for this app is in `LoginForm\bin\debug` (the VS project folder, debug build output) called `LoginForm.exe.config` – MTV May 29 '19 at 17:06
  • 1
    `[Application].exe.config` is a *reflection* of `app.config`. Default values are stored there. The User values, those that can change at run-time, are store in the `user.config` file. Remove these 2 settings. Rebuild the Solution (not the project). Add back the two settings, without writing anything in the value field. Rebuild the Solution. Now save new settings values when the application is run. Go check in `AppData/Local` whether the `user.config` file has been created. – Jimi May 29 '19 at 18:08
  • I found 3 `user.config` files in `C:\Users\[CurrentUser]\AppData\Local\Microsoft\LoginForm.exe_Url_*`. There are 3 directories named `LoginForm.exe_Url_*` (* denoting a string of alphanumerics). It looks like a new directory with another `user.config` file is created each time I modify a setting entry and rebuild the solution. Settings are store in plain text - yikes, not a good way to store passwords at all! – MTV May 29 '19 at 20:27
  • I see the problem with my code now. When the application restarts, `Form1()` calls `LoadSettings()` which, when executing `textBox1.Text = Properties.Settings.Default.Name;`, modifies the `textBox1.Text` property causing the `textBox1_TextChanged` handler to run. That handler function calls `SaveSettings()` which then **overwrites** what I had previously stored in `Properties.Settings.Default.Password` using what's currently in the `textBox2.Text` field (which is empty because the application restarted). – MTV May 29 '19 at 20:54
  • 1
    Good you found out the recursive call :) Probably, the TextChanged event is not the right *place* where to call the SaveSettings method (it very rarely is the right event for anything :). Passwords can be stored as text. Well, the salted hash of a password converted to a base64 string. – Jimi May 29 '19 at 20:58

1 Answers1

0

When the application restarts, Form1() calls LoadSettings() which, when executing textBox1.Text = Properties.Settings.Default.Name, modifies the textBox1.Text property causing the textBox1_TextChanged handler to run. That handler function calls SaveSettings() which then overwrites what had previously been stored in Properties.Settings.Default.Password using what's currently in the textBox2.Text field. Because the application had restarted, textBox2.Text is empty and thus the old value of Properties.Settings.Default.Password was overwritten with an empty string.

Thank you Jimi for helping me locate the user.config file and for his sage advice on what not to do in a textChanged event. I can't see a good reason why you'd need to do something every time a character changes in your textBox either -- makes sense why most applications use an Apply or Save button. Seeing this file change as I step-debugged helped me identify the problem with my code.

MTV
  • 91
  • 9