Is there any way to load settings from a different file other than the default App.config file at runtime? I'd like to do this after the default config file is loaded.
I use the Settings.Settings GUI in Visual Studio to create my App.config file for me. The config file ends up looking like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="SnipetTester.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<SnipetTester.Properties.Settings>
<setting name="SettingSomething" serializeAs="String">
<value>1234</value>
</setting>
</SnipetTester.Properties.Settings>
</applicationSettings>
</configuration>
In code, I'm able to access the settings like this:
Console.WriteLine("Default setting value: " + Properties.Settings.Default.SettingSomething);
The idea is that when the application is run, I should be able to specify a config file at run time and have the application load the config file into the Properties.Settings.Default object instead of using the default app.config file. The formats of the config files would be the same, but the values of the settings would be different.
I know of a way to do this with the ConfigurationManager.OpenExeConfiguration(configFile);. However, in the tests that I've run, it doesn't update the Properties.Settings.Default object to reflect the new values from the config file.
After thinking about this a bit longer, I've been able to come up with a solution that I like a little better. I'm sure it has some pitfalls, but I think it'll work for what I need it to do.
Essentially, the Properties.Settings class is automatically generated by Visual Studio; it generates the code for the class for you. I was able to find where the code was generated and add a few function calls to load a config file on its own. Here's my addition:
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
//Parses a config file and loads its settings
public void Load(string filename)
{
System.Xml.Linq.XElement xml = null;
try
{
string text = System.IO.File.ReadAllText(filename);
xml = System.Xml.Linq.XElement.Parse(text);
}
catch
{
//Pokemon catch statement (gotta catch 'em all)
//If some exception occurs while loading the file,
//assume either the file was unable to be read or
//the config file is not in the right format.
//The xml variable will be null and none of the
//settings will be loaded.
}
if(xml != null)
{
foreach(System.Xml.Linq.XElement currentElement in xml.Elements())
{
switch (currentElement.Name.LocalName)
{
case "userSettings":
case "applicationSettings":
foreach (System.Xml.Linq.XElement settingNamespace in currentElement.Elements())
{
if (settingNamespace.Name.LocalName == "SnipetTester.Properties.Settings")
{
foreach (System.Xml.Linq.XElement setting in settingNamespace.Elements())
{
LoadSetting(setting);
}
}
}
break;
default:
break;
}
}
}
}
//Loads a setting based on it's xml representation in the config file
private void LoadSetting(System.Xml.Linq.XElement setting)
{
string name = null, type = null, value = null;
if (setting.Name.LocalName == "setting")
{
System.Xml.Linq.XAttribute xName = setting.Attribute("name");
if (xName != null)
{
name = xName.Value;
}
System.Xml.Linq.XAttribute xSerialize = setting.Attribute("serializeAs");
if (xSerialize != null)
{
type = xSerialize.Value;
}
System.Xml.Linq.XElement xValue = setting.Element("value");
if (xValue != null)
{
value = xValue.Value;
}
}
if (string.IsNullOrEmpty(name) == false &&
string.IsNullOrEmpty(type) == false &&
string.IsNullOrEmpty(value) == false)
{
switch (name)
{
//One of the pitfalls is that everytime you add a new
//setting to the config file, you will need to add another
//case to the switch statement.
case "SettingSomething":
this[name] = value;
break;
default:
break;
}
}
}
}
The code I added exposes an Properties.Settings.Load(string filename) function. The function accepts a config filename as a parameter. It will parse the file and load up any settings it encounters in the config file. To revert back to the original configuration, simply call Properties.Settings.Reload().
Hope this might help someone else!
Look at using ExeConfigurationFileMap and ConfigurationManager.OpenMappedExeConfiguration.
See Cracking the Mysteries of .Net 2.0 Configuration
The ExeConfigurationFileMap allows you to specifically configure the
exact pathnames to machine, exe, roaming and local configuration
files, all together, or piecemeal, when calling
OpenMappedExeConfiguration(). You are not required to specify all
files, but all files will be identified and merged when the
Configuration object is created. When using
OpenMappedExeConfiguration, it is important to understand that all
levels of configuration up through the level you request will always
be merged. If you specify a custom exe and local configuration file,
but do not specify a machine and roaming file, the default machine and
roaming files will be found and merged with the specified exe and user
files. This can have unexpected consequences if the specified files
have not been kept properly in sync with default files.
It depends on the type of the application:
Web Application & Windows Application - use the configSource xml attribute if you are willing to store the config files in the same folder (or subfolders) as the application
Create a settings provider and also implement IApplicationSettingsProvider. Samples here and here. You might also need to use the IConfigurationManagerInternal interface to replace the default .NET configuration manager. When implementing the provider don't forget to make a difference between user settings and application settings and the roaming profiles.
If you want to get started quickly just decompile the LocalFileSettingsProvider class (the default settings provider) and change it to your needs (you might find some useles code and might need to replicate all of the classes on which it depends).
Good luck
You can include the types so you don't need to manually update the source every time.
`private void LoadSetting(System.Xml.Linq.XElement setting)
{
string name = null, type = null;
string value = null;
if (setting.Name.LocalName == "setting")
{
System.Xml.Linq.XAttribute xName = setting.Attribute("name");
if (xName != null)
{
name = xName.Value;
}
System.Xml.Linq.XAttribute xSerialize = setting.Attribute("serializeAs");
if (xSerialize != null)
{
type = xSerialize.Value;
}
System.Xml.Linq.XElement xValue = setting.Element("value");
if (xValue != null)
{
if (this[name].GetType() == typeof(System.Collections.Specialized.StringCollection))
{
foreach (string s in xValue.Element("ArrayOfString").Elements())
{
if (!((System.Collections.Specialized.StringCollection)this[name]).Contains(s))
((System.Collections.Specialized.StringCollection)this[name]).Add(s);
}
}
else
{
value = xValue.Value;
}
if (this[name].GetType() == typeof(int))
{
this[name] = int.Parse(value);
}
else if (this[name].GetType() == typeof(bool))
{
this[name] = bool.Parse(value);
}
else
{
this[name] = value;
}
}
}`
Related
I have this method which populates an object from my XML "custom configuration" config file:
public static BindingList<StationConfiguration> GetStationsFromConfigFile()
{
string xmlDocumentText = File.ReadAllText(GetConfigFilePath());
var doc = new XmlDocument();
doc.LoadXml(xmlDocumentText);
BindingList<StationConfiguration> stations = new BindingList<StationConfiguration>();
foreach (XmlNode node in doc.DocumentElement["StationsSection"].ChildNodes[0].ChildNodes)
{
stations.Add(
new StationConfiguration(
node.Attributes["Comment"].Value
, node.Attributes["FtpUsername"].Value
, node.Attributes["FtpPassword"].Value
, node.Attributes["DestinationFolderPath"].Value
));
}
return stations;
}
As you can see, I'm using File.ReadAllText to pull the contents of the XML config file into a String.
This all works well for a non-encrypted config file. But now I need to encrypt it. The way MSDN suggests doing that begins like this:
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
(Incidentally, when I look at the config.FilePath property, it shows me the correct path of the XML config file, just having an extra ".config" extension for some strange reason. It ends with ".exe.config.config" for some odd reason. But I digress...)
This is my StationConfigurationSection class:
public class StationConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("Stations", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(StationCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public StationCollection Stations
{
get
{
return (StationCollection)base["Stations"];
}
}
public override bool IsReadOnly()
{
return false;
}
}
My complete XML config file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="StationsSection" type="EcFtpClient.StationConfigurationSection, EcFtpClient" />
</configSections>
<StationsSection>
<Stations>
<add Comment="ABC" FtpUsername="eliezer" FtpPassword="secret" DestinationFolderPath="C:\Users\eliezer\Desktop\local dest" FtpTimeoutInSeconds="30" FtpHostname="ftp://192.168.1.100/" FtpFolderPath="" />
</Stations>
</StationsSection>
<startup>
<supportedRuntime version="v2.0.50727" />
</startup>
<appSettings>
<add key="NameOfService" value="ECClient" />
<add key="PollingFrequencyInSeconds" value="60" />
</appSettings>
</configuration>
I would like to use the MSDN System.Configuration approach, since it makes en/de-cryption very easy, but how can I blend their approach with what I have to make it work?
-- UPDATE --
I've got the loading of the file but am still stuck when it comes to saving the file.I've changed the loading method (at the very top of this question) to this:
public static BindingList<StationConfiguration> GetStationsFromConfigFile()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(GetConfigFilePath());
StationConfigurationSection stationsConfig = (StationConfigurationSection)config.GetSection("StationsSection");
var stationCollection = ((StationCollection)stationsConfig.Stations);
BindingList<StationConfiguration> stationsToReturn = new BindingList<StationConfiguration>();
for (int index = 0; index < stationCollection.Count; index++)
{
stationsToReturn.Add(
new StationConfiguration(
stationCollection[index].Comment,
stationCollection[index].FtpUsername,
stationCollection[index].FtpPassword,
stationCollection[index].DestinationFolderPath)
);
return stationsToReturn;
}
What that gets me is the ability to load a file regardless of whether it's encrypted or not - it loads successfully. That's great.
But I'm still not sure how to get saving working. Here's my save method:
public static void SaveStationsToConfigFile(BindingList<StationConfiguration> updatedStations, bool isConfigToBeEncrypted)
{
string configFilePath = GetConfigFilePath();
var xDoc = XDocument.Load(configFilePath);
var xDeclaration = xDoc.Declaration;
var xElement = xDoc.XPathSelectElement("//StationsSection/Stations");
// clear out existing station configurations
xDoc.Descendants("Stations").Nodes().Remove();
foreach (var station in updatedStations)
{
xElement.Add(new XElement("add",
new XAttribute("Comment", station.Station),
new XAttribute("FtpUsername", station.Username),
new XAttribute("FtpPassword", station.Password),
new XAttribute("DestinationFolderPath", station.FolderName),
new XAttribute("FtpTimeoutInSeconds", 30),
new XAttribute("FtpHostname", GetEnvironmentAppropriateFtpHostName()),
new XAttribute("FtpFolderPath", GetEnvironmentAppropriateFtpFolderPath())
));
}
xDoc.Declaration = xDeclaration;
xDoc.Save(configFilePath);
}
And in order to save it with the protection/encrpytion, I need to do something like this - in other words, using the System.Configuration.Configuration objects:
stationsConfig.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
stationsConfig.SectionInformation.ForceSave = true;
objConfig.Save(ConfigurationSaveMode.Modified);
But I'm currently still doing the save with XDocument.Save...
Is there a way to convert my XDocument into a System.Configuration.Configuration compatible object?The hack that comes to mind is after the call to XDocument.Save - to load it and save it again using the System.Configuration.Configuration stuff. But that's a hack...
I believe you have to play along with the configuration settings framework. Rather than trying to open the xml file yourself, you need to create a descendant of ConfigurationSection.
That way it will read and write from the encrypted configuration for you.
It looks like you already started that route (EcFtpClient.StationConfigurationSection), but you didn't include any of that code.
I'm trying to change the value of a key in the <appSettings> section of the app.config file at runtime so that it represents a proper date, but for some reason when running the program it doesn't change anything at all. I've tried the code and it worked in other occasions with test projects, but for some reason it doesn't work here.
Here's the code in question:
private void ChangeSyncDate(DateTime date)
{
System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); // Open App.config
config.AppSettings.Settings["SyncDate"].Value = date.ToString();
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
And this is the function that uses that code:
public List<Business.Reservation> RequestReservationRetrieval()
{
string syncstring = ConfigurationManager.AppSettings["SyncDate"];
List<Business.Reservation> webReservations;
using (repo = new ReservationRepository())
{
webReservations = repo.ObtainReservations();
}
if(syncstring.Equals("nil"))
{
ChangeSyncDate(DateTime.Now); // and here
return webReservations;
}
else
{
DateTime syncdate = Convert.ToDateTime(syncstring);
foreach(Reservation r in webReservations)
{
if (r.Date <= DateTime.Now && r.Date >= syncdate)
webReservations.Remove(r);
}
ChangeSyncDate(DateTime.Now); // here
return webReservations;
}
}
As it stands, the value in app.config that I mean to change isn't changing at all.
This is the value I'm trying to modify:
<appSettings>
<add key="SyncDate" value="nil"/>
</appSettings>
Thank you for taking the time to read this.
You should be using WebConfigurationManager not ConfigurationManager to open the AppSettings. Give it a try.
I'm not sure I am going about this right, but I am trying to write a custom configuration file for an asp.NET web project. I want to make it clear this is not a windows form, because half the stuff I find is only for those. I am trying to read and write to this file to change a couple of application settings.
I wrote this huge class using this tutorial. Here's a simplified version:
namespace Tedski.Configuration {
public class TedskiSection : ConfigurationSection {
private static ConfigurationProperty s_propName;
private static ConfigurationPropertyCollection s_properties;
static TedskiSection() {
s_propName = new ConfigurationProperty(
"name",
typeof(string),
null,
ConfigurationPropertyOptions.IsRequired
);
s_properties = new ConfigurationPropertyCollection();
s_properties.Add(s_propName);
}
protected override ConfigurationPropertyCollection Properties {
get { return s_properties; }
}
[ConfigurationProperty("name")]
public string Name {
get {
return (string)base[s_propName];
}
set {
base[s_propName] = value;
}
}
}
}
I am now not sure where to define my configuration. I can put this in my Web.config file like this:
<configuration>
<configSections>
<section name="Tedski" type="Tedski.Configuration.TedskiSection" />
</configSections>
<Tedski name="Ted" />
</configuration>
and everything loads up fine with this:
TedskiSection section = ConfigurationManager.GetSection("Tedski") as TedskiSection;
Console.WriteLine(section.Name); //produces "Ted"
However, I need to be able to load this up with the Configuration object, in order to be able to call Configuration.Save(). I can't seem to load up that specific section and save the Web.config (from what I understand this is dangerous). Another solution I'm trying out is creating a separate .config file (Tedski.config) with the same XML syntax as defined above.
I tried using this answer to load up Tedski.config, but I get an error:
ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
configMap.ExeConfigFilename = Server.MapPath("~/Tedski.config");
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigUserLevel.None);
TedskiSection section = config.GetSection("Tedski") as TedskiSection; //fails
ConfigurationErrorsException "An error occurred creating the
configuration section handler for Tedski: Could not load type
'Tedski.Configuration.TedskiSection' from assembly
'System.Configuration, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'
If I got this to load, I could then modify section.Name and call config.Save(), but I'm stuck here.
In your type property, you have to tell it which assembly contains your Tedski.Configuration.TedskiSection. For example:
<section name="Tedski" type="Tedski.Configuration.TedskiSection, TedskiAssemblyName" />
Replace "TedskiAssemblyName" there with the name of the assembly that contains the class.
I'm trying to load modules into my application dynamically, but I want to specify separate app.config files for each one.
Say I have following app.config setting for main app:
<appSettings>
<add key="House" value="Stark"/>
<add key="Motto" value="Winter is coming."/>
</appSettings>
And another for library that I load using Assembly.LoadFrom:
<appSettings>
<add key="House" value="Lannister"/>
<add key="Motto" value="Hear me roar!"/>
</appSettings>
Both libraries have a class implementing the same interface, with the following method:
public string Name
{
get { return ConfigurationManager.AppSettings["House"]; }
}
And sure enough calls to Name from both main class and loaded assembly class output Stark.
Is there a way to make main app use its own app.config and each loaded assembly use theirs? Names of config files are different in the output, so that should be possible I think.
Ok, here's the simple solution I ended up with:
Create the follow function in the utility library:
public static Configuration LoadConfig()
{
Assembly currentAssembly = Assembly.GetCallingAssembly();
return ConfigurationManager.OpenExeConfiguration(currentAssembly.Location);
}
Using it in dynamically loaded libraries like this:
private static readonly Configuration Config = ConfigHelpers.LoadConfig();
No matter how that library gets loaded it uses the correct config file.
Edit:
This might be the better solution for loading files into ASP.NET applications:
public static Configuration LoadConfig()
{
Assembly currentAssembly = Assembly.GetCallingAssembly();
string configPath = new Uri(currentAssembly.CodeBase).LocalPath;
return ConfigurationManager.OpenExeConfiguration(configPath);
}
To copy file after build you might want to add the following line to post-build events for asp app (pulling the config from library):
copy "$(SolutionDir)<YourLibProjectName>\$(OutDir)$(Configuration)\<YourLibProjectName>.dll.config" "$(ProjectDir)$(OutDir)"
As far as I know, you need separate application domains for the app.config to work separately. The creation of an AppDomainSetup allows you to specify which config file to use. Here's how I do it:
try
{
//Create the new application domain
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationBase = Path.GetDirectoryName(config.ExePath) + #"\";
ads.ConfigurationFile =
Path.GetDirectoryName(config.ExePath) + #"\" + config.ExeName + ".config";
ads.ShadowCopyFiles = "false";
ads.ApplicationName = config.ExeName;
AppDomain newDomain = AppDomain.CreateDomain(config.ExeName + " Domain",
AppDomain.CurrentDomain.Evidence, ads);
//Execute the application in the new appdomain
retValue = newDomain.ExecuteAssembly(config.ExePath,
AppDomain.CurrentDomain.Evidence, null);
//Unload the application domain
AppDomain.Unload(newDomain);
}
catch (Exception e)
{
Trace.WriteLine("APPLICATION LOADER: Failed to start application at: " +
config.ExePath);
HandleTerminalError(e);
}
Another way you could go about getting the desired effect would be to implement your configuration values inside a resource file compiled into each of your DLLs. A simple interface over the configuration object would allow you to switch out looking in an app.config versus looking in a resource file.
It may work if you change the code little bit:
public string Name
{
get {
Configuration conf = ConfigurationManager.OpenExeConfiguration("library.dll");
return conf.AppSettings.Settings["House"].Value;
}
}
I have a console application and it has app.config. When I run this code:
class Program
{
static void Main()
{
ConnectionStringsSection connSection = ConfigurationManager.GetSection("connectionStrings") as
ConnectionStringsSection;
if (connSection != null)
{
if (!connSection.SectionInformation.IsProtected)
connSection.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
else
connSection.SectionInformation.UnprotectSection();
}
Console.Read();
}
}
I get error: "This operation does not apply at runtime". I also tried giving permissions to my app.config but no luck.
What can the issue?
You can try the following:
static void Main()
{
// Get the current configuration file.
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
ConnectionStringsSection connSection = config.GetSection("connectionStrings") as
ConnectionStringsSection;
if (connSection != null)
{
if (!connSection.SectionInformation.IsProtected)
connSection.SectionInformation.ProtectSection(null);
else
connSection.SectionInformation.UnprotectSection();
}
connSection.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
Console.ReadKey();
}
I think you are supposed to use the OpenExeConfiguration method in this scenario:
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(pathToExecutable);
ConnectionStringsSection connSection =
config .GetSection("connectionStrings") as ConnectionStringsSection;
the parameter pathToExecutable should be the full path to the exe of your application, for example: "C:\application\bin\myapp.exe"
You are not supposed to encrypt sections at runtime, you encrypt them before runtime with the aspnet_setreg.exe tool. More info here.
ASP.NET then reads the encrypted sections at runtime transparently.