Tag Archive for basic

.NET y Configuraciones – Parte 6

Bueno, miren que lejos hemos llegado con el asunto de las configuraciones :) pero no se preocupen, aún falta mucho más que aprender sobre nuestros amigos los archivos app/web.config y sobre nuestras configuraciones personalizadas, imagínense un mundo con menos y con configuraciones más claras y con mayor significado para el desarrollador. Hasta el momento hemos explorado secciones, grupos de secciones, elementos, colecciones todas personalizadas, hoy aprenderemos un poco acerca de validadores.

Element Validators

Imagínense que en su sistema de configuración necesitan validar que el número ingresado por el usuario en la configuración se encuentre dentro de un rango, o que el texto ingresado cumpla con una expresión regular o cumpla una longitud mínima o máxima (si, ya se, ambos se pueden lograr con un regex, pero para efectos de ejemplo digamos que son dos diferentes). Muchos quizás nos veremos tentados a obtener el valor de la configuración y posteriormente validarlo, bien, en el caso de elementos de configuración es buena idea abstenerse de hacerlo. La buena noticia es que tenemos a nuestra disposición toda una infraestructura de validación de valores de configuración y esta se lleva a cabo en el momento en que el ConfigurationManager lee los valores.

Los Validators son atributos especiales que acompañan a nuestros elementos de configuración, podemos definir nuestros propios validadores (ese será el tema de un post futuro) pero por defecto la framework nos empaca un par de validadores simples y sencillos:

IntegerValidator Valida un entero dentro de un rango
LongValidator Valida un número dentro de un rango
PositiveTimeSpanValidator Valida un timespan dentro de un rango positivo
RegexStringValidator Valida que una cadena cumple con una expresión regular
StringValidator Valida que una cadena debe cumplir una longitud máxima/mínima
TimeSpanValidator Valida un timespan dentro de un rango

Usar los validadores no puede ser más sencillo, simplemente adornamos nuestras propiedades o elementos con el atributo del validador que nos interesa y la framework de configuración se encarga del resto

using System;
using System.Configuration;

namespace Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [ConfigurationProperty("port", DefaultValue = 80)]
        [IntegerValidator(MaxValue = 100, MinValue = 20)]
        public int Port {
            get { return (int) this["port"]; }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host {
            get { return (string) this["host"]; }
        }

        [ConfigurationProperty("timeout")]
        [TimeSpanValidator(MinValueString = "00:00:00",
            MaxValueString = "00:01:00")]
        public TimeSpan Timeout {
            get { return (TimeSpan) this["timeout"]; }
        }
    }
}

Para probar la configuración podemos usar este simple app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample" type="Samples.SampleConfigurationSection, ValidatorSample"/>
  </configSections>
  <sample host="localhost" port="80" timeout="00:00:30" />
</configuration>

Y este pequeño programa de consola puede servirnos como simple prueba

using System;
using System.Configuration;

namespace Samples {
    internal class Program {
        private static void Main(string[] args) {
            var cfg = ConfigurationManager.GetSection("sample")
                as SampleConfigurationSection;
            if (cfg == null)
                return;

            Console.WriteLine("Host: {0}, Port: {1}, Timeout: {2}",
                cfg.Host, cfg.Port, cfg.Timeout);

            Console.ReadLine();
        }
    }
}

Si algún elemento no cumple con lo esperado por el validador el ConfigurationManager tirará una excepción de tipo ConfigurationErrorsException que a su vez en la propiedad Errors contendrá los ConfigurationException en forma de arreglo.

.NET y Configuraciones – Parte 5

Aunque hemos creado colecciones de elementos en las secciones de configuración personalizadas no nos dejemos engañar, hay mucho más acerca de las colecciones de elementos de configuración que simplemente conocer como agregar elementos nuevos.

Tipos de colecciones de configuración

