none
How to access an application user setting from an assembly? RRS feed

  • Question

  • I'd like to construct a base class and derived classes such that the derived classes specify a particular user setting (which is a string property) in such a fashion as to allow the base class to read from and write to the property that was specified by the derived class.

    Here's the usage: I am creating a base class from which UI windows are being derived (and yes, I know about the problems of deriving from Window and have gotten around them). Each of the derived windows has an asscociated string property in the project's user settings. The base class will need to access that string property during window initialization and again at window closure.

    The obvious problem with the above is that the base class won't know which property string is the right one. It will need to be told which user string in the application settings is the right one. Somehow the derived class will need to pass this information down to the base, ideally in the constructor.

    I've been able to construct a work around such that there is a virtual property in the base class for which an override is present in the derived class but the end result is a lot more in the way of keystrokes than I'd like to do. Any ideas?


    Richard Lewis Haggard


    Friday, May 3, 2013 6:15 PM

Answers

  • My apologies. I worded the initial question very poorly. Let me restate it.

    The problem is, how can one access an application user setting from an assembly?

    The usage here is that a class that inherits from Window is in an assembly. This class is in turn the base for a number of other Window classes consumed in an application. Each of the application child classes needs to read from and write to a user setting that lives in app.exe.config. Since these actions are the same for all of the child windows the obvious place for the duplicated functionality is in the base class.

    The problem is that WPF makes a class that knows how to access the user settings but this class lives in the application and the assembly has no way of knowing about it.

    The original idea I had of somehow accessing the class that lives in the application and knows how gain access to the user settings through get; and set; turned out to be possible but required a fair number of keystrokes in each of the child window classes. However, going directly to the application configuration file itself turned out to be a relatively easy problem to solve, involving only a few dozen lines of code.

    Breaking down the problem into smaller uinits, we get something that looks like this:

    • Pass name of node that contains user settings and the name of the target property from child to base.
    • Find the application.exe.config file.
    • Access the user settings group.
    • Access the parent of the user setting.
    • Access the user setting itself.

    In my case I want to not only read the value but write it as well so I made a method to find and return the user setting that both the read and write functions could consume.

    Just as background, here's the basic layout of the app.exe.config file in its original unmodified state: 'PlacementMain' is the example user setting to be read and modified.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <configSections>
            <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
                <section name="CSU.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
            </sectionGroup>
        </configSections>
        <userSettings>
            <CSU.Properties.Settings>
                <setting name="PlacementMain" serializeAs="String" />
            </CSU.Properties.Settings>
        </userSettings>
    </configuration>

    There are a couple of important things to note here.

    1) The node tag of <CSU.Properties.Settings> is not a constant. Different applications will have a different tag. If the idea is to create code that will be reused in multiple applications then this is going to be one of those things that the assembly is unavoidably going to be ignorant of and so it will be necessary for the consumer of this functionality to pass that particular bit of information in.

    2) Likewise, the setting's name (<setting name=PlacementMain etc in the example) is something that the application's consumer is going to have to pass back to the base class code.

    Both of these requirements are trivial to meet. In my case I just set up a constructor override that passed both of these strings down to the base from the child.

    First step - passing user settings parent node and target property name from child to base. The base class declares a constructor that accepts the two strings.

    // Base class
    // Lots of trivia cut from this example. . . .
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using PicoClassLibrary;
    using System.Reflection;
    using System.Configuration;
    
    namespace CSU
    {
        public class BaseWindow : Window
        {
            protected string PlacementStringName { get; set; }
            protected string UserSettingsSectionName { get; set; }
    
    
            // Class constructors --------------
    
            public BaseWindow()  {  }
    
    
            public BaseWindow( string strUserSettingsSectionName, string strPlacement )
            {
                PlacementStringName = strPlacement;
                UserSettingsSectionName = strUserSettingsSectionName;
            }
    
    <snip>
        }
    }
    
    
    
    
    // And up in the child, we derive from the base 
    // (there's lots of other little things to do in
    // the XAML to derive your Window from a base
    // but that's another problem which we will not
    // clutter up this particular item with).
    
        public partial class MainWindow : BaseWindow
        {
            public MainWindow()
                : base( "CSU.Properties.Settings", "PlacementMain" )
            {
                InitializeComponent();
            }
        }
    

    2) Accessing the user setting in the app.exe.config file. When it is time to either read or write the setting, there are many common factors. Reading just needs the setting element. Writing needs the supporting objects as well so when I constructed the method to do this, I made provisions for passing back enough information that the caller would be able to update the element.

            /// <summary>
            /// Return the app.exe.config element that corresponds to a particular
            /// user setting.
            /// </summary>
            /// <param name="config">Where the Configuration created to access the element will be returned.</param>
            /// <param name="clientSection">Where the ClientSettingsSection created to access the element will be returned.</param>
            /// <param name="settingElement">Where the SettingElement itself will be returned.</param>
            /// <param name="strUserSetting">Key name of the element to be returned.</param>
            /// <returns></returns>
            protected bool GetUserSetting( string strUserSetting, ref Configuration config, ref ClientSettingsSection clientSection, ref SettingElement settingElement )
            {
                bool bReturn = false;
    
                do
                {
                    try
                    {
                        if ( string.IsNullOrEmpty( strUserSetting ) )
                            break;
    
                        // Gain access to the app.exe.config file.
                        string strPath = System.Reflection.Assembly.GetEntryAssembly().Location;
    
                        // Can we access the user settings group?
                        config = ConfigurationManager.OpenExeConfiguration( strPath );
                        if ( config == null )
                            break;
    
                        ConfigurationSectionGroup group = config.SectionGroups[@"userSettings"];
                        if ( group == null )
                            break;
    
                        clientSection = group.Sections[UserSettingsSectionName] as ClientSettingsSection;
                        if ( clientSection == null )
                            break;
    
                        // There is a direct accessor to the particular element we are targeting BUT it is 
                        // a protected member. Oddly, the iteration across the collection is public so we
                        // can use that to search for the element.
                        foreach ( SettingElement se in clientSection.Settings )
                        {
                            if ( se.Name == PlacementStringName )
                            {
                                settingElement = se;
                                break;
                            }
                        }
    
                        // If no matching target user setting found then quit not.
                        if ( settingElement == null )
                            break;
    
                        // Successful search.
                        bReturn = true;
                    }
                    catch ( Exception ex )
                    {
                        Console.WriteLine( ex.ToString() );
                    }
    
                } while ( false );
    
                return bReturn;
            }
    
    
    

    Consuming the above to read is trivial.

                do
                {
                    try
                    {
                        // Gain access to the element that corresponds to the
                        // user setting in the config file.
                        Configuration config = null;
                        ClientSettingsSection clientSection = null;
                        SettingElement settingElement = null;
    
                        if ( !GetUserSetting( PlacementStringName, ref config, ref clientSection, ref settingElement ) )
                            break;
    
                        // A setting exists and was returned. If it does not have a value then quite now
                        // else apply the placement.
                        if ( string.IsNullOrEmpty( settingElement.Value.ValueXml.InnerText ) )
                        {
                            // Did not find the setting. Do nothing.
                            break;
                        }
                        else
                        {
                            // 'SetPlacement' is an extension
                            // method that consumes the string.
                            // It is not included in this example.
                            this.SetPlacement( settingElement.Value.ValueXml.InnerText );
                         }
                    }
                    catch ( Exception ex )
                    {
                        Console.WriteLine( ex.ToString() );
                    }
                } while ( false );
    

    Updating the user setting is only a little more complicated. In the following example a string is returned from a Window extension method that is not included in this code since the idea is to demonstrate how to access user settings that live in the application's configuration file, which the extension method has nothing to do with.

                do
                {
                    try
                    {
                        // Gain access to the element 
                        // that corresponds to the
                        // user setting in the config file.
                        Configuration config = null;
                        ClientSettingsSection clientSection = null;
                        SettingElement settingElement = null;
    
                        if (!GetUserSetting( PlacementStringName, ref config, ref clientSection, ref settingElement ))
                            break;
    
                        // First remove and then add the 
                        // element. Cannot simply update the 
                        // existing element.
                        clientSection.Settings.Remove( settingElement );
                        settingElement.Value.ValueXml.InnerText = this.GetPlacement();
                        clientSection.Settings.Add( settingElement );
                        config.Save( ConfigurationSaveMode.Full );
                    }
                    catch ( Exception ex )
                    {
                        Console.WriteLine( ex.ToString() );
                    }
    
                } while ( false );
    
    In short, you cannot directly re-use the application.exe.config user setting directly through its existing get; set; functionality without going through a lot of trouble and utilizing delegates or equally large numbers of keystrokes on each of the derived child classes. You can, however, get at the target properties directly in the base class with only a small amount of extra code (two strings passed on a constructor to bae class).

    Richard Lewis Haggard

    Monday, May 6, 2013 10:01 PM

