.NET and me Coding dreams since 1998!

12Nov/070

How to have configuration file for DLL

In cases when I need to make some component independently pluggable, I am defining separate configuration file for that component. That is particular useful if you are not the end user of the component, so you can not rely that appropriate configuration sections would be add to the app.config or web.config file.


Today's use case would be covering the need for defining a collection of user data stored in a component configuration file and that data should be read during the read time and returned as a collection of concatenated strings. Every user entry would have unique Id and first name but not necessarily the last name entry,


Project structure


image


Configuration file definition


Would look like something like this:



   1: <?xml version="1.0" encoding="utf-8"  standalone="yes" ?>
   2: <configuration>
   3:   <configSections>
   4:     <sectionGroup name="SomeSectionGroupName" >
   5:       <section name="AuthenticationSection" type="Pluggin.AuthenticationConfigurationSection, Pluggin" />
   6:     </sectionGroup>
   7:   </configSections>
   8:   <SomeSectionGroupName>
   9:     <AuthenticationSection>
  10:       <UserCollection>
  11:         <UserConfigurationElement id="1" name="Nikola" surname="Malovic" />
  12:         <UserConfigurationElement id="2" name="Snezana" surname="Malovic" />
  13:         <UserConfigurationElement id="3" name="Nina" />
  14:       </UserCollection>
  15:     </AuthenticationSection>
  16:   </SomeSectionGroupName>
  17: </configuration>

Configuration file would contain:



  • (L4) Configuration section group called "SomeSectionGroupName" and in that section group
  • (L5) Configuration section named: "AuthenticationSection"
  • (L10) AuthenticationSection would contain configuration ellement collection "UserCollection"
  • (L11, L12,L13) which would group collection of "UserConfigurationElement"

ConfigurationSection definition


As we can see in line 5 of configuration file, AuthenticationSection is defined by type AuthenticationConfigurationSection located in Pluggin assembly.


The code defined there is very simple and it is based on specialization of the base ConfigurationSection with defining the mappings between the config file xml entities and appropriate CLR types


Code is very simple and it looks like this:



   1: using System.Configuration;
   2:  
   3: namespace Pluggin
   4: {
   5:     public class AuthenticationConfigurationSection : ConfigurationSection
   6:     {
   7:  
   8:         [ConfigurationProperty("UserCollection", IsDefaultCollection = true)]
   9:         public UserConfigurationElementCollection Users
  10:         {
  11:             get { return (UserConfigurationElementCollection)base["UserCollection"]; }
  12:         }
  13:  
  14:     }
  15: }

In line 8, we are using ConfigurationProperty attribute to define that "this specific config file section would contain a "UserCollection" XML collection element"


In line 11, we define mapping between that element and UserConfigurationElementCollection type and that is all this configuration section


ConfigurationElementCollection definition


The configuration element collection implementation is based on inheriting the ConfigurationElementCollection and implementing base abstract methods CreateNewElement() and GetElementKey(ConfigurationElement element)


Code looks something like this:



   1: using System.Configuration;
   2:  
   3: namespace Pluggin
   4: {
   5:     public class UserConfigurationElementCollection : ConfigurationElementCollection
   6:     {
   7:  
   8:         protected override ConfigurationElement CreateNewElement()
   9:         {
  10:             return new UserConfigurationElement();
  11:         }
  12:  
  13:         protected override object GetElementKey(ConfigurationElement element)
  14:         {
  15:             return ((UserConfigurationElement)element).Id;
  16:         }
  17:  
  18:         public override ConfigurationElementCollectionType CollectionType
  19:         {
  20:             get { return ConfigurationElementCollectionType.BasicMap; }
  21:         }
  22:  
  23:         protected override string ElementName
  24:         {
  25:             get { return "UserConfigurationElement"; }
  26:         }
  27:     }
  28: }

In line 8, we override the abstract creation member and set it up to return new instance of UserConfigurationElement (which is our specialized configuration element - I will cover it in more detail in a few seconds)


In line 13, we override the get element key method to cast the given element to UserConfigurationElement and return its Id property (which is key attribute)


To enable mapping the configuration element XML definition  and the CLR configuration element definition we need to add additional overrides in L18 and L23 which enable .NET to find during run time appropriate configuration element type which is to be mapped to certain config element.
Without those two additional overrides, my little sample would throw  an ConfigurationErrorsException:



A first chance exception of type 'System.Configuration.ConfigurationErrorsException' occurred in System.Configuration.dll


Additional information: Unrecognized element 'UserConfigurationElement'.


ConfigurationElement definition


In L11,L12 and L13 we can see that configuration of this element has three properties Id, Name, surname (except in L13 where surname is missing)


Mapping of this configuration definition to .NET type is been done by inheriting the ConfigurationElement class and defining mapping between the configuration element properties and appropriate class properties with optional defining of additional ttributes


