ConfigurationManager.AppSettings.Settings.Add() appends value on each run - c#

I have the following piece of code. Every time, I run the C# project the values for the app settings key gets appended.
var configSettings = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
configSettings.AppSettings.Settings.Add("Key", "Value");
configSettings.Save(ConfigurationSaveMode.Full, true);
ConfigurationManager.RefreshSection("appSettings");
1st run:
Key: Value
2nd run:
Key, Value, Value
Why are the values getting appended? I need it to start on a clean plate on each run.

You need to check if the AppSetting already exists. If it exists, you have to update the value. If it doesn't you have to add the value.
var configSettings = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var settings = configSettings.AppSettings.Settings;
if (settings["Key"] == null)
{
settings.Add("Key", "Value");
}
else
{
settings["Key"].Value = "NewValue";
}
configSettings.Save(ConfigurationSaveMode.Full, true);
ConfigurationManager.RefreshSection("appSettings");
AppSettings.Settings is basically a collection of key/value pairs.
Check the below MSDN documentation for more details.
https://msdn.microsoft.com/en-us/library/system.configuration.configurationmanager.appsettings(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.configuration.appsettingssection.settings(v=vs.110).aspx

Related

.NET Core: why is the result of ConfigurationBuilder["Key"] null when.ConfigurationBuilder.GertChildren has elements?

I have created a windows service using .NET Core 2.1 and added a set of environment variables:
Application_key1 = Key1
Application_key2 = Key2
Application_key3 = Key3
Application_key4 = Key4
Application_key5 = Key5
I am accessing it through:
var configuration = new ConfigurationBuilder().AddEnvironmentVariables("Application_").Build();
return new ApplicationSettings
{
Key = configuration["key1"],
//Get the rest of the keys
};
Works great when I debug using my IDE. The environment variables get pulled out of my user settings just fine. When I run, the service, it doesn't pull the data reporting back a null reference exception. I then added the environment variables to the registry in HKLM/System/CurrentControlSet/services/{Service} as a REG_MULTI_SZ:
I verified that the environment variables are correctly being pulled from the registry:
if (configuration == null)
{
eventLog.WriteEntry("Configuration is null", EventLogEntryType.Warning);
}
else
{
foreach (var child in configuration.GetChildren())
{
eventLog.WriteEntry($"Key: {child.Key} Value: {child.Value}", EventLogEntryType.Warning);
}
}
It displays correctly in the event viewer. However, when I try to access the data:
var a = configuration["key1"]
the result is null. I don't really have a clue where to go next
You have to remove the spaces between the equals signs for the environment variables to the registry:
Key1 = Value1
etc.
becomes
Key1=Value1
etc.

How to add a field in my quartz configuration dynamically?

