views:

609

answers:

3

I have an WinForms application that is deployed using Visual Studio 2008's publish (ClickOnce) system. Within the application's app.config file I have a config section that is required by a third party component that has the form:

<section name="thirdPartySection"
type="System.Configuration.NameValueSectionHandler" />

The section is thus not in the appSettings and looks like:

<thirdPartySection >
  <add key="someKey" value="someValue" />
</thirdPartySection >

I understand that the key/value pairs are a NameValueCollection. The problem I face is that I wish to change the value either a deployment time or at runtime (either is fine with me) so that someValue will be someOtherValue based on the environment installed in.

Currently I make some other config changes at runtime, but those are in the AppSettings section, and thus easy to get at. I have found many references in my search for a solution, but they seem to rely on the section having a custom class, not the NameValueCollection that I'm faced with.

Does anyone know the best way to modify this data? A runtime change with a ConfigurationManager.RefreshSection() would be more in line with my current code, but I'm open to suggestions during the install phase as well.

Edit: This works at runtime. This is how I was handling the old configuration overrides.

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

config.AppSettings.Settings["Main.ConnectionString"].Value = 
    PolicyTrackerInfo.ConnectionString;

config.AppSettings.Settings["Main.linq"].Value = 
    PolicyTrackerInfo.LinqConnectionString;


config.Save(ConfigurationSaveMode.Modified);

ConfigurationManager.RefreshSection("appSettings");

My attempt to do the same for another section:

string overwriteXml = config.GetSection("thirdPartySection")
    .SectionInformation.GetRawXml();

XmlDocument xml = new XmlDocument();
xml.LoadXml(overwriteXml);
XmlNode node = xml.SelectSingleNode("thirdPartySection/add");
node.Attributes["value"].Value = PolicyTrackerInfo.OverwriteString;

So far, so good. However, I don't see a method that allows me to replace the old XML with my modified data. Is it possible at runtime?

As an aside: I tried modifying the app.config.deploy file by hand. That just gives me a validation error as the modification is detected by the installer and it refuses to proceed. I really like the auto deploy, and the prior override worked great.

A: 

I would write a custom installer and, in the AfterInstall event, modify the config file using the XML mechanisms you have above. I don't know, though, how or if that works with ClickOnce.

Jacob G
The issue I have with a custom installer is I lose a major feature of Click Once: if I update the code and deploy it, all users are *forced* to immediately upgrade (this is within a corporate environment). Losing this (and the simplicity of ClickOnce, which I like) means in addition to writing an installer I have to write an auto updater that detects that the code is obsolete and enforces some simple update mechanism. Considering how easy it is to update the other sections of app.config I find it hard to believe this is the *best* option.
Godeke
That said, it appears that as nobody has come forward at *all* in the first 4 days of this bounty, I'm guessing that updating the app.config is either harder than I thought, or I have failed to use Stack Overflow correctly in some way.
Godeke
I think that you can still deploy with ClickOnce, assuming you support Offline, and have the custom installer class still execute. Then you shouldn't need to do anything custom beyond what ClickOnce supports already.
Jacob G
+1  A: 

For the sake of putting forward an idea that people can vote up or down (not that I have seen much other than tumbleweed around this question), I'm thinking of using the technique posted here: http://www.devx.com/dotnet/Article/10045

The basic idea is to make ClickOnce deploy a shim application which will just do an XCOPY deployment of the main application (and as it isn't using the app.config file I can just use standard XML modification techniques and be done with it).

Alternatively, as this application is deployed from the network, I may just put the assembly on the network and work with the permission system to grant it access to the folders and database it requires. Any thoughts?

Godeke
+1  A: 

One thing you could do is add a first-time-running section to your code that does additional setup, such as modifying the application config file. To detect whether this setup needs to be done, your third-party config section could come pre-populated with dummy values that your application would recognize as belonging to a new install. For example, your config file could look like this:

<thirdPartySection>
    <add key="someKey" value="#NEEDS_INITIALIZED#" />
</thirdPartySection >

And your Main method could look something like this:

static public void Main(params string[] args)
{
    const string uninitializedValue = "#NEEDS_INITIALIZED#";

    // Load the third-party config section (this assumes it inherits from
    // ConfigurationElementCollection
    var config = ConfigurationManager.OpenExeConfiguration(
        ConfigurationUserLevel.None);
    var section = config.GetSection("thirdPartySection") 
        as NameValueConfigurationCollection;
    var setting = section["someKey"];
    if (setting.Value == uninitializedValue)
    {
        setting.Value = PolicyTrackerInfo.OverwriteString;
        config.Save();
    }
}
Jacob
I tried using "as NameValueConfigurationCollection" but I am told that it can't convert the section to such a collection. I have seen a lot of posts recommending that approach: is there something that has to be registered or otherwise configured to use that? It looks to be a simple technique, so I would find that extremely useful (and your approach of testing for configuration so it is only done once is probably something I should have done, but the runtime overwrite worked and so it was left there).
Godeke
System.Configuration should be all you need as a reference. The problem may be that the third-party section doesn't derive from NameValueConfigurationCollection. On the line where you get the section, could you inspect it in the debugger to see what type it is?
Jacob
I have accepted your answer so you get the bounty. If I don't attempt to cast the section, it is simply a "ConfigurationSection" as expected. If I debug is is a System.Config.DefaultSection (despite the formatting of the data in the section matching a NameValue section).
Godeke
According to MSDN, this is the type of a section not registered in ConfigSections: yet the section appears there as noted in the original question. Hmmm, I'm suspecting a boneheaded mismatch somewhere.
Godeke
OK, I'm really puzzled: `System.Configuration.ConfigurationSettings.GetConfig("thirdPartySection");` returns a ReadOnlyNameValueCollection. The modern GetSection method gets the generic form.
Godeke
Shouldn't the type be `System.Configuration.NameValueSectionHandler`? That's how it's registered in your config file. Can you cast to that?
Jacob
If you cannot gain access to the elements through the configuration API, I suppose you could resort to normal XML manipulation techniques.
Jacob
I have decided to punt. I have created a macro in Visual Studio that does the required edits prior to the deployment step (and reverts them after the deployment). It works, it is simple enough and it avoids what has turned into a huge tarpit for time. I understand they decided to move to custom named sections in 2.0, but the vendor of the third party tool still supports 1.1 so they use named value sections: which are apparently impossible to access easily. GetSection hates NameValue sections that aren't custom, but I don't control that implementation. (Tests on custom ones work.)
Godeke