Can not get FileInfo back from configuration - c#

<configuration>
<configSections>
<sectionGroup name="loggingConfigs">
<section name="LoggingParameters"
type="Framework.Logging.LoggingParameters, Framework.Logging" />
</sectionGroup>
</configSections>
<loggingConfigs>
<LoggingParameters
myconfig="C:mydir\mypath1\mypath2\log4net.config"
/>
</loggingConfigs>
</configuration>
public class LoggingParameters : ConfigurationSection
{
[ConfigurationProperty("myconfig", IsRequired = true)]
private string ConfigFileLoc
{
get { return (string)this["myconfig"]; }
}
public FileInfo MyConfigFile
{
get
{
string s = ConfigFileLoc; <--- getting empty string here..don't know why
return new FileInfo(s);
}
}
}
When I make the following call else where in my application,
FileInfo f = new Framework.Logging.LoggingParameters().MyConfigFile;
the ConfigFileLoc always comes back as blank. I can not figure out why its blank.. why the string is empty.. please help.

I had to remove the SectionGroup (don't know if that is a requirement for you)
<configSections>
<section name="LoggingParameters"
type="Framework.Logging.LoggingParameters, Framework.Logging" />
</configSections>
<LoggingParameters myconfig="C:mydir\mypath1\mypath2\log4net.config" />
And got the FileInfo with this code (path is not empty anymore)
LoggingParameters config = (LoggingParameters)ConfigurationSettings.GetConfig("LoggingParameters");
FileInfo fInfo = config.MyConfigFile;
This post by Scott Mitchell may help you

I would avoid writing configuration access code by hand. It's too easy to foobar. Check out this addin that lets you visually design your configurations. Let it write the boilerplate code for you while you focus solely on the visual designer and your problem domain.
http://csd.codeplex.com/

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

.NET configuration with multiple instances of a set of parameters

I have an application that can connect to several servers. The actual number of servers is not known until runtime,and may change from day to day. It takes several actual parameters to fully define a server.
I'm trying to configure the application using .NET support for application configurations.
The configuration file looks something like:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup
name="userSettings"
type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="server"
type="System.Configuration.SingleTagSectionHandler"
allowExeDefinition="MachineToLocalUser"
requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<server name="washington">
<add name="host" value="abc.presidents.com"/>
<add name="port" value="1414"/>
<add name="credentials" value="george"/>
</server>
<server name="adams">
<add name="host" value="def.presidents.com"/>
<add name="port" value="1419"/>
<add name="credentials" value="john"/>
</server>
<!--insert more server definitions here -->
</userSettings>
</configuration>
And I tried to read this with code something like this (the WriteLines are for diagnosing the problem. They will go away when/if it works.):
try
{
var exeConfiguration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
Console.WriteLine(exeConfiguration);
var userSettings = exeConfiguration.GetSectionGroup("userSettings");
Console.WriteLine("{0}: {1}", userSettings, userSettings.Name);
var sections = userSettings.Sections;
Console.WriteLine("{0}: {1}", sections, sections.Count);
foreach (ConfigurationSection section in sections)
{
Console.WriteLine("{0}", section);
// todo Here's where we capture information about a server
}
}
catch (System.Exception ex)
{
Console.WriteLine("Exception: {0}", ex.Message);
}
This throws an exception from the foreach and produces the following output:
System.Configuration.Configuration
System.Configuration.UserSettingsGroup: userSettings
System.Configuration.ConfigurationSectionCollection: 1
Exception: Sections must only appear once per config file. See the help topic <location> for exceptions.
If I remove the second server, leaving only washington, it "works" producing this output:
System.Configuration.Configuration
System.Configuration.ConfigurationSectionGroup: userSettings
System.Configuration.ConfigurationSectionCollection: 1
System.Configuration.DefaultSection
I've tried other variations on the theme (nested sectionGroups, etc.) without finding a solution. The various tutorial examples I've found seem to want me to create a separate class for each Server. This is obviously impractical, and should be unnecessary since all servers are created equal.
Questions:
Does System.Configuration support the concept of a collection of sets of related settings (like an array of structures)?
If so.. how?
If not.. is there another way to store persistent configuration information that does support this concept, or am I going to have to roll-my-own?
Progress Report
Based on the partial answer by Robert McKee I modified the xml like so:
<section name="servers"
type="System.Configuration.DefaultSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
and
<servers>
<add name="washington" host="abc.presidents.com" port="1414" credentials="george"/>
<add name="adams" host="def.presidents.com" port="1419" credentials="john"/>
<!--insert more server definitions here -->
</servers>
This is an improvement with one fatal problem. As you can see I changed the type attribute of the section element to name the class ystem.Configuration.DefaultSection DefaultSection succeeds at reading the configuration information (I think, at least it does not complain.) but it does not expose any way to access the information it read!
Therefore I need to use some other type of *Section class. Microsoft provides 95 classes derived from the ConfigurationSection base class, but all of them except DefaultSection and ClientSettingsSection appear to be targeted at special cases (urls, database connection strings, date and times, etc...) ClientSettingsSection won't even read the servers section -- complaining that <add/> is not a valid nested element.
So, bottom line is I'm going to have to write a custom ConfigurationSection to handle these settings. If I get it working I'll add an answer with the ultimate solution (unless someone provide a better answer first.)
Conclusions
Collections of property sets cannot be supported without creating custom property classes.
Support for creating custom property classes is pretty good.
Documentation for creating custom property classes is abysmal, however I did finally find a decent overview document that helped me find an answer.
The Configuration file
The app.config file I ended up with is:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="executiveBranch"
type="PropProto.Properties.ExecutiveBranchSection, PropProto, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
allowExeDefinition="MachineToLocalUser"
requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<executiveBranch>
<presidents>
<president key="first"
name="George Washington"
legacy="The Fother of Our County"
/>
<president key="honestAbe"
name="Abraham Lincoln"
legacy="Freed the Slaves"
/>
<president key="who"
name="Chester Arthur"
/>
<president key="dubya"
name="George W. Bush"
legacy="Mission Accomplished!!!"
/>
<president key="barack"
name="Barack Obama"
legacy="Affordable Health Care"
/>
</presidents>
</executiveBranch>
</userSettings>
</configuration>
There is one more nesting level than I had expected (or want). That's because presidents is a ConfigurationElementCollection and userSettings cannot include a ConfigurationElementCollection directly, so I had to introduce executiveBranch.
Using the Configuration
The code to read these settings is:
var exeConfiguration = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.PerUserRoamingAndLocal);
var userSettings = exeConfiguration.GetSectionGroup("userSettings");
var executiveBranch = (ExecutiveBranchSection)userSettings.Sections.Get(
ExecutiveBranchSection.Tag);
var presidents = executiveBranch.Presidents;
foreach (President president in presidents)
{
Console.WriteLine("{0}: {1}", president.Name, president.Legacy);
}
The Custom Properties Classes
And the custom classes to make all this work:
public class ExecutiveBranchSection : ConfigurationSection
{
public const string Tag = "executiveBranch";
[ConfigurationProperty(PresidentCollection.Tag)]
public PresidentCollection Presidents { get { return (PresidentCollection)base[PresidentCollection.Tag]; } }
}
[ConfigurationCollection(typeof(President),
CollectionType = ConfigurationElementCollectionType.BasicMap,
AddItemName = President.Tag)]
public class PresidentCollection : ConfigurationElementCollection
{
public const string Tag = "presidents";
protected override string ElementName { get { return President.Tag; } }
public President this[int index]
{
get { return (President)base.BaseGet(index); }
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
base.BaseAdd(index, value);
}
}
new public President this[string name] { get { return (President)base.BaseGet(name); } }
protected override ConfigurationElement CreateNewElement()
{
return new President();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as President).Key;
}
}
public class President : ConfigurationElement
{
public const string Tag = "president";
[ConfigurationProperty("key", IsRequired = true)]
public string Key { get { return (string)base["key"]; } }
[ConfigurationProperty("name", IsRequired = true)]
public string Name { get { return (string)base["name"]; } }
[ConfigurationProperty("legacy", IsRequired = false)]
public string Legacy { get { return (string)base["legacy"]; } }
}
Haven't worked with custom config sections, but from a purely logical point of view, isn't this more appropriate:
<userSettings>
<servers>
<add name="washington" host="abc.presidents.com" port="1414" credentials="george"/>
<add name="adams" host="def.presidents.com" port="1419" credentials="john"/>
<!--insert more server definitions here -->
</servers>
<!-- insert more user settings here -->
</userSettings>
You can do it other ways, but if you want a collection of "server", then it must be in a grouping element like "servers". You can't just have multiple "server" in a parent element that can also contain other types of children. The tag within the group is almost always "add".
In any case "System.Configuration.SingleTagSectionHandler" definitely is not the correct type.

