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, msdn.microsoft.com/en-us/library/system.configuration.configurationmanager.openmappedexeconfiguration.aspx
Public Shared Function Read(ByVal path As String) As DataEnvironmentConfigurationElement
Dim section As DataEnvironmentConfigurationSection = DirectCast(DataEnvironment.GetNonDefaultConfigurationFile(path).GetSection("dataEnvironmentConfiguration"), DataEnvironmentConfigurationSection)
Return DataEnvironment.GetCurrentEnvironment(section)
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,
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Key1" value="value1"/>
</appSettings>
<configSections>
<section name="dataEnvironmentConfiguration" type="CustomConfigurationSample.DataEnvironmentConfigurationSection, CustomConfigurationSample"/>
</configSections>
<dataEnvironmentConfiguration currentDataEnvironment="UT">
<dataEnvironments>
<add name="name1" userName="user1" password="pasword1" collection="new value" />
<add name="name2" userName="user2" password="password2s" collection="new value21" />
</dataEnvironments>
</dataEnvironmentConfiguration>
</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,
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="dataEnvironmentConfiguration" type="CustomConfigurationSample.DataEnvironmentConfigurationSection, CustomConfigurationSample"/>
</configSections>
<appSettings>
<add key="Key1" value="value1"/>
</appSettings>
<dataEnvironmentConfiguration currentDataEnvironment="UT">
<dataEnvironments>
<add name="name1" userName="user1" password="pasword1" collection="new value" />
<add name="name2" userName="user2" password="password2s" collection="new value21" />
</dataEnvironments>
</dataEnvironmentConfiguration>
</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, 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.
Public Shared Function Read(ByVal path As String) As DataAccessDefinition
Dim c = GetNonDefaultConfigurationFile(path)
Return DirectCast(c.GetSection("DataAccessDefinition/DataAccess1"), DataAccessDefinition)
End Function
We get the non-custom config file path by using OpenMappedExeConfiguration of the config file,
Private Shared Function GetNonDefaultConfigurationFile(ByVal path As String) As Configuration
Dim ecfm = New ExeConfigurationFileMap()
ecfm.ExeConfigFilename = path
Return ConfigurationManager.OpenMappedExeConfiguration(ecfm, ConfigurationUserLevel.None)
End Function
Handler is implemented as shown below,
Public Class DataAccessConfigHandler
Implements IConfigurationSectionHandler
Public Function Create(ByVal parent As Object, ByVal configContext As Object, ByVal section As System.Xml.XmlNode) As Object Implements System.Configuration.IConfigurationSectionHandler.Create
Dim dataAccess = New DataAccessDefinition
dataAccess.ConnectionName = section.SelectSingleNode("connectionName").InnerText
dataAccess.CommandText = section.SelectSingleNode("commandText").InnerText
dataAccess.CommandType = section.SelectSingleNode("commandType").InnerText
Return dataAccess
End Function
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: 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;
Public Class DataAccessConfigHandler
Inherits ConfigurationSection
Public dataAccess As DataAccessDefinition
Protected Overrides Sub DeserializeSection(ByVal reader As System.Xml.XmlReader)
dataAccess = New DataAccessDefinition
While reader.Read()
If reader.NodeType <> XmlNodeType.Element Then
Continue While
End If
Select Case reader.Name
Case "connectionName"
dataAccess.ConnectionName = reader.ReadElementContentAsString()
Case "commandText"
dataAccess.CommandText = reader.ReadElementContentAsString()
Case "commandType"
dataAccess.CommandType = reader.ReadElementContentAsString()
End Select
End While
End Sub
End Class
Usage example:
Dim h = DirectCast(ConfigurationManager.GetSection("DataAccessDefinition/DataAccess1"), DataAccessConfigHandler)
Return h.dataAccess
<sectionGroup name="DataAccessDefinition">
<section name="DataAccess1" type="CustomConfigurationSample.DataAccessConfigHandler,CustomConfigurationSample"/>
</sectionGroup>
Please refer to the MSDN article for more details, msdn.microsoft.com/en-us/library/2tw134k3.aspx
Now this works absolutely fine. Hope this helps while working with configuration files.
-Ganesh Shankaran