In my last post I tried to give a brief overview of Configuration. In this post I am going to go over the concepts of the Configuration Library. The following components that make up the public interface for the runtime : the Configuration Manager, the Configuration Context, Configuration Providers, Storage Providers, Transformers and Xml Include Types. These concepts let us separate the concerns and responsibilities of configuration.
The Configuration Manager is just a facade over the entire Configuration Runtime. I would say 90% of the time you will use this functionality and not need anymore. It is a static instance that allows you to work with configuration data, while hiding the implementation of how the data is retrieved. This is similar to using ConfigurationSettings.GetConfig(). When working with the Configuration Manager, realize that it works with your defined application configuration file.
A configuration provider is a contract for anyone that wants to provide a service that relies on configuration data. Now in retrospect, this is really a configuration consumer. In other words, the provider consumes configuration data and acts upon that data. Given the Provider Model coming out in Whidbey, we opted for provider. Sorry Brian. The configuration provider contract is the interface IConfigurationProvider. When creating a provider, use the ConfigurationProvider base class because it has implementation that will help you not have to write as much code .
The heart of the Configuration Provider interface is the Initialize method. This method excepts a ConfigurationView instance. This object is just a wrapper on accessing configuration data. Think of this as your data access API. You may have heard that I rewrote Configuration a few times , and this was one of the last changes that we made for two reasons. First and foremost we wanted each block to be able to respond to storage changes. By hiding the data retrieval behind another level of indirection, you will be able to access the latest greatest data without worrying about responding to any events. The second reason we did this is because Brian wanted this . Actually it has to do with active versus passive data in our code. Before, configuration data access was littered throughout the code which made it very active throughout. This smelled really bad. So we went to a more passive mode of accessing the data, and we expect this to get better in V2.
Now for those interested, here are the gory details of how we get your application to respond to configuration changes. When you create a Storage Provider, you use an IStorageProviderReader/Writer that implements an IConfigurationChangeWatcherFactory (more on this in a minute). This allows you to create an IConfigurationChangeWatcher that will tell the Configuration Runtime when an external change happens. Since the Configuration Runtime caches live references to your data, if you store the data in your component, then you want get changes to that reference because we replace the reference in the cache with the new data. You can see where you would not get what you expect .
The storage providers are responsible for reading and writing data to and from a physical storage. The interfaces for dealing with storage providers are the following : IStorageProviderReader, IStorageProviderWriter, IConfigurationChangeWatcherFactory, and IConfigurationChangeWatcher.
An IStorageProviderReader / Writer does exactly what they advertise, read and write configuration data. We separated out the reader and writer so you could have read only data. There is no magic here, just your implementation. We ship with a storage provider that reads and writes data to an external Xml file.
An IStorageProviderReader implements the IConfigurationChangeWatcherFactory. This allows us to query your provider for an IConfigurationChangeWatcher that will tell us when your external configuration data changes (of course you don’t have to have this). We also have one for our own meta-data. We ship with a watcher that watches files. So if you want to create a storage provider for Sql Server you would have to write some mechanism to notify you that data has changed (like a service) or wait for Yukon .
A transformer can (I say can because you don’t have to have one) sit between your storage provider and your application code. The transformer takes your data from storage and “transforms” it into something consumable for your application and vice-versa for your storage provider. Now since we don’t place any limitations on a Transformer, I could imagine someone creating a composite transformer that would then invoke many transformers for your configuration data. Since we return data in the form of and XmlNode from our storage provider we have a Transformer that uses the XmlSerializer to transformer this into individual object graphs defined for each block.
Xml Include Types
Since we use the XmlSerialzier for our Transformer, we needed a way to tell the serializer what types it could expect to see. Now internally we can use the XmlIncludeAttribute on our base class, but we didn’t want you recompiling everything any time you wanted to extend the library (I will keep my opinion to myself about why you put an attribute on a base class instead of the derived). So when you define the Xml Include Types, we feed this to the serializer so you your types are recognized.
The context is not all that interesting , it is just an instance based version of the Configuration Manager. One of the major differences is that the Configuration Manager uses the application configuration file, while the context can either use a separate configuration file or a ConfigurationDictionary that defines your configuration from code. We needed this instance so we could pass around the right configuration to each and every block that would be used. For example, you don’t need configuration to be defined in storage, you could just new it up and put it into a ConfigurationDictionary, or it could come from a separate file (as I stated previously). If we were to read the configuration from the application configuration file every time, well, you would not get what you expect. So when you invoke a block and that configuration is read, all subsequent calls down the chain will use that ConfigurationContext.
Now that is a pretty abstract description so lets use a real example. When using the Exception Handling block, you can use the Logging Application Block to log data to a sink, which in turn uses the Data Access Block to log your exception to a database. Now you can see if we don’t pass the right context all the way down the chain, the Data Access Block would not have a clue what database it should use.
Since the Context was really not covered well in the documentation (sorry about that), I will be posting on how to use it in the future.
Next post… how to write a StorageProvider . Same code time, same code channel.