In the first topic we used a default configuration which was easy, but offers only the default functionality for using .config files. If you want to customize where configurations are stored and how configurations are accessed you can explicitly configure an IConfigurationProvider used with AppConfiguration.

The following providers are available:

  • ConfigurationFileConfigurationProvider
    Stores information in standard .NET .config files, like web.config, app.config or possibly in externally linked .config files. This is the most common configuration file format for typical applications. One big advantage of .config files is that especially in Web apps .NET can detect changes to the file and restart applications to reload settings automatically. This is the default implementation used by the base constructors.

  • XmlFileConfigurationProvider
    This format is based on XML serialization that stores configuration data in standalone XML files. Unlike the .config files these files aren't tracked by .NET so any changes to the files need be manually updated by reloading the configuration class.

  • SqlServerConfigurationProvider
    Stores configuration information in a single field of a database as an XML string. You can store multiple configurations in a single table by using a (int) Key value for each different configuration. Database configuration storage can be useful when configuration data might be shared across machines.

  • StringConfigurationProvider
    This format doesn't actually store configuration data at all, but simply provides serialization from the class to an XML string and loading up the class from an XML string. This can be useful if you have some custom storage mechanism and you don't want to create a custom provider. As long as your custom storage mechanism can accept a string you can use this provider with WriteAsString() to retrieve XML and Read(xml) to load from XML.

There are a number of ways you can instantiate the configuration object. If you are using custom configurations it's recommended that you implement two constructors: An empty default constructor and one that accepts a configuration provider. Here's an example:

