In the second part of this blog post we are going to look at a simple methodology for applications to take advantage of a centralized and transparent configuration store. I had thought this was going to be the hardest part of this project yet it turned out to be the most straightforward. This was done by implementing our own ProtectedConfigurationProvider class which gave us the ability to inject configuration data at runtime from any source available to me. I settled on this approach based on a lot of searching but which has been nicely condensed in several blog posts you can read here and here.
In the second part of this blog post we are going to look at a simple methodology for applications to take advantage of a centralized and transparent configuration store. I had thought this was going to be the hardest part of this project yet it turned out to be the most straightforward. This was done by implementing our own ProtectedConfigurationProvider class which gave us the ability to inject configuration data at runtime from any source available to me. I settled on this approach based on a lot of searching but which has been nicely condensed in several blog posts you can read here and here.
The approach leverages the same mechanism that is natively provided by the .NET framework to encrypt/decrypt connection strings in web.config files in ASP.NET applications. While many developers are already familiar with this mechanism, here is a link for those of you who are not. This mechanism works by decrypting the connection string in memory when the web.config file is first read on application start. This is then stored in the System.Configuration.ConfigurationManager and is able to be accessed seamlessly by a developer. We are going to take the same approach but instead of decrypting the file using RSA or DPAPI (the two built in .NET technologies) we are going to make a call out to our RESTful external configuration management system to retrieve the configuration data. This approach provides several nice advantages but the most important from my point of view is that it is completely transparent during development. You still reference configuration items as using the normal methodology (System.Configuration.ConfigurationManager.AppSettings[“XXX”]). In addition this system can be turned on or off by making a few changes to a configuration file instead of having to make and test code modifications,
In order to begin you will need to create and configure two items. The first is a standard application configuration file (web.config or app.config) and the second is class that implements the System.Configuration.ProtectedConfigurationProvider base class. I have provided several examples below and explained the most important aspects.
[code language="xml"]<configuration><configProtectedData defaultProvider="MyProtectedConfigurationProvider "><providers><add name="MyProtectedConfigurationProvider" type="CustomConfig.MyProtectedConfigurationProvider, CustomConfig" Environment="Dev" AppName="TestApp"/></providers></configProtectedData><appSettings configProtectionProvider="MyProtectedConfigurationProvider"><EncryptedData section="appsettings"><!--In this section you can add keys to override the central configuration settings--></EncryptedData></appSettings></configuration>[/code]
There are two main sections of the application configuration file to be concerned with. First, you will need to setup the configProtectedData element. This element specifies a name and a class implementation for the provider. This needs to be setup to point to your custom provider class. The second element is the section you would like to dynamically load, in this case the app settings section. This section needs to be configured to specify the configProtectionProvider you specified in the first section and then needs to have the <EncryptedData></EncryptedData> tags added inside its tags. These tags are required. You can also insert key values in this area and they will be based to the configProtectionProvider. We intend to take advantage of this ability to allow for local overrides of central configuration data. (e.g. if the key exists in the config file then don’t use the one from the central source.)
A few items of note in the example above, in the configProtectedData element you will notice I have added specific attribute tags for Environment and AppName. These will get passed to the Initialize method of the class as a NameValueCollection. While I have chosen to add these two you can add as many or as few as you find appropriate. The second point to take note of is that you can not only pass these additional attributes in the configProtectedDataelement but from within the configuration section itself. I found this to be very useful to identify the section I as trying to retrieve such as appsettings and connectionstrings.
[code language="csharp"]public class MyProtectedConfigurationProvider : ProtectedConfigurationProvider{public override void Initialize(string name, NameValueCollection config){ Environment = config["Environment"]; AppName = config["AppName"]; base.Initialize(name, config);}publicMyProtectedConfigurationProvider(string environment, string appName, string sectionName = ""){}publicoverride System.Xml.XmlNode Decrypt(System.Xml.XmlNode encryptedNode){ var doc = new System.Xml.XmlDocument(); var node = doc.CreateNode(System.Xml.XmlNodeType.Element, SectionName, ""); //put code here to connect to the external configuration source and pull your configuration data var section = new StringBuilder(); section.Append("<").Append(SectionName).Append(">"); // Put code here to process the external configuration data and put it into a valid XML Node // foreach (var item in configurationItems) { // Perform a check here of all child nodes of the encryptedNode which is the data inside the<EncryptedData> // tag of the section in the web/ app.config var children = encryptedNode.ChildNodes.Cast<XmlNode>(); var childNode = children.Where(n => n.Name == item.Key).SingleOrDefault(); if (childNode is null) { section.Append("<add key=\"").Append(item.Name).Append("\" value=\"").Append(item.Value).Append("\" />"); } else { section.Append("<add key=\"").Append(item.Name).Append("\" value=\"").Append(childNode.Value).Append("\" />"); } } section.Append("</").Append(SectionName).Append(">"); doc.LoadXml(section.ToString()); return doc.DocumentElement;}}public override System.Xml.XmlNode Encrypt(System.Xml.XmlNode node){ throw new NotImplementedException();}}[/code]
The key method that you will need to do all your work in is the Decrypt method. This is the method that is called by the .NET Framework to get the configuration data section. It is in this method that you will call your external data source and return the XML Node for the configuration section. In our case we were making a call out to a RESTful web service to retrieve our data but it just as easily could be a local or shared file, database or any other data persistence mechanism you can imagine. You will notice that the Decrypt method takes an encrypted XML Node as a parameter. This is the information stored inside the EncryptedData tags in the config file (this will be covered later). The real trick here is that this data does not necessarily need to be encrypted. This allows you to do some interesting things such as provide local overrides for settings or pass information the the Decrypt method.
Looking ahead it seems that in ASP.NET vNext Microsoft has finally recognized some of the difficulties with application configuration and has provided a much more flexible model than the web/app.config files we have all become familiar with. Feel free to read more about it here or here. It is apparent that vNext is a radical overhaul of the .NET Framework and not just an evolutionary set of changes that we have seen to date. However it is still in the Alpha phase and has no release date. With these uncertainties and the realistic fact that it will be years (if ever) before any or all our applications are migrated I think the decision to continue with the current approach seems prudent.
Tell us what you need and one of our experts will get back to you.