Índice de la sección dedicada a .NET (en el Guille) Cómo... en .NET

Manejar los valores almacenados en el fichero .config

Segunda parte:
Cómo añadir nuevas secciones al fichero .config y poder leerlas, modificarlas y guardarlas en tiempo de ejecución

Código para Visual Basic.NET (VB.NET)

Código para C Sharp (C#)

 

Publicado el 22/Feb/2005
Actualizado el 17/Ago/2006
Autor: Guillermo 'guille' Som


En esta segunda parte del manejo del contenido del fichero de configuración, vamos a ver cómo crear un fichero "al vuelo", para el caso de que no exista.
También veremos cómo añadir una nueva sección al fichero de configuración para poder almacenar valores de una forma un poco más organizada.

 

Nota del 21/Feb/2006:
Aquí tienes una clase (versión mejorada de este código) para usar con VB2005

Nota del 17/Ago/2006:
Aquí tienes la versión mejorada para Visual Studio .NET (2002/2003)


Introducción

Tal como vimos en la primera parte, podemos leer y guardar valores en la sección appSettings del fichero de configuración de nuestras aplicaciones.

El contenido de ese fichero (en particular de la sección appSettings) lo podíamos usar de dos formas:
1- Dejando que el propio compilador se encargue de obtener los valores asignados.
2- Leer esos valores por nuestra cuenta.

El problema aquí es que si mezclamos las cosas, posiblemente consigamos una combinación explosiva... sigue leyendo y te enterarás a que me refiero.

Usar otras secciones además de appSettings

Tal como vimos en el artículo anterior (arriba tienes el link) podemos leer los valores de la sección appSettings del fichero de configuración de dos formas distintas, (no, no es una repetición del párrafo anterior, aunque a mi también me lo pareció mientras lo escribía), una usando la clase AppSettingsReader (que es la que usa el Visual Studio para asignar los valores) y otra usando el método cfgGetValue definido en el código.
El problema, es que AppSettingsReader solo puede leer valores de la sección "predefinida", es decir de appSettings. Bueno, problema, problema, lo que se dice problema, realmente no lo es... pero si pretendes crear nuevas secciones, entonces si que lo sería, ya que esa clase solamente lee las claves de appSettings.
Pero si usamos el método cfgGetValue, podrás leer de cualquier sección, para ello, lo único que tienes que hacer es indicarle la sección de la que quieres leer.
Supongamos que tenemos otra sección llamada General, tal como te muestro aquí:

<?xml version="1.0" encoding="utf-8" ?> 
<configuration>
   <appSettings>
	<!-- La configuración de la aplicación de usuario y de la propiedad configurada van aquí.-->
	<!-- Ejemplo: <add key="settingName" value="settingValue"/> -->
	<add key="Form1.Text" value="Form1" />
	<add key="CheckBox1.Checked" value="False" />
	<add key="RadioButton1.Checked" value="True" />
	<add key="RadioButton2.Checked" value="False" />
	<add key="TextBox1.Text" value="TextBox1" />
 	<!-- Valores personalizados -->
	<add key="Form1.Left" value="300" />
	<add key="Form1.Top" value="20" />
   </appSettings>
   <General>
	<!-- Los valores posibles de usarEstilosXP son 0 ó 1 -->
	<add key="Usar EstilosXP" value="1" />
	<!-- Los valores posibles son 0 ó 1 -->
	<add key="Cerrar aplicación" value="0" />
   </General>
</configuration>

Para poder acceder a esa sección tendremos que usar el método cfgGetValue de la siguiente forma:
If cfgGetValue("configuration/General", "Usar EstilosXP", "1") = "1" Then

Es decir, en sección indicamos configuration/General y así podríamos acceder a esos valores.
El problema es que... bueno, no siempre funcionará.
Al menos si sigues usando la clase AppSettingsReader (y aunque tú no la definas expresamente, el Visual Studio crea una dentro del código oculto generado por el diseñador de Windows.Forms), ya que se producirá una excepción indicándonos que...

Figura 1. Excepción producida al leer el fichero .config
Figura 1. Excepción producida al leer el fichero .config

...no se reconoce la sección General.
Esto (supuestamente) es así, porque el fichero .config solamente debe usarse para configurar la aplicación y por tanto solamente para usar la sección appSettings.
Pero lo cierto es que podemos añadir nuevas secciones al fichero de configuración, aunque para ello tenemos varias opciones:

Para indicarle al .NET que existe otra sección, tendremos que añadir estas líneas al fichero de configuración:

   <configSections>
<section name="General"
            type="System.Configuration.DictionarySectionHandler" />
   </configSections>
 

Una vez hecho esto, ya no se producirá ninguna excepción al leer los valores de configuración mediante la clase usada por el propio compilador.
Por supuesto también podremos leer los valores de esa sección mediante el método cfgGetValue.

 

Una función para leer los valores de toda una sección de un golpe

Como "agregado", podemos leer todo el contenido de una sección mediante una llamada al método GetConfig de un objeto del tipo ConfigurationSettings, el valor devuelto por el método GetConfig será del tipo IDictionary, veamos cómo podemos "fabricar" un método cfgGetValue usando ese método de ConfigurationSettings:

Private Function cfgGetValue(ByVal seccion As String, _
    			ByVal clave As String, _
			ByVal predeterminado As String) As String
    Dim valores As IDictionary
    '
    valores = CType(ConfigurationSettings.GetConfig(seccion), IDictionary)
    If valores.Contains(clave) Then
        Return valores(clave).ToString
    Else
        Return predeterminado
    End If
End Function

 

La utilidad de esta clase es que nos puede servir para leer al completo una sección y después poder usar esos datos para buscar los valores que nos interese, ya que usada así como te lo he mostrado, pues es como un poco "repetitivo" eso de tener que leer la sección completa para simplemente acceder a un valor...

¿Cómo hacerlo?
Declarando la variable "valores" a nivel de clase y sólo usar la llamada a GetConfig si el contenido de esa clase es "nada" (Nothing o null).

Vale... aquí tienes el código, en el que se comprueba la última sección leída, si es diferente, se lee la sección al completo:

Private valores As IDictionary = Nothing
Private seccionAnt As String = ""
'
Private Function cfgGetValueSection(ByVal seccion As String, _
    		ByVal clave As String, _
    		ByVal predeterminado As String) As String
    ' Si la sección indicada no es la última leída
    ' asignamos un valor nulo para que vuelva a leer la sección
    If seccionAnt <> seccion Then
        valores = Nothing
        seccionAnt = seccion
    End If
    '
    If valores Is Nothing Then
        valores = CType(ConfigurationSettings.GetConfig(seccion), IDictionary)
    End If
    If valores.Contains(clave) Then
        Return valores(clave).ToString
    Else
        Return predeterminado
    End If
End Function

 

El código de C# está un poco más abajo.

 

Crear las secciones del fichero de configuración mediante código

Después de este alto en el camino, veamos cómo podemos solucionar el problema que te comenté en el artículo anterior: Que no exista el fichero de configuración y queramos crearlo en tiempo de ejecución.

Para hacer esto, tendremos que echar mano del método LoadXml de la clase XmlDocument, ahora te explico el porqué, pero antes vamos a ver el problema que se nos presenta.

El problema que nos encontrábamos al hacer algo como esto:

'
ficConfig = Application.ExecutablePath & ".config"
'
' comprobar si existe el fichero de configuración
If System.IO.File.Exists(ficConfig) = False Then
    cfgSetValue("configuration/appSettings", "CheckBox1.Checked", "False")
    cfgSetValue("configuration/appSettings", "RadioButton1.Checked", "True")
    cfgSetValue("configuration/appSettings", "RadioButton2.Checked", "False")
    cfgSetValue("configuration/appSettings", "TextBox1.Text", "Hola, Mundo")
    cfgSetValue("configuration/appSettings", "Form1.Text", "Form1")
    '
    configXml.Save(ficConfig)
End If
'
configXml.Load(ficConfig)

Es que, aunque estemos llamando al método Save de la clase XmlDocument, realmente no tenemos nada que guardar, es decir, el objeto del tipo XmlDocument está vacío... no tiene nada de nada...
¿Cómo es posible?
Muy sencillo, todas las líneas anteriores que usan el método cfgSetValue, realmente "pretenden" guardar algo en el objeto configXml, pero si recuerdas lo que te dije, ese método solamente asignaré el valor si la clave ya existe... si no existe, simplemente devuelve el valor predeterminado (el indicado en el último parámetro).
Por tanto, lo que debemos hacer es "llenar" el objeto configXml con "datos" de verdad...
Lamentablemente, la única forma de hacerlo que he encontrado es "por la cuenta la vieja", es decir, añadiendo el mismo código que te he mostrado más arriba... es decir, tenemos que meter ¡¡¡todo el código de cada una de las secciones, claves y valores!!!
Pero... peor sería que la aplicación dejara de funcionar porque un usuario "listillo" ha borrado el fichero ese con extensión .config que no sabe para que sirve...

Aquí tienes el código para crear ese fichero en tiempo de ejecución:

'
' Para usar este código,
' lo mejor es quitar las asignaciones creadas por el VS
'
ficConfig = Application.ExecutablePath & ".config"
'
' comprobar si exite el fichero de configuración
If System.IO.File.Exists(ficConfig) = False Then
    Dim sXml As New System.Text.StringBuilder
    '
    ' crear la cadena a asignar al objeto XmlDocument
    sXml.Append("<configuration>")
    sXml.Append("<configSections>")
    sXml.Append("<section name=""General"" type=""System.Configuration.DictionarySectionHandler"" />")
    sXml.Append("</configSections>")
    sXml.Append("<appSettings>")
    sXml.Append("<add key=""Form1.Text"" value=""Form1"" />")
    sXml.Append("<add key=""CheckBox1.Checked"" value=""False"" />")
    sXml.Append("<add key=""RadioButton1.Checked"" value=""True"" />")
    sXml.Append("<add key=""RadioButton2.Checked"" value=""False"" />")
    sXml.Append("<add key=""TextBox1.Text"" value=""TextBox1"" />")
    sXml.Append("<!-- Valores personalizados -->")
    sXml.Append("<add key=""Form1.Left"" value=""0"" />")
    sXml.Append("<add key=""Form1.Top"" value=""0"" />")
    sXml.Append("</appSettings>")
    sXml.Append("<General>")
    sXml.Append("<!-- Los valores posibles de usarEstilosXP son 0 ó 1 -->")
    sXml.Append("<add key=""Usar EstilosXP"" value=""1"" />")
    sXml.Append("<!-- Los valores posibles son 0 ó 1 -->")
    sXml.Append("<add key=""Cerrar aplicación"" value=""0"" />")
    sXml.Append("</General>")
    sXml.Append("</configuration>")
    '
    ' asignamos la cadena al objeto
    configXml.LoadXml(sXml.ToString)
    '
    ' Guardamos el contenido de configXml y creamos el fichero
    configXml.Save(ficConfig)
Else
    ' solo es necesario leerlo si no lo hemos creado
    configXml.Load(ficConfig)
End If
'
' Leemos el valor de usar estilos XP antes de llamar a InitializeComponent
If cfgGetValue("configuration/General", "Usar EstilosXP", "1") = "1" Then
    Application.EnableVisualStyles()
End If
'
'El Diseñador de Windows Forms requiere esta llamada.
InitializeComponent()
'

 

 

Pues creo que esto es todo... ya solamente queda averiguar cómo crear y asignar esos valores al fichero de configuración sin necesidad de hacer todo eso que he hecho... es decir, usando código normal, para que si una clave no existe, que la podamos crear "al momento", pero... eso será en otra ocasión, si es que averiguo cómo hacerlo... que he probado varias cosillas, pero no doy con la tecla.

Mientras tanto, espero que con lo mostrado en estos dos artículos te sea suficiente (a mi ya me vale así... uno que se conforma con cualquier cosa... je, je)

Nos vemos.
Guillermo
Nerja, 22 de Febrero de 2005 03:22

 


Código para Visual Basic.NET (VB.NET)El código para VB .NET

Todo el código de VB está en el artículo.

 


Código para C Sharp (C#)El código para C#

El código del método cfgGetValue usando la clase ConfigurationSettings.

//
private string cfgGetValue(string seccion, string clave, string predeterminado) 
{
    IDictionary valores;
    //
    valores = (IDictionary)ConfigurationSettings.GetConfig(seccion);
    if( valores.Contains(clave) ){
        return valores[clave].ToString();
    }
    else {
        return predeterminado;
    }
}


//
IDictionary valores = null;
string seccionAnt = "";
//
private string cfgGetValueSection(string seccion, string clave, string predeterminado) 
{
    // Si la sección indicada no es la última leída
    // asignamos un valor nulo para que vuelva a leer la sección
    if( seccionAnt != seccion ) {
        valores = null;
        seccionAnt = seccion;
    }
    //
    if( valores == null ) {
        valores = (IDictionary)ConfigurationSettings.GetConfig(seccion);
    }
    if( valores.Contains(clave) ) {
        return valores[clave].ToString();
    }
    else {
        return predeterminado;
    }
}

 

Usando el "creador" del código para los casos en que el fichero de configuración no exista:

//
ficConfig = Application.ExecutablePath + ".config";
//
// comprobar si exite el fichero de configuración
if( System.IO.File.Exists(ficConfig) == false ) {
    System.Text.StringBuilder sXml = new System.Text.StringBuilder();
    //
    // crear la cadena a asignar al objeto XmlDocument
    sXml.Append("<configuration>");
    sXml.Append("<configSections>");
    sXml.Append("<section name=\"General\" type=\"System.Configuration.DictionarySectionHandler\" />");
    sXml.Append("</configSections>");
    sXml.Append("<appSettings>");
    sXml.Append("<add key=\"Form1.Text\" value=\"Form1\" />");
    sXml.Append("<add key=\"CheckBox1.Checked\" value=\"False\" />");
    sXml.Append("<add key=\"RadioButton1.Checked\" value=\"True\" />");
    sXml.Append("<add key=\"RadioButton2.Checked\" value=\"False\" />");
    sXml.Append("<add key=\"TextBox1.Text\" value=\"TextBox1\" />");
    sXml.Append("<!-- Valores personalizados -->");
    sXml.Append("<add key=\"Form1.Left\" value=\"0\" />");
    sXml.Append("<add key=\"Form1.Top\" value=\"0\" />");
    sXml.Append("</appSettings>");
    sXml.Append("<General>");
    sXml.Append("<!-- Los valores posibles de usarEstilosXP son 0 ó 1 -->");
    sXml.Append("<add key=\"Usar EstilosXP\" value=\"1\" />");
    sXml.Append("<!-- Los valores posibles son 0 ó 1 -->");
    sXml.Append("<add key=\"Cerrar aplicación\" value=\"0\" />");
    sXml.Append("</General>");
    sXml.Append("</configuration>");
    // asignamos la cadena al objeto
    configXml.LoadXml(sXml.ToString());
    //
    // Guardamos el contenido de configXml y creamos el fichero
    configXml.Save(ficConfig);
}
else {
    // solo es necesario leerlo si no lo hemos creado
    configXml.Load(ficConfig);
}
//
//
if( cfgGetValue("configuration/General", "Usar EstilosXP", "1") == "1" ) {
    Application.EnableVisualStyles();
}
//
//
// Necesario para admitir el Diseñador de Windows Forms 
InitializeComponent();
//

 


Espacios de nombres usados en este artículo:

System
System.Windows.Forms
System.Xml
System.IO
System.Configuration
System.Collections

 


la Luna del Guille o... el Guille que está en la Luna... tanto monta...