All replies

  • It occurs to me that I need to specify an additional limitation, that the window base class is not in the same assembly as its derived class. Had it been then the problem becomes very simple. Properties can be directly accessed in the subclass as Settings.Default[ strPropName ];

    Richard Lewis Haggard

    Friday, May 3, 2013 9:23 PM
  • Hi Richard,

    Please have a look at below thread to see if it helps.

    Accessing a property of derived class from the base class in C#

    http://stackoverflow.com/questions/210601/accessing-a-property-of-derived-class-from-the-base-class-in-c-sharp

    Good day.


    Bob Shen
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, May 6, 2013 7:21 AM
    Moderator
  • Thanks for taking the time to respond but I was interested in user settings properties, not class properties. There is no issue with derived class properties.

         Example: Settings.Default.PlacementString
         where 'PlaementString' is a setting that was added through project properties => Settings.


    Richard Lewis Haggard

    Monday, May 6, 2013 1:50 PM
  • My apologies. I worded the initial question very poorly. Let me restate it.

    The problem is, how can one access an application user setting from an assembly?

    The usage here is that a class that inherits from Window is in an assembly. This class is in turn the base for a number of other Window classes consumed in an application. Each of the application child classes needs to read from and write to a user setting that lives in app.exe.config. Since these actions are the same for all of the child windows the obvious place for the duplicated functionality is in the base class.

    The problem is that WPF makes a class that knows how to access the user settings but this class lives in the application and the assembly has no way of knowing about it.

    The original idea I had of somehow accessing the class that lives in the application and knows how gain access to the user settings through get; and set; turned out to be possible but required a fair number of keystrokes in each of the child window classes. However, going directly to the application configuration file itself turned out to be a relatively easy problem to solve, involving only a few dozen lines of code.

    Breaking down the problem into smaller uinits, we get something that looks like this:

    • Pass name of node that contains user settings and the name of the target property from child to base.
    • Find the application.exe.config file.
    • Access the user settings group.
    • Access the parent of the user setting.
    • Access the user setting itself.

    In my case I want to not only read the value but write it as well so I made a method to find and return the user setting that both the read and write functions could consume.

    Just as background, here's the basic layout of the app.exe.config file in its original unmodified state: 'PlacementMain' is the example user setting to be read and modified.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <configSections>
            <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
                <section name="CSU.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
            </sectionGroup>
        </configSections>
        <userSettings>
            <CSU.Properties.Settings>
                <setting name="PlacementMain" serializeAs="String" />
            </CSU.Properties.Settings>
        </userSettings>
    </configuration>

    There are a couple of important things to note here.

    1) The node tag of <CSU.Properties.Settings> is not a constant. Different applications will have a different tag. If the idea is to create code that will be reused in multiple applications then this is going to be one of those things that the assembly is unavoidably going to be ignorant of and so it will be necessary for the consumer of this functionality to pass that particular bit of information in.

    2) Likewise, the setting's name (<setting name=PlacementMain etc in the example) is something that the application's consumer is going to have to pass back to the base class code.

    Both of these requirements are trivial to meet. In my case I just set up a constructor override that passed both of these strings down to the base from the child.

    First step - passing user settings parent node and target property name from child to base. The base class declares a constructor that accepts the two strings.

    // Base class
    // Lots of trivia cut from this example. . . .
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using PicoClassLibrary;
    using System.Reflection;
    using System.Configuration;
    
    namespace CSU
    {
        public class BaseWindow : Window
        {
            protected string PlacementStringName { get; set; }
            protected string UserSettingsSectionName { get; set; }
    
    
            // Class constructors --------------
    
            public BaseWindow()  {  }
    
    
            public BaseWindow( string strUserSettingsSectionName, string strPlacement )
            {
                PlacementStringName = strPlacement;
                UserSettingsSectionName = strUserSettingsSectionName;
            }
    
    <snip>
        }
    }
    
    
    
    
    // And up in the child, we derive from the base 
    // (there's lots of other little things to do in
    // the XAML to derive your Window from a base
    // but that's another problem which we will not
    // clutter up this particular item with).
    
        public partial class MainWindow : BaseWindow
        {
            public MainWindow()
                : base( "CSU.Properties.Settings", "PlacementMain" )
            {
                InitializeComponent();
            }
        }
    

    2) Accessing the user setting in the app.exe.config file. When it is time to either read or write the setting, there are many common factors. Reading just needs the setting element. Writing needs the supporting objects as well so when I constructed the method to do this, I made provisions for passing back enough information that the caller would be able to update the element.

            /// <summary>
            /// Return the app.exe.config element that corresponds to a particular
            /// user setting.
            /// </summary>
            /// <param name="config">Where the Configuration created to access the element will be returned.</param>
            /// <param name="clientSection">Where the ClientSettingsSection created to access the element will be returned.</param>
            /// <param name="settingElement">Where the SettingElement itself will be returned.</param>
            /// <param name="strUserSetting">Key name of the element to be returned.</param>
            /// <returns></returns>
            protected bool GetUserSetting( string strUserSetting, ref Configuration config, ref ClientSettingsSection clientSection, ref SettingElement settingElement )
            {
                bool bReturn = false;
    
                do
                {
                    try
                    {
                        if ( string.IsNullOrEmpty( strUserSetting ) )
                            break;
    
                        // Gain access to the app.exe.config file.
                        string strPath = System.Reflection.Assembly.GetEntryAssembly().Location;
    
                        // Can we access the user settings group?
                        config = ConfigurationManager.OpenExeConfiguration( strPath );
                        if ( config == null )
                            break;
    
                        ConfigurationSectionGroup group = config.SectionGroups[@"userSettings"];
                        if ( group == null )
                            break;
    
                        clientSection = group.Sections[UserSettingsSectionName] as ClientSettingsSection;
                        if ( clientSection == null )
                            break;
    
                        // There is a direct accessor to the particular element we are targeting BUT it is 
                        // a protected member. Oddly, the iteration across the collection is public so we
                        // can use that to search for the element.
                        foreach ( SettingElement se in clientSection.Settings )
                        {
                            if ( se.Name == PlacementStringName )
                            {
                                settingElement = se;
                                break;
                            }
                        }
    
                        // If no matching target user setting found then quit not.
                        if ( settingElement == null )
                            break;
    
                        // Successful search.
                        bReturn = true;
                    }
                    catch ( Exception ex )
                    {
                        Console.WriteLine( ex.ToString() );
                    }
    
                } while ( false );
    
                return bReturn;
            }
    
    
    

    Consuming the above to read is trivial.

                do
                {
                    try
                    {
                        // Gain access to the element that corresponds to the
                        // user setting in the config file.
                        Configuration config = null;
                        ClientSettingsSection clientSection = null;
                        SettingElement settingElement = null;
    
                        if ( !GetUserSetting( PlacementStringName, ref config, ref clientSection, ref settingElement ) )
                            break;
    
                        // A setting exists and was returned. If it does not have a value then quite now
                        // else apply the placement.
                        if ( string.IsNullOrEmpty( settingElement.Value.ValueXml.InnerText ) )
                        {
                            // Did not find the setting. Do nothing.
                            break;
                        }
                        else
                        {
                            // 'SetPlacement' is an extension
                            // method that consumes the string.
                            // It is not included in this example.
                            this.SetPlacement( settingElement.Value.ValueXml.InnerText );
                         }
                    }
                    catch ( Exception ex )
                    {
                        Console.WriteLine( ex.ToString() );
                    }
                } while ( false );
    

    Updating the user setting is only a little more complicated. In the following example a string is returned from a Window extension method that is not included in this code since the idea is to demonstrate how to access user settings that live in the application's configuration file, which the extension method has nothing to do with.

                do
                {
                    try
                    {
                        // Gain access to the element 
                        // that corresponds to the
                        // user setting in the config file.
                        Configuration config = null;
                        ClientSettingsSection clientSection = null;
                        SettingElement settingElement = null;
    
                        if (!GetUserSetting( PlacementStringName, ref config, ref clientSection, ref settingElement ))
                            break;
    
                        // First remove and then add the 
                        // element. Cannot simply update the 
                        // existing element.
                        clientSection.Settings.Remove( settingElement );
                        settingElement.Value.ValueXml.InnerText = this.GetPlacement();
                        clientSection.Settings.Add( settingElement );
                        config.Save( ConfigurationSaveMode.Full );
                    }
                    catch ( Exception ex )
                    {
                        Console.WriteLine( ex.ToString() );
                    }
    
                } while ( false );
    
    In short, you cannot directly re-use the application.exe.config user setting directly through its existing get; set; functionality without going through a lot of trouble and utilizing delegates or equally large numbers of keystrokes on each of the derived child classes. You can, however, get at the target properties directly in the base class with only a small amount of extra code (two strings passed on a constructor to bae class).

    Richard Lewis Haggard

    Monday, May 6, 2013 10:01 PM
  • Thanks for your article. It was really useful for me. Finally I have tried the ForceSaveAll flag in Save method:

    config.Save(ConfigurationSaveMode.Modified,true);

    ... and in this case it has been saved, and don't need to remove and add the setting.


    Wednesday, November 13, 2019 10:35 AM