public class ApplicationConfiguration : Westwind.Utilities.Configuration.AppConfiguration { /// <summary> /// Always implement an empty default constructor so new instances /// can be created by the various de-serialization config schemes. /// /// If you only use .config files and none of the other providers then /// you can implement the provider logic on the default constructor. /// </summary> public ApplicationConfiguration() { this.Initialize(); } /// <summary> /// By convention a second constructor that takes a Config Provider /// or null as an optional parameter should be implemented. This /// ctor should implement auto-load behavior and create a default /// provider for the object. Typically called like this: /// /// App.Configuration = new ApplicationConfiguration(null); /// </summary> public ApplicationConfiguration(IConfigurationProvider provider) { this.Initialize(); if (provider == null) { // create the customized default configuration so you only have to configure this in one place provider = new ConfigurationFileConfigurationProvider<ApplicationConfiguration>() { ConfigurationSection = "MyApplicationConfiguration", PropertiesToEncrypt = "MailServerPassword,ConnectionString", EncryptionKey = "secret" }; } // Make sure the Provider gets assigned Provider = provider; // now read the configuration data from the store Read(); } public string ApplicationTitle { get; set; } public string ApplicationSubTitle { get; set; } public string ConnectionString {get; set; } public DebugModes DebugMode {get; set; } public int MaxPageItems {get; set; } public string MailServer { get; set; } public string MailServerUsername { get; set; } public string MailServerPassword { get; set; } // centralized initialization so both constructors can use it public override void Initialize() { // set any default values here ApplicationTitle = "West Wind Web Toolkit Sample"; DebugMode = DebugModes.ApplicationErrorMessage; MaxPageItems = 20; } }

Here two constructors are implemented - a default constructor which is required in order for some of the serialization providers to work and also to set default values for the object. And a second constructor that implements the default storage configuration. The code for those two constructors is cookie cutter - it's always the same, the only thing changing will typically be the configuration provider and settings.

The second constructor basically accepts a configuration provider as input. If none is passed a default provider is created. The provider is assigned to the internal Provider property for future use - the provider essentially handles the retrieval of the data for the configuration class from the configuration store. Once the provider has been created the Provider.Read() method is used to explicitly retrieve the data from the configuration store.

The AppConfiguration default constructor - AppConfig(provider,sectionName) - provides a default implementation similar to the above code using the ConfigurationFileConfigurationProvider, so if you don't use encryption or any custom settings you can achieve the same result simply by implementing the constructor like this:

public ApplicationConfiguration(IConfigurationProvider provider) : base(provider, "AppplicationConfiguration) { }

Using the Default ConfigurationProvider

To use the default configuration provider you can use code like this (more info on why using a static property is a good idea):

public class App { public static ApplicationConfiguration {get; set; } static ApplicationConfiguration() { App.Configuration = new ApplicationConfiguration(null); // note the null parm!!! } }

later in some code anywhere in the app:

string password = App.Configuration.MailServerPassword; this.Page.Title += " - " + App.Configuration.ApplicationTitle;

Using a non-default ConfigurationProvider

Typically, you just create a constructor with a default ConfigurationProvider as shown in the previous example, and that's all you need.

However, in case you do need to use mutliple configuration stores or switch stores dynamically at runtime, you can also pass in a customized ConfigurationProvider explicitly.

// create a custom provider instance var provider = new SqlServerConfigurationProvider<ApplicationConfiguration>() { PropertiesToEncrypt = "MailServerPassword,ConnectionString", EncryptionKey = "secret", ConnectionString = "DevSampleConnectionString", Tablename = "Configuration", Key = 1 }; var configuration = new ApplicationConfiguration(provider); this.Page.Title += " - " + configuration.ApplicationTitle;

Here we simply create any of the stock providers and configure them explicitly and then pass them as a parameter.

Configuration Provider Examples

The following are a few examples that demonstrate configuraiton of the various configuraiton providers available.

Each provider inherits from ConfigurationProviderBase which includes the following properties:

  • PropertiesToEncrypt
  • EncryptionKey
  • ErrorMessage

The base also provides default implementations of both static and instance methods for Read(),Write(), WriteAsString() operations.

ConfigurationFileConfigurationProvider
This is the most common provider and the one we recommend using unless there's a specific need to use one of the others. The advantage of this provider is that its behavior is directly integrated with the .NET configuration classes and so provides all the benefits such as change tracking in Web apps to you without any additional code.

var provider = new ConfigurationFileConfigurationProvider<ApplicationConfiguration>() { PropertiesToEncrypt = "MailServerPassword,ConnectionString", EncryptionKey = "secret", ConfigurationSection = "ApplicationConfiguration", ConfigurationFile = Server.MapPath("~/ApplicationConfiguration.config") }; Configuration = new ApplicationConfiguration(provider);

The key properties specific to configuration files are ConfigurationSection and ConfigurationFile. If the file is not specified the stock .NET .config file - web.config or app.config - is used. If ConfigurationSection is not provided the default appSettings section is used.

XmlFileConfigurationProvider
This provider creates XML output files using standard .NET serialization. Rather than going through the .config files plain XML serialization/deserialization is used to write output to disk. Otherwise the behavior of this provider is nearly identical to the ConfigurationFileConfigurationProvider.

var provider = new XmlFileConfigurationProvider<ApplicationConfiguration>() { PropertiesToEncrypt = "MailserverPassword,ConnectionString", EncryptionKey = "_secretive_", XmlConfigurationFile = Server.MapPath("~/MyAppConfiguration.xml") }; Configuration = new ApplicationConfiguration(provider);

The key property for the XmlFileConfigurationProvider is XmlConfigurationFile which determines the location on disk.

SqlServerConfigurationProvider
The SQL Server configuration provider stores configuration information in a single database field as an XML value. The provider works with SQL Server and Sql Server Compact 4.0.

The following demonstrates using SQL Server:

var provider = new SqlServerConfigurationProvider<ApplicationConfiguration>() { ConnectionString = "Data Source=.;Database=DevSamples;integrated security=true;", Tablename = "ConfigData", Key=1, // multiple keys can be used for multiple configurations PropertiesToEncrypt = "ConnectionString,MailServerPassword", EncryptionKey = "SUPERSECRET" }; var config = new ApplicationConfiguration(provider);

and using SQL Server Compact 4.0. Note that you have to specify the ProviderName property explicitly.

var provider = new SqlServerConfigurationProvider<ApplicationConfiguration>() { ConnectionString = "Data Source=|DataDirectory|\\applicationDb.sdf;", ProviderName = "System.Data.SqlServerCe.4.0", // only needed for non-SQL server Tablename = "ConfigData", Key=1, // multiple keys can be used for multiple configurations PropertiesToEncrypt = "ConnectionString,MailServerPassword", EncryptionKey = "SUPERSECRET" }; var config = new ApplicationConfiguration(provider);

The SqlServerConfigurationProvider will automatically create the table for storing configuration data if the connection has rights to do so.

The provider should also work with most other kinds of SQL backends, but you'll have to create the table manually. The structure for the table is:

CREATE TABLE [ConfigData] ( [id] [int] , [ConfigData] [ntext]);

StringConfigurationProvider
This provider is a limited provider in that it doesn't actually persist to a store, but rather just serializes and deserializes from XML using the AppConfiguration's Read() and WriteAsString() methods.

To configure the string provider is the same as the other providers:

When using this provider with

Multiple Configuration Class Instances

While you can create multiple instances of the same configuration class it's not recommended especially if you change data on the configuration frequently. Ideally a single static instance should be used of any given configuration class implementation.

You can however have multiple separate configuration classes. For example, you can have a LoggingConfiguration, a BannerConfiguration and a MyApplicationConfiguration and each of these can be a separate static property on the global App object described earlier. This allows for clean separation of logic.

This is also quite useful for reusable components you might develop. For example, the LogManager implementation in the West Wind Web Toolkit uses a separate configuration object in this way as does the Banner Manager. These components then host the configuration object on some internal static object class (typically a .Current instance or Singleton).

For example with the LogManager:

string connString = LogManager.Current.Configuration.ConnectionString;

accesses an AppConfiguration subclass instance. For more detail you can check out the LogManager and LogManagerConfiguration classes.

Keeping Properties from getting Serialized

In rare cases you might want to implement properties on your configuration objects that are not persisted into the configuration store. You should avoid this as much as possible but if you do need to do this you can use Serialization Attributes to keep the items from getting serialized:

If you would like to add a property that should not persist or be treated as a configuration properly define it with XmlIgnore and NonSerializable attributes:

[XmlIgnore] [NonSerialized] public string CurrentChapter { get; set; }