C#: What is System.Configuration.Configuration looking for? - c#

I have the following test method:
[TestMethod]
public void TestHarvestMethod()
{
HarvestTargetTimeRangeUTC time = new HarvestTargetTimeRangeUTC();
time.StartTimeUTC = new DateTime(2008, 01, 01, 00, 00, 00, DateTimeKind.Utc);
time.EndTimeUTC = DateTime.UtcNow;
XElement lIntelexReport = XElement.Parse(rawXml);
Harvester target = new Harvester();
target.ConfigureHarvester((System.Configuration.Configuration)null);
var res = target.Harvest(time);
Console.WriteLine(res);
}
That works in conjunction with this method:
public void ConfigureHarvester(System.Configuration.Configuration configuration)
{
reportId = Int32.Parse(configuration.AppSettings.Settings["IncidentReport"].Value);
}
to test this method:
public XElement Harvest(HarvestTargetTimeRangeUTC ranges)
{
XElement lIntelexReport = IntelexServiceCall();
return XMLConversion(QueryData(ranges, lIntelexReport));
}
The problem is that I receive Null Exception error stating that "Object reference not set to an instance of an object." on this line:
reportId = Int32.Parse(configuration.AppSettings.Settings["IncidentReport"].Value);
which I am almost positive is caused by the null value here:
target.ConfigureHarvester((System.Configuration.Configuration)null);
The System.Configuration in the above line is one used commonly in this shop, but normally for a method such as this:
public void ConfigureHarvester(System.Configuration.Configuration configuration)
{
context = new PlannedOutageFactorDataContext();
}
So my reportid field is obviously looking for something other than a null value, the problem is I don't know exactly WHAT it's looking for. Ive read the MSDN for System.Configuration but it was really no help. I would appreciate it if some one could point me in the right direction.

Its looking for you to pass it a copy of your web.config or app.config value so it can extract the information it requires from this (held in the AppSettings section)
For example
<configuration>
<appSettings>
<add key="IncidentReport" value="1" />
</appSettings>
</configuration>
If your testing a webpage, a lot of the time you need to set the location of the webservice so that it can grab a copy of the web.config from your website.
If your developing a console/desktop app then make sure you have an app.config file
Alternativly you can manually pass it in using
System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);

In your test assembly you'll need to have a app.config file. Within this file you'll need a key named 'IncidentReport" with an integer value.

Related

How can I change the value of a custom configSections variable?

I have a custom section in the configSections of my App.config file, how can I change the value of one of the variables of this section in code?
The section I would like to change is "serverConfiguration", and I want to change the value of "serverUrl":
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="serverConfiguration" type="someType" />
</configSections>
<serverConfiguration serverUrl="http://development/server/" />
</configuration>
I found this piece of code below from this previous question, App.Config change value.
It looks close to what I need, but I am not sure how to change it myself to use it for a custom section rather than the AppSettings. Will the code below work for what I am trying to do? How do I change the code below to allow me to pass this new String as serverUrl "http://staging/server/"? Thanks!
class Program
{
static void Main(string[] args)
{
UpdateSetting("lang", "Russian");
}
private static void UpdateSetting(string key, string value)
{
Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
configuration.AppSettings.Settings[key].Value = value;
configuration.Save();
ConfigurationManager.RefreshSection("appSettings");
}
}
You have an option to load the config into XML, edit the node value and save it back. Give a try with this
var xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
xmlDoc.SelectSingleNode("//serverConfiguration").Attributes["serverUrl"].Value = "http://staging/server/";
xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
Probably, it is a good idea to refresh the Config sections after file is saved.
ConfigurationManager.RefreshSection

c# embed config file within exe