Existe un enumerador especial, ConfigurationElementCollectionType, que nos sirve como indicador de “cómo” debe ser el comportamiento de la colección. Al principio uno piensa “¿cómo así el comportamiento? una colección simplemente es un número finito secuencial de elementos”. Bueno, veamos en que difiere cada tipo:

AddRemoveClearMap El tipo por defecto, los elementos de la colección pueden unirse o fusionarse dentro de una jerarquía de archivos de configuración (por ejemplo, la jerarquía en Web.config). Existen operaciones de Clear, Remove, Add que permiten modificar las propiedades heredadas dentro de la jerarquía
AddRemoveClearMapAlternate Igual que AddRemoveClearMap, con la diferencia en que sus elementos ordenan sus elementos en la definición actual antes que la de la jerarquía. Estando los elementos locales antes que los elementos heredados (en la colección)
BasicMap Los elementos definidos en un nivel son compartidos con los de los niveles hijos, pero los niveles hijos no pueden modificar la colección definida por su padre
BasicMapAlternate Funciona igual al BasicMap, pero los elementos heredados son mostrados después que los elementos locales

Las colecciones de tipo BasicMap no poseen operaciones de add, remove, clear. Los tipos Alternate incluyen los elementos padres después de la definición de elementos locales. Lo más común es mantenerse en colecciones de tipo AddRemoveClearMap y utilizar BasicMap cuando realmente queremos asegurarnos que no se debe modificar la colección.

Definir nodos como elementos

Otra opción poco común pero sencilla de hacer es evitar utilizar la palabra add para agregar el nodo a la colección y en vez de eso utilizar el elemento como nodo de la colección. Esto es realmente sencillo y muchas veces nos puede mejorar la claridad del código de configuración. El secreto es simplemente hacer override de las propiedades de la colección que se encarga de retornar los nombres de los elementos en la colección, ElementName y CollectionType, y opcional a ello indicar el tipo de colección que utilizaremos marcando la colección con una propiedad de un atributo, ConfigurationCollectionAttribute.

using System.Configuration;

namespace Samples {
    [ConfigurationCollection(typeof (ServerConfigurationElement),
        CollectionType = ConfigurationElementCollectionType.BasicMap)]
    public class ServerConfigurationCollection : ConfigurationElementCollection {
        public ServerConfigurationElement this[int index] {
            get { return BaseGet(index) as ServerConfigurationElement; }
            set {
                if (BaseGet(index) != null)
                    BaseRemoveAt(index);
                BaseAdd(index, value);
            }
        }

        public new ServerConfigurationElement this[string name] {
            get { return BaseGet(name) as ServerConfigurationElement; }
        }

        public override ConfigurationElementCollectionType CollectionType {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }

        protected override string ElementName {
            get { return "server"; }
        }

        protected override ConfigurationElement CreateNewElement() {
            return new ServerConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element) {
            var value = element as ServerConfigurationElement;
            return value != null ? value.Name : null;
        }
    }
}

Y nuestro archivo de configuración ahora tendría la siguiente forma

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="sample"
             type="Samples.SampleConfigurationSection, Samples.Cfg.AdvancedCollections"/>
  </configSections>
  <sample default="default">
    <servers>
      <server name="default" host="localhost" />
      <server name="another" port="8080" host="192.168.1.1" />
    </servers>
  </sample>
</configuration>

Algunos quizás encontraran esto ligeramente más fácil de leer que la versión original publicada en la parte 4 de esta serie.

Rayos, sin darnos cuenta llevamos ya cinco artículos sobre configuración personalizada en .Net (no se preocupen, aún faltan muchos más), el tiempo vuela cuando uno se divierte :P , como siempre agradezco sus comentarios, tags, pings, whatever… siempre son bienvenidos….

Y bueno, como dice Porky…

.NET y Configuraciones – Parte 4

Hasta el momento hemos tocado la creación de configuraciones personalizadas, elementos de configuración personalizados y la creación de grupos de secciones de configuración personalizados. Es interesante pero eso simplemente es la punta del iceberg de todo lo que dentro del concepto de configuración que nos permite manejar o mover la .NET Framework, hoy profundizaremos un poco más acerca de agregar nuevas características a nuestra configuración personalizada.

