App.config/web.config 中的变量

是否可以在 app.configweb.config文件中执行以下操作?

<appSettings>
<add key="MyBaseDir" value="C:\MyBase" />
<add key="Dir1" value="[MyBaseDir]\Dir1"/>
<add key="Dir2" value="[MyBaseDir]\Dir2"/>
</appSettings>

然后我想在我的代码中访问 Dir2,只需说:

 ConfigurationManager.AppSettings["Dir2"]

这将有助于我当我安装我的应用程序在不同的服务器和位置,我只需要改变一个条目在我的整个 app.config。 (我知道我可以管理代码中的所有连接,但我更喜欢这种方式)。

157690 次浏览

Inside <appSettings> you can create application keys,

<add key="KeyName" value="Keyvalue"/>

Later on you can access these values using:

ConfigurationManager.AppSettings["Keyname"]

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">

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.

I thought I just saw this question.

In short, no, there's no variable interpolation within an application configuration.

You have two options

  1. You could roll your own to substitute variables at runtime
  2. 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

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.

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>

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"

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 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>

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;"

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 came up with this solution:

  1. In the application Settings.settings I defined a variable ConfigurationBase (with type=string Scope=Application)
  2. I introduced a variable in the target attributes in the Settings.settings, all those attributes had to be set to Scope=User
  3. In the app.xaml.cs I read out the value if the ConfigurationBase
  4. 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.

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);

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