I have a quartz configuration section in my web.config, and I want to add a key value field to it. (I know I can just go to the web.config and add it manually but that defeats the purpose)
I tried using this way
var config = (NameValueCollection)WebConfigurationManager.GetSection("quartz");
config.Add("quartz.dataSource.quartzDS.connectionString", "data source =..");
but it failed because collection is read only and can't be modified. Any tip to how to do this?
Edit: I ended up copying the config to a nameValueCollection and then copying it to another one (for the readonly properties) add the key values I want and passing it to the function I needed.
var oldConfig = (NameValueCollection)WebConfigurationManager.GetSection("quartz");
var config = Test(oldConfig);
var connectionString = unitOfWork.GetConnectionStringByTenantIdentifier(tenantId);
config.Add("quartz.dataSource.quartzDS.connectionString", connectionString);
await unitOfWork.GetService<SchedulerService>().StartScheduler(config, tenantId);
this way I will have the custom configuration for every tenant the way I want it. Sorry if my question wasn't clear.
You can actually do this in two ways.
One way is to set your dynamic connection string in the standard AppSettings section and then create a new Quartz Scheduler with a new set of XML properties (an example is provided in Quartz.NET distribution, so I will cut this short)
var properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = "XmlConfiguredInstance",
["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz",
... etc.
};
ISchedulerFactory sf = new StdSchedulerFactory(properties);
IScheduler sched = await sf.GetScheduler();
Then you can save your non-const string in the AppSettings and get it form there.
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Add("quartz.dataSource.quartzDS.connectionString", connstring);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
Or you can read your whole settings file as XML, as previously answered, BUT you have to make sure that any edits are done before you initialize the default Quartz Scheduler, as it's properties become read-only, and to change them you will have to create a new ISchedulerFactory anyway, which kinda beats the purpose.
var xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
xmlDoc.SelectSingleNode("//quartz/add[#key='quartz.dataSource.quartzDS.connectionString']").Attributes["value"].Value = "...";
xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
ConfigurationManager.RefreshSection("quartz");
But I advise you not to edit your main config file at runtime at all, and instead use an ISchedulerFactory XmlConfiguredInstance while getting and saving the connstring into a UAC-compatible location in any format you like (to prevent Modifying app.config at runtime throws exception from happening)
Still if you want to use the config file you can use this tutorial from Yi Zeng for further reading
You can try using XmlDocument classes to go to a lower level.
Make sure the user of your app has write permissions to the config file
public static void WriteKey(String configFileName, String key, String value)
{
XmlDocument doc = new XmlDocument();
doc.Load(configFileName);
XmlNode node = doc.SelectSingleNode("//quartz");
if (node == null)
{
throw new InvalidOperationException("quartz section not found in config file.");
}
try
{
XmlElement elem = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='{0}']", key));
if ((elem != null))
{
elem.SetAttribute("value", value);
}
else
{
elem = doc.CreateElement("add");
elem.SetAttribute("key", key);
elem.SetAttribute("value", value);
node.AppendChild(elem);
}
doc.Save(configFileName);
}
catch
{
throw new InvalidOperationException("Error writing config file");
}
}

How to Create New User Setting in app.config at Run Time

