Manejar los
valores almacenados en el fichero .config
|
Publicado el 22/Feb/2005
|
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" ThenEs 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...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:
- La primera es hacer estas dos cosas:
1- NO usar la clase AppSettingsReader para leer los valores
2- Usar el método cfgGetValue.- La segunda es indicarle al punto NET que en el fichero de configuración existe una sección llamada General y después de decírselo podemos usar cualquiera de las dos formas mostradas anteriormente para leer valores, aunque solamente el método cfgGetValue será capaz de "extraer" información de esa sección. La clase AppSettingsReader supuestamente no sabe nada de que esa sección existe... aunque nosotros sabemos que sí lo sabe...
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
Todo el código de VB está en el artículo.
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