Udostępnij za pośrednictwem


Why do we get System.InvalidCastException when we read through custom Configuration file?

A developer’s life becomes a lot easier by using configuration files. Configuration files, being XML files, can be changed as needed and developers can use configuration files to change settings without recompiling applications. Administrators can use configuration files to set policies that affect how applications run on their computers.
Managed code can use the classes in the System.Configuration namespace to read settings from the configuration files. When we read through the configuration files, there are certain scenarios which will lead to an System.InvalidcastException
If you have distinct groupings of configurations to be included in the application configuration file, consider creating a custom configuration section. This will allow you to have more organizational structuring around the storage of various settings. Include a custom section by including the configSections element in the configuration file and a sub-element called section that defines the custom section and the handler for reading it.
When we try to make use of a custom configuration file, under certain circumstance, application throws a System.InvalidcastException. Below are two such scenarios and the way to get over them. 
As we all know, OpenMappedExeConfiguration gets the custom config file by specifying ExeConfigurationFileMap( with the path of the assembly set) and configuration user level. Here is the MSDN article for reference, https://msdn.microsoft.com/en-us/library/system.configuration.configurationmanager.openmappedexeconfiguration.aspx

    1: Public Shared Function Read(ByVal path As String) As DataEnvironmentConfigurationElement
    2:     Dim section As DataEnvironmentConfigurationSection = DirectCast(DataEnvironment.GetNonDefaultConfigurationFile(path).GetSection("dataEnvironmentConfiguration"), DataEnvironmentConfigurationSection)
    3:     Return DataEnvironment.GetCurrentEnvironment(section)
    4: End Function

Now, let’s look in to two such scenarios where accessing custom configuration files might lead us to System.InvalidcastException

Scenario 1: We have a configuration file that tries to access the Section information in the code using GetSection but results in the type cast error while it was accessed.

When the type information was checked, everything looked perfect. And the config file being XML format we might think that we can follow any order of elements that are being used in the config file, The faulting config file looked as shown below, 

    1: <?xml version="1.0" encoding="utf-8" ?>
    2: <configuration>
    3: <appSettings>
    4:     <add key="Key1" value="value1"/>
    5:   </appSettings>
    6: <configSections>
    7:     <section name="dataEnvironmentConfiguration" type="CustomConfigurationSample.DataEnvironmentConfigurationSection, CustomConfigurationSample"/>
    8:   </configSections>
    9:   
   10: <dataEnvironmentConfiguration currentDataEnvironment="UT">
   11:     <dataEnvironments>
   12:       <add name="name1" userName="user1" password="pasword1" collection="new value" />
   13:       <add name="name2" userName="user2" password="password2s" collection="new value21" />
   14:       </dataEnvironments>
   15:   </dataEnvironmentConfiguration>
   16: </configuration>

When we make use of this config file, we will get a System.InvalidcastException.

Resolution

There is one catch in the above snip which is the root cause of the issue. While DOM (Data Object model) tries to parse the config section, it looks for the type information mentioned in the ConfigSections element, and there is a specific requirement that, this ConfigSections appears just below the Configuration element. CLR XML DOM parser assumes that the configSections element is the first element in the configuration file.

Here is how it should appear,

    1: <?xml version="1.0" encoding="utf-8" ?>
    2: <configuration>
    3: <configSections>
    4:     <section name="dataEnvironmentConfiguration" type="CustomConfigurationSample.DataEnvironmentConfigurationSection, CustomConfigurationSample"/>
    5:   </configSections>
    6:   <appSettings>
    7:     <add key="Key1" value="value1"/>
    8:   </appSettings>
    9: <dataEnvironmentConfiguration currentDataEnvironment="UT">
   10:     <dataEnvironments>
   11:       <add name="name1" userName="user1" password="pasword1" collection="new value" />
   12:       <add name="name2" userName="user2" password="password2s" collection="new value21" />
   13:       </dataEnvironments>
   14:   </dataEnvironmentConfiguration>
   15: </configuration>

So, when we the make the necessary changes in the custom config file, the issue gets fixed and we are good to use the config file. You could get more information about this in the MSDN article, https://msdn.microsoft.com/en-us/library/ms228256.aspx