I have an Editable ComboBox. The user enters text and presses a Save button. Their text is turned into a string.
I need it at Run Time to Create a new User Setting to the app.config with the name of their string. (I think this part works now).
Then another ComboBox's Selected Item is saved to the Setting. (Object reference not set error).
This is to create a custom preset that will save each control state, checkboxes, textboxes, etc. in the program.
// Control State to be Saved to Setting
Object comboBox2Item = ComboBox2.SelectedItem;
// User Custom Text
string customText = ComboBox1.Text;
// Create New User Setting
var propertyCustom = new SettingsProperty(customText);
propertyCustom.Name = customText;
propertyCustom.PropertyType = typeof(string);
Settings.Default.Properties.Add(propertyCustom);
// Add a Control State (string) to the Setting
Settings.Default[customText] = (string)comboBox2Item;
At this part I get an error.
Settings.Default[customText] = (string)comboBox2Item;
Exception:Thrown: "Object reference not set to an instance of an object."
I have tried setting ComboBox1.Text to an Object instead of string, with same error. The text and string is also not null.
Object customText = ComboBox1.Text;
Here's a visual of what I'm trying to do
Original Answer:
I haven't tried adding a new setting to the file but i have had to update it. Here is some code that I use to save and retrieve the saved changes to the file. I know it doesn't directly answer the question but should point you in the right direction as to what classes to look at and use.
I'll try to update to directly answer this question once I get some breathing time.
public static void UpdateConfig(string setting, string value, bool isUserSetting = false)
{
var assemblyPath = AppDomain.CurrentDomain.BaseDirectory;
var assemblyName = "AssemblyName";
//need to modify the configuration file, launch the server with those settings.
var config =
ConfigurationManager.OpenExeConfiguration(string.Format("{0}\\{1}.exe", assemblyPath, "AssemblyName"));
//config.AppSettings.Settings["Setting"].Value = "false";
var getSection = config.GetSection("applicationSettings");
Console.WriteLine(getSection);
var settingsGroup = isUserSetting
? config.SectionGroups["userSettings"]
: config.SectionGroups["applicationSettings"];
var settings =
settingsGroup.Sections[string.Format("{0}.Properties.Settings", assemblyName)] as ClientSettingsSection;
var settingsElement = settings.Settings.Get(setting);
settings.Settings.Remove(settingsElement);
settingsElement.Value.ValueXml.InnerText = value;
settings.Settings.Add(settingsElement);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
Edited Answer:
I did a quick google search and found an accepted answer on the MSDN forum.MSDN question. You have to call save on the properties class in order for the add to take affect. Think of a database transaction, until you call commit, it doesn't take effect.
So what appears to be missing in your code is: Properties.Settings.Default.Save(); which should be the very next line after your Settings.Default.Properties.Add(propertyCustom);

How to Update (Add/Modify/Delete) keys in AppSettings section of web.config at runtime

I like to Update keys/Values defined in AppSettings section of Web.config at runtime. however I DO NOT want to actually save them to Web.config file.
I have a huge web application that have consists of many modules, DLLs and source code files. A bunch of critical information ranged from database configuration, encryption keys, username and passwords for webservices are saved in AppSettings section of the web.config file. Recent project requirement needs me to move these values out of web.config and keep in a secure storage.
I already secured these values in an external location and I can read them back when application starts.
here is the sample code.
Global.asax
public class Global: System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
Dictionary<string, string> secureConfig = new Dictionary<string,string>{};
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey","VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
ConfigurationManager.AppSettings.Add(item.Key, item.Value);
}
}
}
As you may noticed it is not feasible to change references to AppSettings in a massive code created by multiple programming teams to read their settings from my secureConfig dictionary and on the other hand I should not save these values in web.config file which is available to web administrators and operators, system admins and cloud admins.
To Make programmers life easier, I want to let them add their values to AppSettings section of web.config during development, but they will be removed from there and put to secure storage later during deployment, however these values should be available to program transparently as they are still in AppSettings section.
Question: how can I add values to AppSettings at runtime so program can read them using ConfigurationManager.AppSettings["ACriticalKey"] to get "VeryCriticalValue" without saving them in Web.Config?
Please note: ConfigurationManager.AppSettings.Add(item.Key, item.Value); gives me ConfigurationErrorsException with message The configuration is read only.
Please note: Preferably some settings should be able to stay in AppSettings as before
I know this is an old question, but I ran into the same problem and I found that Set works in the same way as Add, and does not throw an exception, so just replace Add with Set, like so:
ConfigurationManager.AppSettings.Set(item.Key, item.Value);
You need to make use of WebConfigurationManager.OpenWebConfiguration()
Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
config.AppSettings.Settings.Remove("Variable");
config.AppSettings.Settings.Add("Variable", "valyue");
config.Save();
Perhaps this link will help. It references 2.0 but I believe the method is still valid in 4.0.
Also, the SO question on the same/similar topic here may be of interest.
Also, modifying the web.config at runtime should cause an application pool recycle each time. Not trying to tell you how to suck eggs, just thought I'd note it for anyone's prospective interest...Thx.
Thanks to nkvu which directed me to a his first link which in turn sent me to Williarob's post "Override Configuration Manager" I managed to find a solution to my question.
The mentioned blog post covers how to read settings from another XML file and it works with both windowed applications and web applications (with a little modification in config file name and path). Although this blog written on 2010 it is still working fine with .NET4 without problem.
However as I was going to read my configuration from a secure device, I simplified the class and here is how to use the classes provided by Williarob
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Internal;
using System.Linq;
using System.Reflection;
namespace Williablog.Core.Configuration {
public sealed class ConfigSystem: IInternalConfigSystem {
private static IInternalConfigSystem clientConfigSystem;
private object appsettings;
private object connectionStrings;
/// <summary>
/// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config
/// </summary>
public static void Install() {
FieldInfo[] fiStateValues = null;
Type tInitState = typeof(System.Configuration.ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic);
if (null != tInitState) {
fiStateValues = tInitState.GetFields();
}
FieldInfo fiInit = typeof(System.Configuration.ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
FieldInfo fiSystem = typeof(System.Configuration.ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);
if (fiInit != null && fiSystem != null && null != fiStateValues) {
fiInit.SetValue(null, fiStateValues[1].GetValue(null));
fiSystem.SetValue(null, null);
}
ConfigSystem confSys = new ConfigSystem();
Type configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
IInternalConfigSettingsFactory configSettingsFactory = (IInternalConfigSettingsFactory) Activator.CreateInstance(configFactoryType, true);
configSettingsFactory.SetConfigurationSystem(confSys, false);
Type clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
clientConfigSystem = (IInternalConfigSystem) Activator.CreateInstance(clientConfigSystemType, true);
}
#region IInternalConfigSystem Members
public object GetSection(string configKey) {
// get the section from the default location (web.config or app.config)
object section = clientConfigSystem.GetSection(configKey);
switch (configKey) {
case "appSettings":
// Return cached version if exists
if (this.appsettings != null) {
return this.appsettings;
}
// create a new collection because the underlying collection is read-only
var cfg = new NameValueCollection();
// If an AppSettings section exists in Web.config, read and add values from it
if (section is NameValueCollection) {
NameValueCollection localSettings = (NameValueCollection) section;
foreach (string key in localSettings) {
cfg.Add(key, localSettings[key]);
}
}
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey", "VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
if (cfg.AllKeys.Contains(item.Key)) {
cfg[item.Key] = item.Value;
} else {
cfg.Add(item.Key, item.Value);
}
}
// --------------------------------------------------------------------
// Cach the settings for future use
this.appsettings = cfg;
// return the merged version of the items from secure storage and appsettings
section = this.appsettings;
break;
case "connectionStrings":
// Return cached version if exists
if (this.connectionStrings != null) {
return this.connectionStrings;
}
// create a new collection because the underlying collection is read-only
ConnectionStringsSection connectionStringsSection = new ConnectionStringsSection();
// copy the existing connection strings into the new collection
foreach (ConnectionStringSettings connectionStringSetting in ((ConnectionStringsSection) section).ConnectionStrings) {
connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
}
// --------------------------------------------------------------------
// Again Load connection strings from secure storage and merge like below
// connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
// --------------------------------------------------------------------
// Cach the settings for future use
this.connectionStrings = connectionStringsSection;
// return the merged version of the items from secure storage and appsettings
section = this.connectionStrings;
break;
}
return section;
}
public void RefreshConfig(string sectionName) {
if (sectionName == "appSettings") {
this.appsettings = null;
}
if (sectionName == "connectionStrings") {
this.connectionStrings = null;
}
clientConfigSystem.RefreshConfig(sectionName);
}
public bool SupportsUserConfig { get { return clientConfigSystem.SupportsUserConfig; } }
#endregion
}
}
To install this (or original version of configuration override) add following line to
your Global. class (Global.asax.cs) in Application_Start
Williablog.Core.Configuration.ConfigSystem .Install();
like below:
public class Global: System.Web.HttpApplication {
//...
#region protected void Application_Start(...)
protected void Application_Start(object sender, EventArgs e) {
Williablog.Core.Configuration.ConfigSystem .Install();
//...
}
#endregion
//...
}

I can't modify the app.config file

What's wrong with the following code:
private static void UpdateAppSettings(string settingName, string settingValue)
{
if (settingName == null) throw new ArgumentNullException("settingName");
if (settingValue == null) throw new ArgumentNullException("settingValue");
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var appSettings = config.AppSettings;
var setting = appSettings.Settings[settingName];
setting.Value = settingValue;
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("AppSettings");
}
I don't know why it doesn't save the new settings. It first opens the app.config file. After going to appsetting area. It then finds a specific key as settingName and changes the value to settingValue. Afterwards, it saves the file and refreshes it. It almost works; up to config.Save(...). But after that I don't know what happens.
This question seems to be the same as what you are asking.
The answer looks to be this:
config.AppSettings[settingName] = settingValue;
EDIT:
This question has the answer I think
config.AppSettings.Settings[settingName]

Categories

Resources