When I try to access configuration section that is encrypted and cannot be decrypted properly (for example, someone just grabbed config file from another machine blindly) - Configuration class is throwing an exception. I want to catch that situation and rewrite the section completely in such case.
I've tried to remove and add back the section in question, but it seems that removal is ignored - second statement in 'catch' throws another exception about such section already exists:
try
{
// this getter might throw an exception - e.g. if encrypted on another machine
var connStrings = config.ConnectionStrings;
}
catch (ConfigurationException)
{
config.Sections.Remove("connectionStrings");
config.Sections.Add("connectionStrings", new ConnectionStringsSection());
}
It might be related to the fact I have connectionStrings section residing in separate file, i.e. my config file has something like <connectionStrings configSource="connections.config"/>, while actual encrypted content is in the connections.config file.
Is it possible to do what I need without falling back to direct XML manipulations, by using .NET Configuration classes only?
I'm pretty sure this will do what you want. In my case, I just included a bogus connectionString setting:
<connectionStrings>
<add connectionString="foo"/>
</connectionStrings>
I didn't include a "name" property, so trying to read ConnectionStrings will blow up, just like in your case:
try {
var x = ConfigurationManager.ConnectionStrings; //blows up
}
catch {
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.Sections.Remove("connectionStrings");
config.Save(); //Now save the changes
ConfigurationManager.RefreshSection("connectionStrings"); //and reload the section
var x = ConfigurationManager.ConnectionStrings; //can now read!
}
You still won't be able to do this:
config.Sections.Add("connectionStrings", new ConnectionStringsSection());
Because (at least on my machine), it's picking up connectionStrings from the Machine.config.
I suspect though that in your case that's fine. You are now at a point where if you want to, you can add your own connection strings and you don't really need to completely blow away the connection string section.
Related
I'm developing a plugin for Excel (for customizable reporting and data extraction from various sources).
Right now I'm struggling with ODBC connection (DSN in particular). Even though I have created a dedicated DSN and pointed the app to use it, I had to specify the same information in the connection string (pwd, user name, db name etc.) to make it work.
I don't want to expose this type of information in the xml file and wonder if it possible to force the app to use the info from DSN only (or, as alternative, to encode the conn. string in the customized xml file)?
You may want to encrypt connection string. Details here
static void ToggleConfigEncryption(string exeConfigName)
{
// Takes the executable file name without the
// .config extension.
try
{
// Open the configuration file and retrieve
// the connectionStrings section.
Configuration config = ConfigurationManager.
OpenExeConfiguration(exeConfigName);
ConnectionStringsSection section =
config.GetSection("connectionStrings")
as ConnectionStringsSection;
if (section.SectionInformation.IsProtected)
{
// Remove encryption.
section.SectionInformation.UnprotectSection();
}
else
{
// Encrypt the section.
section.SectionInformation.ProtectSection(
"DataProtectionConfigurationProvider");
}
// Save the current configuration.
config.Save();
Console.WriteLine("Protected={0}",
section.SectionInformation.IsProtected);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
I am trying to use WebConfigurationManager.AppSettings[string] to read/write values to be stored in a configuration file on the server. I thought that it read/wrote it to the Web.config file, or maybe app.config. However, I ran the following test code - which the first time through (as desired) it throws an exception and but writes 'NOTSet' to that configuration entry - this allows me go easily go edit the file and change it to the correct value.
After running it a 2nd time, I can see the value returned is "NOTSet" - exactly as I would like. The code is working exactly as planned.
Except - Where is it written? I did a 'baregrep NOTSet .' - which recursively searched the ENTIRE project source directory and ONLY found the line in code that set the var - which means it was not written to anything in the entire project tree - not web.config, app.config or any other file.
I sighed, and said it must be in the registry - I searched the whole registry for anything with that value, then tried the key value - nothing.
Yet, running the program IS pulling the value that was set on the last run! Where did my data go? I want to be able to edit the value- and more importantly, have a FILE that I can copy to the production web server with all the GUIDs set correctly.
public enum Guids
{
IncidentSourceEnumPortal,
WorkItemClass,
WorkItemManagementPack
}
public class GuidConsts
{
static readonly Dictionary<Guids, Guid> GuidList = new Dictionary<Guids, Guid>();
public static Guid Guids(Guids guidId)
{
if (!GuidList.ContainsKey(guidId))
{
string id = WebConfigurationManager.AppSettings[guidId.ToString()];
Guid newGuid;
if ( (id == null) || Guid.TryParse(id, out newGuid))
{
WebConfigurationManager.AppSettings[guidId.ToString()] = "NOTSet";
throw new Exception(String.Format("Invalid guid - not found in Config: {0}", guidId));
}
GuidList.Add(guidId, newGuid);
return newGuid;
}
return GuidList[guidId];
}
}
To save the web.config file you need to call Configuration.Save() to persist changes. They are not automatically saved when you change a value:
https://msdn.microsoft.com/en-us/library/ms134088%28v=vs.110%29.aspx
Writing to web.config on the fly is not really advisable anyway, for one thing it will cause the app pool to be restarted potentially resulting in loss of session state across your application and probably other application-wide things you don't want to happen.
I want to change the location where my application looks for the app.config file.
I know that I can use ConfigurationManager.OpenExeConfiguration() to access an arbitrary config file - however, when the .Net Framework reads the config file (for ConnectionStrings or EventSources, for instance), it will look at the default location. I want to actually change the location, globally for the entire .Net Framework (for my application, of course).
I also know that I can use AppDomainSetup to change the location of the app.config for a new AppDomain. However, that doesn't apply to the primary AppDomain of the application.
I also know that I can override function Main() and create a new AppDomain as above and run my application in that new AppDomain. However, that has other side-effects - for instance, Assembly.GetEntryAssembly() will return a null reference.
Given how everything else works in .Net, I would expect there to be some way to configure the startup environment of my application - via a Application Manifest, or some such - but I have been unable to find even a glimmer of hope in that direction.
Any pointer would be helpful.
David Mullin
I used the approach with starting another AppDomain from Main(), specifying the "new" location of the configuration file.
No issues with GetEntryAssembly(); it only returns null, when being called from unmanaged code - or at least it doesn't for me, as I use ExecuteAssembly() to create/run the second AppDomain, much like this:
int Main(string[] args)
{
string currentExecutable = Assembly.GetExecutingAssembly().Location;
bool inChild = false;
List<string> xargs = new List<string>();
foreach (string arg in xargs)
{
if (arg.Equals("-child"))
{
inChild = true;
}
/* Parse other command line arguments */
else
{
xargs.Add(arg);
}
}
if (!inChild)
{
AppDomainSetup info = new AppDomainSetup();
info.ConfigurationFile = /* Path to desired App.Config File */;
Evidence evidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain(friendlyName, evidence, info);
xargs.Add("-child"); // Prevent recursion
return domain.ExecuteAssembly(currentExecutable, evidence, xargs.ToArray());
}
// Execute actual Main-Code, we are in the child domain with the custom app.config
return 0;
}
Note that we are effectively rerunning the EXE, just as a AppDomain and with a different config. Also note that you need to have some "magic" option that prevents this from going on endlessly.
I crafted this out from a bigger (real) chunk of code, so it might not work as is, but should illustrate the concept.
I am not sure why you want to change the location of your config file - perhaps there can be different approach for solving your actual problem. I had a requirement where I wanted to share configuration file across related applications - I had chosen to use own xml file as it had given me extra benefit of having complete control over the schema.
In your case, it's possible to externalize sections of your config file to a separate file using configSource property. See here under "Using External Configuration Files" to check how it has been done for connection strings section. Perhaps, this may help you.
var configPath = YOUR_PATH;
if (!Directory.Exists(ProductFolder))
{
Directory.CreateDirectory(ProductFolder);
}
if (!File.Exists(configPath))
{
File.WriteAllText(configPath, Resources.App);
}
var map = new ExeConfigurationFileMap
{
ExeConfigFilename = configPath,
LocalUserConfigFilename = configPath,
RoamingUserConfigFilename = configPath
};
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
Then use config member as you want.
Another approach is to leave the config file with the executable file and move the relevant changeable sections to external xml files which can be in whatever location you choose.
If you are using your config file in a readonly capacity, then you can add the relevant chunks to an XML file in a different location using XML Inlcude. This won't work if you are trying to write values back directly to app.config using the Configuration.Save method.
app.config:
<?xml version="1.0"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
<appSettings>
<xi:include href="AppSettings.xml"/>
</appSettings>
<connectionStrings>
<xi:include href="ConnectionStrings.xml"/>
</connectionStrings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/></startup>
</configuration>
ConnectionStrings.xml:
<?xml version="1.0"?>
<add name="Example1ConnectionString"
connectionString="Data Source=(local)\SQLExpress;Initial Catalog=Example1DB;Persist Security Info=True;User ID=sa;Password=password"
providerName="System.Data.SqlClient" />
<add name="Example2ConnectionString"
connectionString="Data Source=(local)\SQLExpress;Initial Catalog=Example2DB;Persist Security Info=True;User ID=sa;Password=password"
providerName="System.Data.SqlClient" />
AppSettings.xml:
<?xml version="1.0"?>
<add key="Setting1" value="Value1"/>
<add key="Setting2" value="Value2"/>
A file URI looks like this:
file:///C:/whatever.txt
You can even define failover files in case the one you are trying to reference is missing. This pattern is from https://www.xml.com/pub/a/2002/07/31/xinclude.html:
<xi:include href="http://www.whitehouse.gov/malapropisms.xml">
<xi:fallback>
<para>
This administration is doing everything we can to end the stalemate in
an efficient way. We're making the right decisions to bring the solution
to an end.
</para>
</xi:fallback>
I've got the function for changing the values in web.config
but my problem is it is not getting the path of web.config correctly and throwing
"Could not find file 'C:\Users\maxnet25\Web.config'"
It was giving error on xmlDoc.Load() function.
My code:
public void UpdateConfigKey(string strKey, string newValue)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.BaseDirectory + "..\\..\\Web.config");
if (!ConfigKeyExists(strKey))
{
throw new ArgumentNullException("Key", "<" + strKey + "> not find in the configuration.");
}
XmlNode appSettingsNode = xmlDoc.SelectSingleNode("configuration/appSettings");
foreach (XmlNode childNode in appSettingsNode)
{
if (childNode.Attributes["key"].Value == strKey)
childNode.Attributes["value"].Value = newValue;
}
xmlDoc.Save(AppDomain.CurrentDomain.BaseDirectory + "..\\..\\Web.config");
xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
Label1 .Text ="Key Upated Successfullly";
}
What error messsage is being given?
Either way, you're not really going about modifying web.config in the right way. You should probably take a look at the System.Configuration.ConfigurationManager class as this provides programmatic access to the web.config file in a structured manner. Note that to access this class you need to add a reference to System.Configuration.dll to your project to bring the ConfigurationManager into scope.
If you look at the example code for the GetSection method, it shows how to create/add settings in the appSettings section of a .net config file, so that example should be enough to get you where you want to go.
If you definately want to use this approach to manipulate your web.config file, I suspect that:
AppDomain.CurrentDomain.BaseDirectory + "..\\..\\Web.config")
is incorrect, based on the path that you've shown in the error message. Try removing the ..\..\ and seeing if that works. AppDomain.CurrentDomain.BaseDirectory should be pointing at the location of your web.config file without modification.
Assuming this is indeed an ASP.NET website, instead of this:
AppDomain.CurrentDomain.BaseDirectory + "..\\..\\Web.config"
Use this:
HttpContext.Current.Server.MapPath("~/Web.config")
On a side note, please be aware that anytime you make a change to web.config, your web application restarts. You might not need to worry about that depending on what your web app does though.
Try using Server.MapPath() to resolve the location of your web.config. If you're in a page, Server is one of the page properties. If not, you can find it in HttpContext.Current.
As an example...
HttpContext.Current.Server.MapPath("~/web.config")
...should return the physical path to the web.config at the top of your web application.
Now, you're probably much better off using the WebConfigurationManager, as shown in this post. The approach is much cleaner, but requires a reference to System.Configuration.
Have you added a web.config to your web site?
You should use either:
System.Configuration.ConfigurationManager
for app.config files, or:
System.Web.Configuration.WebConfigurationManager
for web.config files.
You can actually use System.Configuration.ConfigurationManager with web.config files as well, and to be honest, I'm not actually sure if there's any benefit for using one over the other.
But either way, you should not be using the Xml namespaces and writing/modifying the raw XML.
Has anyone got this working in a web application?
No matter what I do it seems that my appSettings section (redirected from web.config using appSettings file=".\Site\site.config") does not get reloaded.
Am I doomed to the case of having to just restart the application? I was hoping this method would lead me to a more performant solution.
Update:
By 'reloading' I mean refreshing ConfigurationManager.AppSettings without having to completely restart my ASP.NET application and having to incur the usual startup latency.
Make sure you are passing the correct case sensitive value to RefreshSection, i.e.
ConfigurationManager.RefreshSection("appSettings");
This seems to be a flaw (maybe a bug) when using an external config file for your appSettings. I've tried it using the configSource attribute and RefreshSection simply never works, I'm assuming this is the same when using the file attribute.
If you move your appSettings back inside your web.config RefreshSection will work perfectly but otherwise I'm afraid you're doomed.
For some reason ConfigurationManager.RefreshSection("appSettings") wasn't working for me. Reloading the Web.Config into a Configuration object seems to work correctly. The following code assumes the Web.Config file is one directory below the executing (bin) folder.
ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
Uri uriAssemblyFolder = new Uri(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase));
string appPath = uriAssemblyFolder.LocalPath;
configMap.ExeConfigFilename = appPath + #"\..\" + "Web.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None);
And is used like:
string webConfigVariable = config.AppSettings.Settings["webConfigVariable"].Value;
.RefreshSection() does not work when the appSettings is external.
You can however use the following to change a value:
ConfigurationManager.AppSettings.Set(key, value)
This will NOT change the setting on file, only the loaded value in memory.
So instead of using RefreshSection I did the following:
string configFile="path to your config file";
XmlDocument xml = new XmlDocument();
xml.Load(configFile);
foreach (XmlNode node in xml.SelectNodes("/appSettings/add"))
{
string key = node.Attributes["key"].Value;
string value= node.Attributes["value"].Value;
ConfigurationManager.AppSettings.Set(key, value);
}
Any subsequent calls to AppSettings.Get will contain the updated value.
The appSettings will then be updated without needing to restart the application.
As an alternative you could write your own ConfigSection and set restartOnExternalChanges="false".
Then, when reading the section with ConfigurationManager.GetSection("yourSection") the settings will be auto-refreshed without an application restart.
And you could implement your settings strongly typed or as NameValueCollection.
Yes. you are stuck with iis restarting.
There is a feature with asp.net 4.0 and iis 7.5 where the initial startup is removed.
I am not sure if this is possible in a web app, but it works in a desktop app. Try using ConfigurationSettings rather than ConfigurationManager (it will yell at you for using outdated classes...), then reading all the data into a class. When you wish to refresh, simply create a new instance and drop all references to the old instance. My theory for why this works (might be wrong): when you don't directly access the app.config file the entire time you are running, the file lock is dropped by the application. Then, edits can be made when you are not accessing the file.
The App.Config settings are cached in memory when the application starts. For this reason, I don't think you'll be able to change those settings without restarting your application. One alternative that should be pretty straight forward would be to create a separate, simple XML configuration file, and handle loading/caching/reloading it yourself.
To write, call it this way:
Dim config As System.Configuration.Configuration = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~")
Return AddOrUpdateAppSetting(config, "YourSettingKey", "YourValueForTheKey")
To read and be sure you get the values in file, instead of those in cache, read it this way:
Dim config As System.Configuration.Configuration = WebConfigurationManager.OpenWebConfiguration("~")
Return config.AppSettings.Settings("TheKeyYouWantTheValue").Value
Full example:
Protected Shared Function AddOrUpdateAppSetting( _
ByVal Config As System.Configuration.Configuration _
, ByVal TheKey As String _
, ByVal TheValue As String _
) As Boolean</p>
Dim retval As Boolean = True
Dim Itm As System.Configuration.KeyValueConfigurationElement = _
Config.AppSettings.Settings.Item(TheKey)
If Itm Is Nothing Then
If Config.AppSettings.Settings.IsReadOnly Then
retval = False
Else
Config.AppSettings.Settings.Add(TheKey, TheValue)
End If
Else
' config.AppSettings.Settings(thekey).Value = thevalue
If Itm.IsReadOnly Then
retval = False
Else
Itm.Value = TheValue
End If
End If
If retval Then
Try
Config.Save(ConfigurationSaveMode.Modified)
Catch ex As Exception
retval = False
End Try
End If
Return retval
End Function
Have you tried storing your AppSettings in its own external file?
From app.config/web.config:
<appSettings configSource="appSettings.config"></appSettings>
appSettings.config:
<?xml version="1.0"?>
<appSettings>
<add key="SomeKey" value="SomeValue" />
</appSettings>
Changes made to appSettings.config should be reflected instantly.
More info:
http://msdn.microsoft.com/en-us/library/system.configuration.sectioninformation.configsource.aspx