That code could look like this:



   1: using System.Configuration;
   2:  
   3: namespace Pluggin
   4: {
   5:     public class UserConfigurationElement : ConfigurationElement
   6:     {
   7:         [ConfigurationProperty("id", IsKey = true, IsRequired = true)]
   8:         public string Id
   9:         {
  10:             get { return (string)this["id"]; }
  11:             set { this["id"] = value; }
  12:         }
  13:  
  14:         [ConfigurationProperty("name", IsRequired = true)]
  15:         public string FirstName
  16:         {
  17:             get { return (string)this["name"]; }
  18:             set { this["name"] = value; }
  19:         }
  20:  
  21:         [ConfigurationProperty("surname", IsRequired = false)]
  22:         public string LastName
  23:         {
  24:             get { return (string)this["surname"]; }
  25:             set { this["surname"] = value; }
  26:         }
  27:     }
  28: }

In line 5, we can see that our UserConfigurationElement inherits the ConfigurationElement


In line 7, we are decorating property Id with configuration property attribute who defines that:



  • the Id property is to be mapped to the "id" (case sensitive) XML element property
  • that the id element property contains unique identifier data of the configuration element
  • that the id element value is required data so it can not be omitted

In line 14, we have the same configuration property attribute decoration but this time we can an example of totally different naming of the .NET property, so we don't have to follow chosen XML names if we don't want because attribute constructor element name value defines  the mapping


In line 21, we see the same configuration property attribute decoration but with IsRequired = false value definition which in this case mean that some of the elements could be without surname value


Run-time configuration file content read out


Now when we have configuration file defined and appropriate types defining mapping XML->NET all we have to do is to use them during the run time.


The code loading the authentication section from a given config file looks like this:



   1: private static AuthenticationConfigurationSection GetAuthenticationConfigurationSection()
   2: {
   3:     ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
   4:     Assembly asm = Assembly.GetCallingAssembly();
   5:     Uri uri = new Uri(Path.GetDirectoryName(asm.CodeBase));
   6:     fileMap.ExeConfigFilename = Path.Combine(uri.LocalPath, "pluggin.dll.config");
   7:     Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
   8:  
   9:     AuthenticationConfigurationSection authConfigurationSection =
  10:         config.GetSectionGroup("SomeSectionGroupName").Sections["AuthenticationSection"] as
  11:         AuthenticationConfigurationSection;
  12:     if (authConfigurationSection == null)
  13:         throw new NotSupportedException();
  14:  
  15:     return authConfigurationSection;
  16: }

In determine local path of the configuration file we used:



  • in line 4, we got a pointer to the active assembly
  • In line 5, we used GetDirectoryName static method of the Path type for  active assembly code base propety value.
    Then we passed retrieved directory name to the Uri object constructor
  • In line 6, we set the ExeConfigFilename property of the fileMap instance (created in line 3), to a string concatenated from uri.LocalPath and hard coded config file name value (This could be enhanced to be dynamically determined to be like asembly.dll.config)
  • That file map instance is then passed as a parameter to OpenMappedExeConfiguration method of the COnfigurationManager type

The code is using the local path of the Uri object constructed based upon active assembly directory name to set the ExeConfigFile name property of the ExeConfigurationFileMap type instance. That  instance is then passed as parameter to ConfigurationManager OpenMapperdExeConfiguration method


In Line 9 and 10, we  retrieve from opened configuration file  section group with SomeSectionGroupName and AuthenticationSection section which we cast then to AuthenticationConfigurationSection


L12, just checks if something went wrong


Now when we have strongly typed reference to configuration section, it's read out is fairly trivial task:



   1: public static IEnumerable<string> GetUsers()
   2: {
   3:     AuthenticationConfigurationSection builderSection = GetAuthenticationConfigurationSection();
   4:     foreach (UserConfigurationElement userConfigurationElement in builderSection.Users)
   5:     {
   6:         yield return
   7:             String.Format("id:{0}, name:{1}, surname:{2}", userConfigurationElement.Id,
   8:                           userConfigurationElement.FirstName, userConfigurationElement.LastName);
   9:     }
  10: }

In line 3, we call previously explained method which returns an instance of AuthenticationConfigurationSection pointing to appropriate configuration file section


In line 3 we iterate through all Users (property defined in AuthenticationConfigurationSection which maps "UserCollection" XML element to UserConfigurationElement collection.


In line 6-8, for each UserConfigurationElement we access its properties Id, FirstName and LastName (which are mapped to appropriate xml configuration element properties), and return the string containing concatenated property values.


Test drive


To test the functionality, I've created simple console application with next code:



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using Pluggin;
   5:  
   6: namespace TestDrive
   7: {
   8:     class Program
   9:     {
  10:         static void Main(string[] args)
  11:         {
  12:             IEnumerable users =Builder.GetUsers();
  13:             foreach (string user in users)
  14:             {
  15:                 Console.WriteLine(user);
  16:             }
  17:             Console.ReadKey();
  18:         }
  19:     }
  20: }

which once started results with:


image


Conclusion


It is possible and in fact it is very simple to define an "application  independent" component configuration file which could allow encapsulating the required default component setting up from the end component user while still allowing configuration level customization


You can download source code from here.


 




 
























Share this post :

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.