Is there a way to write to a Custom app.Config Configuration from code

So far I have:
<section name="PinnedPhotos" type="PhotoViewer.PinnedPhotos, PhotoViewer" allowLocation="true" allowDefinition="Everywhere"/>
<PinnedPhotos>
<add location="Location1.jpg"/>
<add location="Location2.jpg"/>
</PinnedPhotos>
in my app.Config
and in my Code I have:
PinnedPhotos config = (PinnedPhotos)System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location).Sections["PinnedPhotos"];
But I would like to update and save config from code e.g. Adding more photos to be pinned to the existing list. Is there a way to do this?
EDIT
var isReadOnly = config.Photos.IsReadOnly();
returns False... So I guess there should be a way to write to this collection?
Got it:
I needed to add the two Methods to my Collection:
public void Add(string location)
{
PhotoElement pe = new PhotoElement(location);
BaseAdd(pe);
}
public void Remove(string location)
{
BaseRemove(location);
}
and then It was just a Case of calling:
config.CurrentConfiguration.Save();

An error occurred creating the configuration section handler

I have a dot.NET 4.0 web application with a custom section defined:
<configuration>
<configSections>
<section name="registrations" type="System.Configuration.IgnoreSectionHandler, System.Configuration, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="true" restartOnExternalChanges="true" allowLocation="true"/>
....
at the end of the web.config file I have the respective section:
....
<registrations>
.....
</registrations>
</configuration>
Every time I call System.Configuration.ConfigurationManager.GetSection("registrations"); I get the following exception:
An error occurred creating the configuration section handler for registrations: The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) (C:\...\web.config line 13)
I'm also using Unity but don't know if that's in any way related to the error.
Have you faced this error before? How can I fix it? Do I need to replace the IgnoreSectionHandler with something else?
Given this app.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="registrations" type="MyApp.MyConfigurationSection, MyApp" />
</configSections>
<registrations myValue="Hello World" />
</configuration>
Then try using this:
namespace MyApp
{
class Program
{
static void Main(string[] args) {
var config = ConfigurationManager.GetSection(MyConfigurationSection.SectionName) as MyConfigurationSection ?? new MyConfigurationSection();
Console.WriteLine(config.MyValue);
Console.ReadLine();
}
}
public class MyConfigurationSection : ConfigurationSection
{
public const String SectionName = "registrations";
[ConfigurationProperty("myValue")]
public String MyValue {
get { return (String)this["myValue"]; }
set { this["myValue"] = value; }
}
}
}
You are missing the Namespace in the type attribute of your section in App.Config. Infact you dont need the full assembly info in there either. only the namespace is enough
Updated 1
yourcustomconfigclass config =(yourcustomconfigclass)System.Configuration.ConfigurationManager.GetSection(
"registrations");
and in config file only write
<section name="registrations" type="System.Configuration.IgnoreSectionHandler" requirePermission="true" restartOnExternalChanges="true" allowLocation="true"/>

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