Colecciones y listas

Se que muchos hemos visto secciones de la configuración de ASP.NET y notado el uso de colecciones en él web.config, por ejemplo, el segmento de appSettings:

<appSettings>
    <clear />
    <remove name="port" />
    <add name="port" value="81" />
</appSettings>

Luego acceder a tal segmento de la configuración es "relativamente" sencillo, basta con algo como esto:

int port = int.Parse(ConfigurationManager.AppSettings["port"]);

Como habrán notado esto nos permite tener secciones que se comportan como colecciones de valores, que luego pueden ser accedidas por un indexer usando el nombre (en este caso "port") o el índice (en este caso, 0). Estas colecciones también soportan operaciones básicas (remove, clear, add). Crear colecciones de valores para la configuración no es para nada difícil simplemente hay que heredar de una clase abstracta especial, ConfigurationElementCollection

Comencemos por la unidad mínima, digamos que son elementos para datos de servidores

using System.Configuration;

namespace Samples {
    public class ServerConfigurationElement : ConfigurationElement {
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name {
            get { return (string) this["name"]; }
        }

        [ConfigurationProperty("port", DefaultValue = 80)]
        public int Port {
            get { return (int) this["port"]; }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host {
            get { return (string) this["host"]; }
        }
    }
}

Hasta el momento nada diferente de lo que conocemos. Ahora agreguemos el soporte para la colección de elementos de configuración tipo ServerConfigurationElement

using System.Configuration;

namespace Samples {
    [ConfigurationCollection(typeof(ServerConfigurationElement))]
    public class ServerConfigurationCollection : ConfigurationElementCollection {
        protected override ConfigurationElement CreateNewElement() {
            return new ServerConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element) {
            var value = element as ServerConfigurationElement;
            return value != null ? value.Name : null;
        }

        public ServerConfigurationElement this[int index] {
            get {
                return BaseGet(index) as ServerConfigurationElement;
            }
            set {
                if (BaseGet(index) != null)
                    BaseRemoveAt(index);
                BaseAdd(index, value);
            }
        }

        public new ServerConfigurationElement this[string name] {
            get {
                return BaseGet(name) as ServerConfigurationElement;
            }
        }
    }
}

Obligatoriamente tenemos que implementar los métodos CreateNewElement (el cual creo su nombre e implementación son suficientemente descriptivos) y el método GetElementKey, este nos retorna el valor que tendrá la key del diccionario de elementos. Observen como implementamos los indexers que luego necesitaremos para acceder a los elementos de la colección. Nótese también que nuestra colección esta adornada con el atributo ConfigurationCollectionAttribute, el cual no es estrictamente necesario.

Bien, nos queda simplemente implementar la sección de configuración general, no muy diferente

using System.Configuration;

namespace Samples {
    public class SampleConfigurationSection : ConfigurationSection {
        [ConfigurationProperty("default", IsRequired = true)]
        public string DefaultServer {
            get { return (string) this["default"]; }
        }

