views:

1008

answers:

6

I need to know where and how application settings (connections string) could be stored so that they can be changed at run time and saved.

I know in VS you can configure settings under Project>Properties and these get stored in the appname.exe.config file under apps install directory.But the "application scope" ones are not read/write at run time and if you change the ones under user scope a copy of the config file is created under users directory and wont be accessed by other users of the application.

I need to have a way so that the user can configure connection string, stored in a common config file, as per their needs from within the app and then have all other users(on that machine) can use that too. how can I achieve this?

A: 

Reading and writing to the Registry used to be the recommended way of storing user settings, and you can still do this easily (using Microsoft.Win32.Registry), but there are issues with this approach (mainly due to varying permissions levels among your users - some users aren't allowed to write to some parts of the registry or create new keys).

MusiGenesis
I'd avoid this as you're going to face user rights issues when touching the registry.
Will
The registry is partitioned into HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER just as the application settings are partitioned into application scope and user scope. Switching from application settings to registry does not help to get around say problems with privileges.
Martin Liversage
It would actually work fine if they wrote to the user's part of the registry, but I think the asker actually wants a global setting that any user can modify.
MusiGenesis
I would like to avoid using the registry at any time. But yes, i need to know if there is anyway that I can have a common config file that can be modified and easily accessed from code. ( without creating my own XML version of it)
I think you have to roll your own here because you're doing something slightly unusual: an application-wide setting that any user should be able to modify regardless of rights.
MusiGenesis
+2  A: 

Application scope connection strings are writable, but you have to go about it differently from just getting the string for usage. For example:

Configuration c = ConfigurationManager
   .OpenExeConfiguration(ConfigurationUserLevel.None);

c.ConnectionStrings.ConnectionStrings["myConnectionString"]
   .ConnectionString = "theConnectionString";
c.Save();

You should also refresh the section so that the change is properly retrieved. Immediately afterward:

string sectionName = c.ConnectionStrings.SectionInformation.SectionName;
ConfigurationManager.RefreshSection(sectionName);
Ben M
will this work even under the UAC environment of Vista??
The config file cannot be saved unless the user has the necessary rights to the underlying file system.
Martin Liversage
Only if the user has administrator access, which on a single-user computer is extremely likely. (And if it's a multi-user computer, you probably only want the administrator to change this setting.)
Ben M
+4  A: 

Simplest, fastest solution is to create a configuration file in a shared location where normal users have rights to read/write.

Create a class with public properties for your configuration data, then serialize it to xml in this shared location.

public class Configuration
{
  // config filename
  private static _name = Path.Combine(
      System.Environment.GetFolderPath(
         Environment.SpecialFolder.CommonApplicationData),
      @"MyApp\MyConfig.xml");

  // the connection string
  public string ConnectionString {get;set;}

  // load the configuration from disk
  public static Configuration Load()
  {
    using (var f = File.OpenRead(name))
    {
      var x = new System.Xml.Serialization.XmlSerializer(typeof(Configuration));
      return x.Deserialize(f) as Configuration;
    }
  }

  // save the configuration to disk
  public static Save(Configuration config)
  {
    using (var f = File.OpenWrite(name))
    {
      var x = new System.Xml.Serialization.XmlSerializer(typeof(Configuration));
      x.Serialize(f, config);
    }
  }
}
Will
I was afraid this would be the only option. But really isnt there any other easier way to do this?
I'd use Environment.SpecialFolder.CommonApplicationData instead of the above path, but otherwise I don't think there's any other way to do this.
MusiGenesis
I think he'd be better off using the standard Configuration classes to do this, since he could then leverage their built-in functionality if his all-users configuration becomes more complex. (ConfigurationManager.OpenExeConfiguration allows an abitrary path to the config file to be used.)
Ben M
True, Ben, but creating those custom configuration classes can be very confusing for the beginning developer. I've done it both ways, and unless I NEED what System.Configuration gives me, I go the other way.
Will
Changed wording of answer from BEST to EASIEST. I think the custom config is the best way, but this is the easiest way.
Will
I'll buy that, Will. :-)
Ben M
What about versioning issues when using this method? SHould that be a concern?
@Musi: updated code as per your recommendation.@superartsy: Yup. If you're concerned with versioning you should include the version number in your path. This is one of the things System.Configuration does for you.
Will
@Will- can you tell me how System.Configuration does the version number in path. I tried looking up and couldn't find much.
@superartsy: it munges the version number into the path. I forget exactly what format. It isn't that important or hard to do. You just get the version number of the executing assembly and use that as part of the path or the configuration name. I wouldn't stress too much about versioning configs unless your requirements will cause your configuration to change often.
Will
@will -I found a simpler answer to the versioning issue. Using the optionalfield attribute for swerialization. mroe here http://msdn.microsoft.com/en-us/library/ms229752(VS.80).aspx
A: 

You have some conflicting elements in your question:

application settings (connections string) could be stored so that they can be changed at run time and saved.

.

application scope" ones are not read/write at run time and if you change the ones under user scope a copy of the config file is created under users directory and wont be accessed by other users of the application.

Best practices dictate that settings (like connection strings) that you want to apply to every user of a system should not be changeable by any random user of the system. That kind of setting should only be changed by someone qualified to administer the system.

Therefore, the behavior you describe is exactly what's intended. If you really want to allow users to change this, then make it a user setting so if a user screws it up they only screw it up for their own account.

Joel Coehoorn
Yes in an IDEAL world you are correct, something that affects everyone should be changed only by an authority.More than "changing after use" we are talking about is "intial setup". Once deployed ,I want the give the user and easy form through which they can pick the server and database they would like to connect to.This would usually occur only one time, or when say the network underwent some change.
And what's wrong with letting each user do that?
Joel Coehoorn
+1  A: 

The way I did it was I saved an XML file that was serialized from a POCO object that had the SQL Connection information into isolated storage. Since there are no real restrictions on the user account that can read and write from there, you don't have to worry about user permissions.

I then modified the settings events so that when the connection string property was read from the app settings file, I instead deserialized the XML file in isolated storage, built the connection string, and then put that into the app settings connection string property. Since I was doing this inside the settings class itself, I was able to write to it (but not save back to the file).

When my app runs, if it can't connect to the server I just pop up a window asking for the sql server info and they type it in and it saves it to the isolated storage, never to ask them for it again (even through upgrades).

If you have a client application (WPF or WinForms), users on other machines will not be able to use this and quite frankly you probably will not find a solution for that, unless you look into .Net Remoting or something of that nature. However, if you have multiple users on the SAME machine in a WinForms App, you can use the IsolatedStorage and it will work for all users.

In the settings class, add this event handler:

VB:

Private Sub MySettings_SettingsLoaded(ByVal sender As Object, ByVal e As System.Configuration.SettingsLoadedEventArgs) Handles Me.SettingsLoaded   
  Me.Item("MyAppConnectionString") = MyLibrary.BuildConnectionString()   
End Sub

C#:

protected override void OnSettingsLoaded(object sender, System.Configuration.SettingsLoadedEventArgs e)
{
  base.OnSettingsLoaded(sender, e);
  this["MyAppConnectionString"] = MyLibrary.BuildConnectionString();
}
TheCodeMonk
I like this! its a pretty unique way of doing things. Except I see one minor issues. When using serializable class for storing setting one can always run into versioning issues. also is there a reason you chose Isolated storage over the Common_AppData folder
For versioning, it depends how you store your settings. If we add a property, when deserializing, most of the time it's not an issue as that property will just be read in as null. Your mileage will vary. We don't add properties to those classes often because we only use those for things like setting the connection string or saving printer settings. We choose isolated storage because it was something that is writeable to all and kind of "hidden" away where people don't know to look.
TheCodeMonk
+1  A: 

As other have pointed out you need to create a shared location on the computer that is writable by all users of your application. This location can be a folder, a file or the registry. However, Windows actually have a location for this. You can read more about this on MSDN. The folder you need is for common application data:

CSIDL_COMMON_APPDATA

This folder should be used for application data that is not user specific. For example, an application may store a spell check dictionary, a database of clip-art or a log file in the CSIDL_COMMON_APPDATA folder. This information will not roam and is available to anyone using the computer. By default, this location is read-only for normal (non-admin, non-power) Users. If an application requires normal Users to have write access to an application specific subdirectory of CSIDL_COMMON_APPDATA, then the application must explicitly modify the security on that sub-directory during application setup. The modified security must be documented in the Vendor Questionnaire.

You can get the location of this folder by calling

Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);

in your .NET application. On Vista this folder is normally C:\ProgramData.

You have to create your own vendor and application specific folder inside the shared folder and this should be done by an install program running with elevated privileges. This install program should also make sure that users have the necessary privileges to get write access as seen in the quote above.

Martin Liversage
Thanks. I did know about this and we have applications that store common files under this directory stricture too. However to store config properties under here would mean I have to read/write/serialize them myself. I was just wondering if I was missing out an easier way.