I have a console program 'A' that at a given point will run program 'B' and program 'C'. However I'm having an issue with the app.config associate with each of the program. Basically program A is just a wrapper class that calls different console application, It should not have any app.config but it should use the current running program's app config. So in theory there should be only 2 app.config one for Program B and another for program C.
So if we run Program A and program B gets executed, it should use program B's app.config to get the information and after when program C gets executed it should use Program C's app.config.
Is there a way to do this? Currently i'm doing this:
var value = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["ProgramBKey"].Value;
It does not seem to work. I checked the debug on Assembly.GetExecutingAssembly().Location it's variable is the \bin\Debug\ProgramB.exe and 'ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings' has setting with Count=0 when there are key values as seen below.
sample code Program A:
static void Main(string[] args)
{
if(caseB)
B.Program.Main(args)
else if(caseC)
C.Program.Main(args)
}
sample app.config for Program B:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="ProgramBKey" value="Works" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
</configuration>
Edit: the following answer pertains to this question from the original post, "Is it possible to compile the app.config for B and C within the exe of the program."
You can use the "Embedded Resource" feature. Here's a small example of using an XML file that's been included as an embedded resource:
public static class Config
{
static Config()
{
var doc = new XmlDocument();
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Fully.Qualified.Name.Config.xml"))
{
if (stream == null)
{
throw new EndOfStreamException("Failed to read Fully.Qualified.Name.Config.xml from the assembly's embedded resources.");
}
using (var reader = new StreamReader(stream))
{
doc.LoadXml(reader.ReadToEnd());
}
}
XmlElement aValue = null;
XmlElement anotherValue = null;
var config = doc["config"];
if (config != null)
{
aValue = config["a-value"];
anotherValue = config["another-value"];
}
if (aValue == null || anotheValue == null)
{
throw new XmlException("Failed to parse Config.xml XmlDocument.");
}
AValueProperty = aValue.InnerText;
AnotherValueProperty = anotherValue.InnerText;
}
}
You can have multiple application using the same config file. That way when you switch applications, they can both find their own parts of the config file.
The way I usually do it is... first let each application "do its own thing", then copy the relevant sections of config file A into config file B.
It will look like this:
<configSections>
<sectionGroup>
<sectionGroup name="applicationSettings"...A>
<sectionGroup name="userSettings"...A>
<sectionGroup name="applicationSettings"...B>
<sectionGroup name="userSettings"...B>
<applicationSettings>
<A.Properties.Settings>
<B.Properties.Settings>
<userSettings>
<A.Properties.Settings>
<B.Properties.Settings>
For me, the whole thing sounds like a "design issue". Why should you want to open Programm B with the config of Programm A?
Are you the author of all those Programms? You might want to use a dll-file instead. This will save you the trouble as all code runs with the config of the Programm running.
Here how you can do it:
Make App.config as "Embedded Resource" in the properties/build action
Copy to output Directory : Do not copy
Add this code to Proram.cs Main
if (!File.Exists(Application.ExecutablePath + ".config"))
{
File.WriteAllBytes(Application.ExecutablePath + ".config", ResourceReadAllBytes("App.config"));
Process.Start(Application.ExecutablePath);
return;
}
Here are the needed functions:
public static Stream GetResourceStream(string resName)
{
var currentAssembly = Assembly.GetExecutingAssembly();
return currentAssembly.GetManifestResourceStream(currentAssembly.GetName().Name + "." + resName);
}
public static byte[] ResourceReadAllBytes(string resourceName)
{
var file = GetResourceStream(resourceName);
byte[] all;
using (var reader = new BinaryReader(file))
{
all = reader.ReadBytes((int)file.Length);
}
file.Dispose();
return all;
}

How can I retrieve a generated value from my C#-code into my web.config file?

I know, that the following code looks naive, but it should only bring to mind, what I want to achieve.
My web.config file:
<connectionStrings>
<add name="ADConnectionString" connectionString="AppData.GetConnectionString()" />
</connectionStrings>
I want to get the string from C#:
public class AppData
{
public static string GetConnectionString()
{
return "LDAP://expample.domain.com:389/DC=example,DC=domain,DC=com";
}
}
I know it is possible to get data from the web.config in the C# code (The AppSettings for example). But is the opposite also possible?
I dont think it is possible as it is just an XML file. All you can do in XML file is add comment,create nodes,add attributes or add nested elements etc etc but you cant add any code to it.
However I think there are certain languages that allow you to do so
https://stackoverflow.com/questions/24486772/write-php-code-inside-xml-file
I DO NOT recommend use it. But you actually can do something like this:
using System.Web.Configuration;
using System.Configuration;
var config = WebConfigurationManager.OpenWebConfiguration(null);
var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings");
connectionStringsSection.ConnectionStrings["ADConnectionString"].ConnectionString = AppData.GetConnectionString();
config.Save();
Use Reflection
Here's an example:
private string test = WebConfigurationManager.ConnectionStrings["Test"].ConnectionString; //returns "ExecuteTest" -- note! no parenthesis!
protected void Page_Load(object sender, EventArgs e)
{
MethodInfo m = this.GetType().GetMethod(test); //expects static method
if (m != null)
{
object result = m.Invoke(this, new object[] { });
}
}
private static void ExecuteTest()
{
//do stuff
}
But no, you can't edit/pull data into the web.config on the fly. Changing the web.config will cause the application to restart killing all sessions. It's possible to build the web.config prior to starting but once it's up and running it's essentially locked down.