        [ConfigurationProperty("servers", IsRequired = true)]
        public ServerConfigurationCollection Servers {
            get {
                return this["servers"]
                   as ServerConfigurationCollection;
            }
        }
    }
}

Nuestra configuración personalizada lucirá algo así:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
        name="sample"
        type="Samples.SampleConfigurationSection, Samples.Cfg"/>
  </configSections>
  <sample default="default">
    <servers>
      <clear />
      <add name="default" host="localhost" />
      <add name="another" port="8080" host="192.168.1.1" />
    </servers>
  </sample>
</configuration>

Y en nuestro programa podemos acceder a ella de forma sencilla

using System;
using System.Configuration;

namespace Samples {
    internal class Program {
        private static void Main(string[] args) {
            var cfg = ConfigurationManager.GetSection("sample") as SampleConfigurationSection;
            if (cfg == null) return;

            Console.WriteLine("Default item: {0}", cfg.DefaultServer);

            foreach (ServerConfigurationElement server in cfg.Servers) {
                Console.WriteLine("server: {0}, host: {1}, port: {2}",
                    server.Name, server.Host, server.Port);
            }

            ServerConfigurationElement defaultServer = cfg.Servers[cfg.DefaultServer];

            Console.WriteLine("Default host: {0}", defaultServer.Host);
            Console.ReadLine();
        }
    }
}

Como les advertí desde el principio, nada complicado y sumamente útil. Creo que vale la pena mencionar que cualquier comentario, sugerencia, duda u observación soy todo ojos para leer sus comentarios.

Cómo dijo Terminator, volveré…

Regresando a lo básico – Javascript en ASP.NET

Hace unos días un amigo me preguntó algo curioso:

Necesito que todo lo que entre mi usuario en una caja de texto en ASP.NET se transforme automáticamente a mayúsculas. Se me había ocurrido usar OnTextChanged pero no se que tan efectivo podría resultarme.

Bien, considero que esta es algo así como una pregunta elemental, así que tomemonos el tiempo para analizarla y ver las opciones. Asumamos que no podemos usar ASP.NET Ajax Library, ni ASP.NET Ajax Toolkit. Tomemos en cuenta que tampoco podemos usar algo así como JQuery.

La implementación en ASP.NET que propone nuestro amigo se vería algo así:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebControls.aspx.cs"
    Inherits="WebApplication7.WebControls" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="nameText" runat="server"
            OnTextChanged="nameText_TextChanged"
            AutoPostBack="true" />
        <br />
        <asp:Label Id="nameLabel" runat="server" />
        <br />
        <asp:Button ID="acceptButton" runat="server" Text="Accept" />
    </div>
    </form>
</body>
</html>

Y el “codebehind” sería algo simple como esto:

using System;
using System.Web.UI;

namespace WebApplication7 {
    public partial class WebControls : Page {
        protected void Page_Load(object sender, EventArgs e) {
        }

        protected void nameText_TextChanged(object sender, EventArgs e) {
            nameText.Text = nameText.Text.ToUpper();
        }
    }
}

Bien, ASP.NET lo que hace al activar el "AutoPostback" es que en cuanto el control pierde su "focus" se envía la página completa de regreso al server, este procesa el evento (en este caso TextChanged) y regresamos el contenido en mayúsculas. Vemos un par de problemas con este caso en particular, el primero es lo poco "práctico" que es enviar toda la página de regreso solamente para hacer una operación "sencilla" como la es transformar el texto en mayúsculas (si, probablemente hay operaciones más "complejas" en las cuales se necesite hacer tal cosa, pero en este caso no se justifica). Tal operación es fácilmente hecha con un simple script de Javascript.

Cuando comenté esto mi amigo me respondió: hey, pero no estoy usando MVC, puedo usar fácilmente Javascript con ASP.NET?, pues, ¡claro que si!, hay varias maneras. Primero, veamos como sería la función de Javascript en cuestión:

function textToUppercase(sender) {
    if (sender && sender.value) {
        sender.value = sender.value.toUpperCase();
     }
}

Una función simple, nada del otro mundo. Bien, ahora solamente modificamos el TextBox para que luzca algo así:

<asp:TextBox ID="nameText" runat="server" onblur="textToUppercase(this);" />

Listo, con esto podemos elminar el codigo que hacía la misma operación en el server, ya no más postbacks :)

Recuerden, no hay problema si agregamos atributos extras de HTMl a nuestros controles de ASP.NET.

Moraleja: Aprovechen el proceso del lado del browser cliente, no abusen del codebehind. Javascript es su amigo, aprovechenlo. No en vano Microsoft esta invirtiendo tiempo para mejorar el soporte de Javascript y la programación al browser cliente para esta y las próximas versiones de ASP.NET.