Well, that was simple. But when we miss the basic element order in the config file it becomes a little difficult to locate the fault.

Let’s look at the second scenario which also throws InvalidtypeCastException, but under different circumstances,

Scenario 2: Here is another scenario where the application throws InvalidtypeCastException and this time it’s while accessing <Sectiongroups>. Let’s look in to the sample that leads to the exception,

We try to get the section detail of the custom config file, Read( ) function (with the path where we have the custom config as the parameter) does the work for us.

    1: Public Shared Function Read(ByVal path As String) As DataAccessDefinition
    2: Dim c = GetNonDefaultConfigurationFile(path)
    3: Return DirectCast(c.GetSection("DataAccessDefinition/DataAccess1"), DataAccessDefinition) 
    4: End Function

We get the non-custom config file path by using OpenMappedExeConfiguration of the config file,

    1: Private Shared Function GetNonDefaultConfigurationFile(ByVal path As String) As Configuration
    2: Dim ecfm = New ExeConfigurationFileMap()
    3: ecfm.ExeConfigFilename = path
    4: Return ConfigurationManager.OpenMappedExeConfiguration(ecfm, ConfigurationUserLevel.None)
    5: End Function

Handler is implemented as shown below, 

    1: Public Class DataAccessConfigHandler
    2:     Implements IConfigurationSectionHandler
    3:  
    4:     Public Function Create(ByVal parent As Object, ByVal configContext As Object, ByVal section As System.Xml.XmlNode) As Object Implements System.Configuration.IConfigurationSectionHandler.Create
    5:         Dim dataAccess = New DataAccessDefinition
    6:         dataAccess.ConnectionName = section.SelectSingleNode("connectionName").InnerText
    7:         dataAccess.CommandText = section.SelectSingleNode("commandText").InnerText
    8:         dataAccess.CommandType = section.SelectSingleNode("commandType").InnerText
    9:         Return dataAccess
   10:     End Function
   11: End Class

While this handler works fine for a default config file, it results in a System.InvalidCastException when we make use of the custom config file.

Resolution:

As IconfigurationSectionHandler is deprecated [ref: https://msdn.microsoft.com/en-us/library/system.configuration.iconfigurationsectionhandler.aspx], the best way to access the Section group is to follow the following method described below,

The configuration section defines which type is used to de-serialize the XML file, and if you try to cast to an incompatible type you will (as expected) get a casting error.

I was able to get it working by using the suggested method of deriving from ConfigurationSection as IconfigurationSectionHandler is deprecated.

Below is the implementation detail;

    1: Public Class DataAccessConfigHandler
    2:     Inherits ConfigurationSection
    3:  
    4:     Public dataAccess As DataAccessDefinition
    5:  
    6:     Protected Overrides Sub DeserializeSection(ByVal reader As System.Xml.XmlReader)
    7: dataAccess = New DataAccessDefinition
    8:  
    9: While reader.Read()
   10: If reader.NodeType <> XmlNodeType.Element Then
   11:    Continue While
   12: End If
   13: Select Case reader.Name
   14: Case "connectionName"
   15: dataAccess.ConnectionName = reader.ReadElementContentAsString()
   16: Case "commandText"
   17: dataAccess.CommandText = reader.ReadElementContentAsString()
   18: Case "commandType"
   19: dataAccess.CommandType = reader.ReadElementContentAsString()
   20: End Select
   21: End While
   22: End Sub
   23: End Class

Usage example:

    1: Dim h = DirectCast(ConfigurationManager.GetSection("DataAccessDefinition/DataAccess1"), DataAccessConfigHandler)
    2:         Return h.dataAccess
    1: <sectionGroup name="DataAccessDefinition">
    2: <section name="DataAccess1" type="CustomConfigurationSample.DataAccessConfigHandler,CustomConfigurationSample"/>
    3: </sectionGroup>

Please refer to the MSDN article for more details,https://msdn.microsoft.com/en-us/library/2tw134k3.aspx 

 Now this works absolutely fine. Hope this helps while working with configuration files.

 

Ganesh Shankaran

Software Development Engineer