How to get the values of a ConfigurationSection of type NameValueSectionHandler

I'm working with C#, Framework 3.5 (VS 2008).
I'm using the ConfigurationManager to load a config (not the default app.config file) into a Configuration object.
Using the Configuration class, I was able to get a ConfigurationSection, but I could not find a way to get the values of that section.
In the config, the ConfigurationSection is of type System.Configuration.NameValueSectionHandler.
For what it worth, when I used the method GetSection of the ConfigurationManager (works only when it was on my default app.config file), I received an object type, that I could cast into collection of pairs of key-value, and I just received the value like a Dictionary. I could not do such cast when I received ConfigurationSection class from the Configuration class however.
EDIT:
Example of the config file:
<configuration>
<configSections>
<section name="MyParams"
type="System.Configuration.NameValueSectionHandler" />
</configSections>
<MyParams>
<add key="FirstParam" value="One"/>
<add key="SecondParam" value="Two"/>
</MyParams>
</configuration>
Example of the way i was able to use it when it was on app.config (the "GetSection" method is for the default app.config only):
NameValueCollection myParamsCollection =
(NameValueCollection)ConfigurationManager.GetSection("MyParams");
Console.WriteLine(myParamsCollection["FirstParam"]);
Console.WriteLine(myParamsCollection["SecondParam"]);
Suffered from exact issue. Problem was because of NameValueSectionHandler in .config file. You should use AppSettingsSection instead:
<configuration>
<configSections>
<section name="DEV" type="System.Configuration.AppSettingsSection" />
<section name="TEST" type="System.Configuration.AppSettingsSection" />
</configSections>
<TEST>
<add key="key" value="value1" />
</TEST>
<DEV>
<add key="key" value="value2" />
</DEV>
</configuration>
then in C# code:
AppSettingsSection section = (AppSettingsSection)ConfigurationManager.GetSection("TEST");
btw NameValueSectionHandler is not supported any more in 2.0.
Here's a good post that shows how to do it.
If you want to read the values from a file other than the app.config, you need to load it into the ConfigurationManager.
Try this method: ConfigurationManager.OpenMappedExeConfiguration()
There's an example of how to use it in the MSDN article.
Try using an AppSettingsSection instead of a NameValueCollection. Something like this:
var section = (AppSettingsSection)config.GetSection(sectionName);
string results = section.Settings[key].Value;
Source:
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d5079420-40cb-4255-9b3b-f9a41a1f7ad2/
The only way I can get this to work is to manually instantiate the section handler type, pass the raw XML to it, and cast the resulting object.
Seems pretty inefficient, but there you go.
I wrote an extension method to encapsulate this:
public static class ConfigurationSectionExtensions
{
public static T GetAs<T>(this ConfigurationSection section)
{
var sectionInformation = section.SectionInformation;
var sectionHandlerType = Type.GetType(sectionInformation.Type);
if (sectionHandlerType == null)
{
throw new InvalidOperationException(string.Format("Unable to find section handler type '{0}'.", sectionInformation.Type));
}
IConfigurationSectionHandler sectionHandler;
try
{
sectionHandler = (IConfigurationSectionHandler)Activator.CreateInstance(sectionHandlerType);
}
catch (InvalidCastException ex)
{
throw new InvalidOperationException(string.Format("Section handler type '{0}' does not implement IConfigurationSectionHandler.", sectionInformation.Type), ex);
}
var rawXml = sectionInformation.GetRawXml();
if (rawXml == null)
{
return default(T);
}
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(rawXml);
return (T)sectionHandler.Create(null, null, xmlDocument.DocumentElement);
}
}
The way you would call it in your example is:
var map = new ExeConfigurationFileMap
{
ExeConfigFilename = #"c:\\foo.config"
};
var configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
var myParamsSection = configuration.GetSection("MyParams");
var myParamsCollection = myParamsSection.GetAs<NameValueCollection>();
This is an old question, but I use the following class to do the job. It's based on Scott Dorman's blog:
public class NameValueCollectionConfigurationSection : ConfigurationSection
{
private const string COLLECTION_PROP_NAME = "";
public IEnumerable<KeyValuePair<string, string>> GetNameValueItems()
{
foreach ( string key in this.ConfigurationCollection.AllKeys )
{
NameValueConfigurationElement confElement = this.ConfigurationCollection[key];
yield return new KeyValuePair<string, string>
(confElement.Name, confElement.Value);
}
}
[ConfigurationProperty(COLLECTION_PROP_NAME, IsDefaultCollection = true)]
protected NameValueConfigurationCollection ConfCollection
{
get
{
return (NameValueConfigurationCollection) base[COLLECTION_PROP_NAME];
}
}
The usage is straightforward:
Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
NameValueCollectionConfigurationSection config =
(NameValueCollectionConfigurationSection) configuration.GetSection("MyParams");
NameValueCollection myParamsCollection = new NameValueCollection();
config.GetNameValueItems().ToList().ForEach(kvp => myParamsCollection.Add(kvp));
Here are some examples from this blog mentioned earlier:
<configuration>
<Database>
<add key="ConnectionString" value="data source=.;initial catalog=NorthWind;integrated security=SSPI"/>
</Database>
</configuration>
get values:
NameValueCollection db = (NameValueCollection)ConfigurationSettings.GetConfig("Database");
labelConnection2.Text = db["ConnectionString"];
-
Another example:
<Locations
ImportDirectory="C:\Import\Inbox"
ProcessedDirectory ="C:\Import\Processed"
RejectedDirectory ="C:\Import\Rejected"
/>
get value:
Hashtable loc = (Hashtable)ConfigurationSettings.GetConfig("Locations");
labelImport2.Text = loc["ImportDirectory"].ToString();
labelProcessed2.Text = loc["ProcessedDirectory"].ToString();
Try this;
Credit: https://www.limilabs.com/blog/read-system-net-mailsettings-smtp-settings-web-config
SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
string from = section.From;
string host = section.Network.Host;
int port = section.Network.Port;
bool enableSsl = section.Network.EnableSsl;
string user = section.Network.UserName;
string password = section.Network.Password;
This works like a charm
dynamic configSection = ConfigurationManager.GetSection("MyParams");
var theValue = configSection["FirstParam"];

Variables within app.config/web.config

Is it is possible to do something like the following in the app.config or web.config files?
<appSettings>
<add key="MyBaseDir" value="C:\MyBase" />
<add key="Dir1" value="[MyBaseDir]\Dir1"/>
<add key="Dir2" value="[MyBaseDir]\Dir2"/>
</appSettings>
I then want to access Dir2 in my code by simply saying:
ConfigurationManager.AppSettings["Dir2"]
This will help me when I install my application in different servers and locations wherein I will only have to change ONE entry in my entire app.config.
(I know I can manage all the concatenation in code, but I prefer it this way).
A slightly more complicated, but far more flexible, alternative is to create a class that represents a configuration section. In your app.config / web.config file, you can have this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- This section must be the first section within the <configuration> node -->
<configSections>
<section name="DirectoryInfo" type="MyProjectNamespace.DirectoryInfoConfigSection, MyProjectAssemblyName" />
</configSections>
<DirectoryInfo>
<Directory MyBaseDir="C:\MyBase" Dir1="Dir1" Dir2="Dir2" />
</DirectoryInfo>
</configuration>
Then, in your .NET code (I'll use C# in my example), you can create two classes like this:
using System;
using System.Configuration;
namespace MyProjectNamespace {
public class DirectoryInfoConfigSection : ConfigurationSection {
[ConfigurationProperty("Directory")]
public DirectoryConfigElement Directory {
get {
return (DirectoryConfigElement)base["Directory"];
}
}
public class DirectoryConfigElement : ConfigurationElement {
[ConfigurationProperty("MyBaseDir")]
public String BaseDirectory {
get {
return (String)base["MyBaseDir"];
}
}
[ConfigurationProperty("Dir1")]
public String Directory1 {
get {
return (String)base["Dir1"];
}
}
[ConfigurationProperty("Dir2")]
public String Directory2 {
get {
return (String)base["Dir2"];
}
}
// You can make custom properties to combine your directory names.
public String Directory1Resolved {
get {
return System.IO.Path.Combine(BaseDirectory, Directory1);
}
}
}
}
Finally, in your program code, you can access your app.config variables, using your new classes, in this manner:
DirectoryInfoConfigSection config =
(DirectoryInfoConfigSection)ConfigurationManager.GetSection("DirectoryInfo");
String dir1Path = config.Directory.Directory1Resolved; // This value will equal "C:\MyBase\Dir1"
You can accomplish using my library Expansive. Also available on nuget here.
It was designed with this as a primary use-case.
Moderate Example (using AppSettings as default source for token expansion)
In app.config:
<configuration>
<appSettings>
<add key="Domain" value="mycompany.com"/>
<add key="ServerName" value="db01.{Domain}"/>
</appSettings>
<connectionStrings>
<add name="Default" connectionString="server={ServerName};uid=uid;pwd=pwd;Initial Catalog=master;" provider="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Use the .Expand() extension method on the string to be expanded:
var connectionString = ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
connectionString.Expand() // returns "server=db01.mycompany.com;uid=uid;pwd=pwd;Initial Catalog=master;"
or
Use the Dynamic ConfigurationManager wrapper "Config" as follows (Explicit call to Expand() not necessary):
var serverName = Config.AppSettings.ServerName;
// returns "db01.mycompany.com"
var connectionString = Config.ConnectionStrings.Default;
// returns "server=db01.mycompany.com;uid=uid;pwd=pwd;Initial Catalog=master;"
Advanced Example 1 (using AppSettings as default source for token expansion)
In app.config:
<configuration>
<appSettings>
<add key="Environment" value="dev"/>
<add key="Domain" value="mycompany.com"/>
<add key="UserId" value="uid"/>
<add key="Password" value="pwd"/>
<add key="ServerName" value="db01-{Environment}.{Domain}"/>
<add key="ReportPath" value="\\{ServerName}\SomeFileShare"/>
</appSettings>
<connectionStrings>
<add name="Default" connectionString="server={ServerName};uid={UserId};pwd={Password};Initial Catalog=master;" provider="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Use the .Expand() extension method on the string to be expanded:
var connectionString = ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
connectionString.Expand() // returns "server=db01-dev.mycompany.com;uid=uid;pwd=pwd;Initial Catalog=master;"
Good question.
I don't think there is. I believe it would have been quite well known if there was an easy way, and I see that Microsoft is creating a mechanism in Visual Studio 2010 for deploying different configuration files for deployment and test.
With that said, however; I have found that you in the ConnectionStrings section have a kind of placeholder called "|DataDirectory|". Maybe you could have a look at what's at work there...
Here's a piece from machine.config showing it:
<connectionStrings>
<add
name="LocalSqlServer"
connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
Usally, I end up writing a static class with properties to access each of the settings of my web.config.
public static class ConfigManager
{
public static string MyBaseDir
{
return ConfigurationManager.AppSettings["MyBaseDir"].toString();
}
public static string Dir1
{
return MyBaseDir + ConfigurationManager.AppSettings["Dir1"].toString();
}
}
Usually, I also do type conversions when required in this class. It allows to have a typed access to your config, and if settings change, you can edit them in only one place.
Usually, replacing settings with this class is relatively easy and provides a much greater maintainability.
I thought I just saw this question.
In short, no, there's no variable interpolation within an application configuration.
You have two options
You could roll your own to substitute variables at runtime
At build time, massage the application configuration to the particular specifics of the target deployment environment. Some details on this at dealing with the configuration-nightmare
You have a couple of options. You could do this with a build / deploy step which would process your configuration file replacing your variables with the correct value.
Another option would be to define your own Configuration section which supported this. For example imagine this xml:
<variableAppSettings>
<variables>
<add key="#BaseDir" value="c:\Programs\Widget"/>
</variables>
<appSettings>
<add key="PathToDir" value="#BaseDir\Dir1"/>
</appSettings>
</variableAppSettings>
Now you would implement this using custom configuration objects which would handle replacing the variables for you at runtime.
You can use environment variables in your app.config for that scenario you describe
<configuration>
<appSettings>
<add key="Dir1" value="%MyBaseDir%\Dir1"/>
</appSettings>
</configuration>
Then you can easily get the path with:
var pathFromConfig = ConfigurationManager.AppSettings["Dir1"];
var expandedPath = Environment.ExpandEnvironmentVariables(pathFromConfig);
Inside <appSettings> you can create application keys,
<add key="KeyName" value="Keyvalue"/>
Later on you can access these values using:
ConfigurationManager.AppSettings["Keyname"]
I would suggest you DslConfig. With DslConfig you can use hierarchical config files from Global Config, Config per server host to config per application on each server host (see the AppSpike).
If this is to complicated for you you can just use the global config Variables.var
Just configure in Varibales.var
baseDir = "C:\MyBase"
Var["MyBaseDir"] = baseDir
Var["Dir1"] = baseDir + "\Dir1"
Var["Dir2"] = baseDir + "\Dir2"
And get the config values with
Configuration config = new DslConfig.BooDslConfiguration()
config.GetVariable<string>("MyBaseDir")
config.GetVariable<string>("Dir1")
config.GetVariable<string>("Dir2")
I don't think you can declare and use variables to define appSettings keys within a configuration file. I've always managed concatenations in code like you.
I'm struggling a bit with what you want, but you can add an override file to the app settings then have that override file set on a per environment basis.
<appSettings file="..\OverrideSettings.config">
For rolling out products where we need to configure a lot of items with similar values, we use small console apps that read the XML and update based on the parameters passed in. These are then called by the installer after it has asked the user for the required information.
I would recommend following Matt Hamsmith's solution. If it's an issue to implement, then why not create an extension method that implements this in the background on the AppSettings class?
Something like:
public static string GetValue(this NameValueCollection settings, string key)
{
}
Inside the method you search through the DictionaryInfoConfigSection using Linq and return the value with the matching key. You'll need to update the config file though, to something along these lines:
<appSettings>
<DirectoryMappings>
<DirectoryMap key="MyBaseDir" value="C:\MyBase" />
<DirectoryMap key="Dir1" value="[MyBaseDir]\Dir1"/>
<DirectoryMap key="Dir2" value="[MyBaseDir]\Dir2"/>
</DirectoryMappings>
</appSettings>
I came up with this solution:
In the application Settings.settings I defined a variable ConfigurationBase (with type=string Scope=Application)
I introduced a variable in the target attributes in the Settings.settings, all those attributes had to be set to Scope=User
In the app.xaml.cs I read out the value if the ConfigurationBase
In the app.xaml.cs I replaced all variables with the ConfigurationBase value. In order to replace the values at run-time the attributes had to be set to Scopr=User
I'm not really happy with this solution because I have to change all attributes manually, if I add a new one I have to regard it in the app.xaml.cs.
Here a code snippet from the App.xaml.cs:
string configBase = Settings.Default.ConfigurationBase;
Settings.Default.CommonOutput_Directory = Settings.Default.CommonOutput_Directory.Replace("${ConfigurationBase}", configBase);
UPDATE
Just found an improvement (again a code snippet from the app.xaml.cs):
string configBase = Settings.Default.ConfigurationBase;
foreach (SettingsProperty settingsProperty in Settings.Default.Properties)
{
if (!settingsProperty.IsReadOnly && settings.Default[settingsProperty.Name] is string)
{
Settings.Default[settingsProperty.Name] = ((string)Settings.Default[settingsProperty.Name]).Replace("${ConfigurationBase}", configBase);
}
}
Now the replacements work for all attributes in my settings that have Type=string and Scope=User. I think I like it this way.
UPDATE2
Apparently setting Scope=Application is not required when running over the properties.
Three Possible Solutions
I know I'm coming late to the party, I've been looking if there were any new solutions to the variable configuration settings problem. There are a few answers that touch the solutions I have used in the past but most seem a bit convoluted. I thought I'd look at my old solutions and put the implementations together so that it might help people that are struggling with the same problem.
For this example I have used the following app setting in a console application:
<appSettings>
<add key="EnvironmentVariableExample" value="%BaseDir%\bin"/>
<add key="StaticClassExample" value="bin"/>
<add key="InterpollationExample" value="{0}bin"/>
</appSettings>
1. Use environment variables
I believe autocro autocro's answer touched on it. I'm just doing an implementation that should suffice when building or debugging without having to close visual studio. I have used this solution back in the day...
Create a pre-build event that will use the MSBuild variables
Warning: Use a variable that will not be replaced easily so use your project name or something similar as a variable name.
SETX BaseDir "$(ProjectDir)"
Reset variables; using something like the following:
Refresh Environment Variables on Stack Overflow
Use the setting in your code:
'
private void Test_Environment_Variables()
{
string BaseDir = ConfigurationManager.AppSettings["EnvironmentVariableExample"];
string ExpandedPath = Environment.ExpandEnvironmentVariables(BaseDir).Replace("\"", ""); //The function addes a " at the end of the variable
Console.WriteLine($"From within the C# Console Application {ExpandedPath}");
}
'
2. Use string interpolation:
Use the string.Format() function
`
private void Test_Interpollation()
{
string ConfigPath = ConfigurationManager.AppSettings["InterpollationExample"];
string SolutionPath = Path.GetFullPath(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, #"..\..\"));
string ExpandedPath = string.Format(ConfigPath, SolutionPath.ToString());
Console.WriteLine($"Using old interpollation {ExpandedPath}");
}
`
3. Using a static class, This is the solution I mostly use.
The implementation
`
private void Test_Static_Class()
{
Console.WriteLine($"Using a static config class {Configuration.BinPath}");
}
`
The static class
`
static class Configuration
{
public static string BinPath
{
get
{
string ConfigPath = ConfigurationManager.AppSettings["StaticClassExample"];
string SolutionPath = Path.GetFullPath(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, #"..\..\"));
return SolutionPath + ConfigPath;
}
}
}
`
Project Code:
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<appSettings>
<add key="EnvironmentVariableExample" value="%BaseDir%\bin"/>
<add key="StaticClassExample" value="bin"/>
<add key="InterpollationExample" value="{0}bin"/>
</appSettings>
</configuration>
Program.cs
using System;
using System.Configuration;
using System.IO;
namespace ConfigInterpollation
{
class Program
{
static void Main(string[] args)
{
new Console_Tests().Run_Tests();
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
}
internal class Console_Tests
{
public void Run_Tests()
{
Test_Environment_Variables();
Test_Interpollation();
Test_Static_Class();
}
private void Test_Environment_Variables()
{
string ConfigPath = ConfigurationManager.AppSettings["EnvironmentVariableExample"];
string ExpandedPath = Environment.ExpandEnvironmentVariables(ConfigPath).Replace("\"", "");
Console.WriteLine($"Using environment variables {ExpandedPath}");
}
private void Test_Interpollation()
{
string ConfigPath = ConfigurationManager.AppSettings["InterpollationExample"];
string SolutionPath = Path.GetFullPath(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, #"..\..\"));
string ExpandedPath = string.Format(ConfigPath, SolutionPath.ToString());
Console.WriteLine($"Using interpollation {ExpandedPath}");
}
private void Test_Static_Class()
{
Console.WriteLine($"Using a static config class {Configuration.BinPath}");
}
}
static class Configuration
{
public static string BinPath
{
get
{
string ConfigPath = ConfigurationManager.AppSettings["StaticClassExample"];
string SolutionPath = Path.GetFullPath(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, #"..\..\"));
return SolutionPath + ConfigPath;
}
}
}
}
Pre-build event:
Project Settings -> Build Events

Categories

Resources