AndorraDotNet

Comunitat d'usuaris .NET del pais dels pirineus
¿No perteneces a AndorraDotNet? ¡Regístrate y sé un miembro!
Iniciar sesión | REGÍSTRATE AQUÍ | Ayuda
en Buscar

400 Bad Request

Dímelo con flores ;-)

July 2008 - Artículos

  • Quieres ver las caras del VBTeam?

    Beth Massi publica un par de fotos en el blog del VBTeam, y nos cuenta la anécdota de porque todos visten de gris.

    Saludos,

    ** crossposting desde el blog de Lluís Franco en geeks.ms **
  • VBTeam - How Do I Videos (WPF Forms over Data)

    movie

    Beth Massi del equipo de VB, ha publicado nuevos videos en el sitio “How Do I” Videos — Visual Basic. En este caso se trata de dos videos que muestran cómo crear formularios basados en datos en Windows Presentation Foundation usando Visual Studio 2008.

    How Do I: Create a Simple Data Entry Form in WPF?

    #1 | How Do I: Create a Simple Data Entry Form in WPF?
    (24 minutes, 32 seconds)

    #2 | How Do I: Display Data in a List in WPF?
    (11 minutes, 47 seconds)

    El link en: http://msdn.microsoft.com/en-us/vbasic/bb466226.aspx#wpfdata

    Si no conoceis el sitio “How Do I” Videos darle un vistazo, que merece la pena y mucho... :-)

    ** crossposting desde el blog de Lluís Franco en geeks.ms **
  • HowTo: LDAP, obtener propiedades de la clase user

    Hace poco publicaba una actualización del método getUserLDAProperties de la clase LDAPservices. Esta función se encarga de recuperar todas las propiedades de la clase 'user' definidas  en el esquema del AD de la organización.

    LDAPUserProperties

    La verdad es que dicha corrección no me terminaba de satisfacer. Así que después de bucear un poco por el modelo de objetos del namespace System.DirectoryServices.ActiveDirectory he encontrado un método más elegante, y que además permite acceder a más propiedades opcionales (algo que la anterior versión no permitía).

    Os dejo aquí el código y en cuando tenga un minuto actualizo el código fuente de LDAPServices en skydrive.

    public static List<string> 
        getUserLDAPProperties(string LDAPURL)
    {
        List<string> properties =new List<string>();
        ActiveDirectorySchema objADSchema = ActiveDirectorySchema.GetCurrentSchema();
        ActiveDirectorySchemaClass objADUserSchema;
        ActiveDirectorySchemaPropertyCollection objADPropertiesCollection;
        objADUserSchema = objADSchema.FindClass("user");
        objADPropertiesCollection = objADUserSchema.MandatoryProperties;
        foreach (ActiveDirectorySchemaProperty objADSchemaProperty in objADPropertiesCollection)
        {
            properties.Add(objADSchemaProperty.Name);
        }
        objADPropertiesCollection = objADUserSchema.OptionalProperties;
        foreach (ActiveDirectorySchemaProperty objADSchemaProperty in objADPropertiesCollection)
        {
            properties.Add(objADSchemaProperty.Name);
        }
        return (from p in properties orderby p select p).ToList();
    }

    Nos vemos!

    ** crossposting desde el blog de Lluís Franco en geeks.ms **
  • ¿Goodbye Dictionary?

    Corrección al artículo (16/07/2008):

    Todas las mediciones de tiempo efectuadas en las compartivas entre listas y diccionarios han sido mal efectuadas y no son válidas. Porqué? Porque a un servidor se le olvidó 'resetear' el cronómetro del StopWatch entre una medición y otra (ay, ay, ay...), de modo que los tiempos tomados para el objeto dictionary incluyen también los de la lista, y por eso son mucho mayores de lo esperado.

    Un 'pequeño' olvido pero que afecta totalmente al sentido del post, ya que la conclusión del post era que acceder a un elemento de un diccionario NO era más rápido que acceder a un elemento de una lista, cuando la realidad es que SI lo es... y mucho. Siento haber publicado lo contrario y aquí me retracto.

    De este error puedo sacar varias conclusiones:

    • Revisar: Acostumbrarme a revisar más en profundidad lo que publico. He usado infinidad de veces el objeto StopWatch y no es nuevo para mi, así que todavía me duele más haberme olvidado de reiniciar el crono... Muchas gracias por el comentario Steven.
    • No hacer tantas cosas al mismo tiempo: La verdad es que cuando escribí el post estaba haciendo varias cosas al mismo tiempo, entre las cuales me encontraba realizando unas pruebas para una aplicación, y al medir los tiempos me quedé muy sorprendido y empecé a escribir el post sin detectar el error en mi código.
    • Todos nos equivocamos: Eso es evidente, y cualquiera puede cometer un error como este. Lo malo es que en lugar de escribir el post rápidamente y publicarlo, debería haberlo dejado para revisarlo hoy... pero lo hecho hecho está.
    • Hay que aprender de los errores: Prefiero hacer algo y equivocarme a no hacer nada y no equivocarme. Con todo, espero aprender algo de todo esto, porque todos seguimos aprendiendo día a día (y malo del día que no lo hagamos).

    Pero vamos con la corrección. Los nuevos tiempos si agregamos un watch.Reset(); antes de empezar a tomar los tiempos del diccionario son los siguientes:

    Compare load 100000 items in List vs. Dict. (ms.)
    List: 678,4068
    Dict: 759,5071

    Compare get one item by key in List vs. Dict. (ms.)
    List: 0,3645
    Dict: 0,0058

    Compare verify one item by key in List vs. Dict. (ms.)
    List: 0,2748
    Dict: 0,0058

    La conclusión es que cargar datos en un diccionario es sólamente un poco más lento que cargarlos en una lista. mientras que es mucho más rápido acceder a los elementos que contiene (del orden 50 veces más rápido aproximadamente). Evidentemente esto justifica sobradamente el uso de diccionarios.

    Bueno... Tal vez el título del artículo ahora debería quedar como "¿Goodbye Dictionary? ¡Hola melón!" :-P

    Saludos desde Andorra,

    Fin de la corrección al artículo

    El artículo original (15/07/2008):

    Generics

    La aparición del framework 2.0 nos trajo una grata sorpresa: La aparición de Generics, que nos proveía por fin de un conjunto de colecciones fuertemente tipadas, que mejoraban mucho el rendimiento al evitar el uso de boxing y unboxing, y permitían un código mucho más legible, elegante, así como detectar y prevenir muchos errores en tiempo de compilación.

    GenericsError1

    Desde ,entonces, de todos los distintos tipos de colecciones genéricas, en el 90% de los casos he usado estos dos: List y Dictionary. El primero de ellos es obviamente una lista de objetos <T>, algo de uso cotidiano, y el segundo es el equivalente genérico del viejo diccionario, que permite guardar una colección de elementos y una clave para acceder a ellos <TKey>, <TValue>:

    Dictionary<int, CPItem> dict = new Dictionary<int, CPItem>();

    Este tipo diccionario es ideal para almacenar una serie de objetos (por ejemplo clientes que provienen de una BD) y poder acceder a uno de ellos a través de su campo clave o identificador, sea éste del tipo que sea (por ejemplo en el caso anterior la clave es de tipo int):

    CPItem item2 = dict[24];

    De este modo, es muy sencillo acceder a un elemento de la colección a través del valor de su identificador (no confundir con su posición dentro de la lista). Sin embargo y a pesar de su innegable potencia, hay un par de cosas que no me gustan de los diccionarios:

    • Siempre es más lento cargar los objetos en un diccionario que en una lista (del orden de 2 a 1 aproximadamente)
    • Un diccionario no es serializable 'per se' (al menos sin hacer trucos), y esto SI puede ser un problema.

    Sin embargo, tiene la gran ventaja frente a la lista de que permite acceder de forma casi instantánea a un elemento de la misma, y además contiene métodos que permiten verificar si la colección contiene un elemento, ya sea por clave o por valor.

    List vs. Dictionary

    Veamos un ejemplo rápido, declaramos una clase con algunos miembros de diversos tipos de datos (para tener de todo un poco) y usamos esta clase para cargar varios objetos en una lista y un diccionario respectivamente:

    class CPItem
    {
        const int max = 1000000;
     
        public int propInt { get; set; }
        public string propString { get; set; }
        public DateTime propDateTime { get; set; }
        public bool propBool { get; set; }
        public double propDouble { get; set; }
     
        public CPItem(int propint)
        {
            Random r = new Random(max);
            propInt = propint;
            propString = string.Format("Item {0}", propint.ToString("000000"));
            propDateTime.AddDays(propint);
            propBool = propint <= 0.5 ? false : true;
            propDouble = r.NextDouble() * max;
        }
    }

    Declaramos los dos objetos genéricos:

    List<CPItem> list = new List<CPItem>();
    Dictionary<int, CPItem> dict = new Dictionary<int, CPItem>();

    Y a continuación usamos un StopWatch para medir los tiempos de carga de 100.000 elementos... y podremos observar una gran diferencia:

    private void LoadListDict()
    {
        const int max = 100000;
        Stopwatch watch = new Stopwatch();
     
        list.Clear();
        watch.Start();
        for (int i = 1; i <= max; i++)
        {
            list.Add(new CPItem(i));
        }
        watch.Stop();
        TimeSpan ts1 = watch.Elapsed;
     
        dict.Clear();
        watch.Start();
        for (int i = 1; i <= max; i++)
        {
            dict.Add(i, new CPItem(i));
        }
        watch.Stop();
        TimeSpan ts2 = watch.Elapsed;
     
        this.ResultsTextBox.Text = string.Format(
            "Compare load {0} items in List vs. Dict. (ms.)\r\nList: {1}\r\nDict: {2}",
            max.ToString(),
            ts1.TotalMilliseconds.ToString(),
            ts2.TotalMilliseconds.ToString());
    }

    En mi estación estos son los tiempos promedio de carga después de ejecutar 5 veces:

    Compare load 100000 items in List vs. Dict. (ms.)
    List: 737,1392
    Dict: 1479,4486

    Bien, está claro que si hay que elegir entre ambos hay que tener un buen motivo para escoger el diccionario... ¡ya que la lista es el doble de rápida!

    LINQ to objects

    Con la aparición de VS2008 y del framework 3.5, aparecen las expresiones de consulta. Éstas nos permiten manipular colecciones de objetos en memoria, para filtrarlos, ordenarlos y agruparlos según nuestras necesidades.

    Así que es posible que nos preguntemos ¿es necesario el uso de diccionarios, si ahora disponemos de LINQ para manejar listas? Es decir, si puedo cargar los datos en una lista genérica y acceder mediante LINQ to objects a un elemento, o a un subconjunto de éstos... ¿qué sentido tiene usar un dictionary?

    Uhm... Pues supongo que el rendimiento podría ser un factor, ya que según la documentación de este objeto: Retrieving a value by using its key is very fast, close to O(1), because the Dictionary<(Of <(TKey, TValue>)>) class is implemented as a hash table. O dicho de otro modo: Recuperar un valor utilizando su clave es muy rápido, porque la clase Dictionary se implementa como una tabla hash.

    Ya sabía yo que había un buen motivo para usar un diccionario: Debe ser más mucho más rápido al acceder a un objeto a través de su clave que extraerlo de una lista mediante LINQ... seguro! .... ¿o tal vez no?

    Hagamos un par de pruebas:

    A) Cuanto tardamos en recuperar a un elemento a través de su clave:

    private void GetOneItemByKey(int keyvalue)
    {
        if (list.Count == 0 || dict.Count == 0) LoadListDict();
        Stopwatch watch = new Stopwatch();
        watch.Start();
        CPItem item1 = (from l in list where l.propInt == keyvalue select l).Single();
        watch.Stop();
        TimeSpan ts1 = watch.Elapsed;
     
        watch.Start();
        CPItem item2 = dict[keyvalue];
        watch.Stop();           
        TimeSpan ts2 = watch.Elapsed;
     
        this.ResultsTextBox.Text = string.Format(
            "Compare get one item by key in List vs. Dict. (ms.)\r\nList: {0}\r\nDict: {1}",
            ts1.TotalMilliseconds.ToString(),
            ts2.TotalMilliseconds.ToString());
    }

    En mi estación estos son los tiempos promedio de carga después de ejecutar 5 veces:

    Compare get one item by key in List vs. Dict. (ms.)
    List: 3,8518
    Dict: 3,8566

    Pues parece que ambos tiempos son iguales. Es más, acceder a la colección a través de LINQ ¡es incluso un poquito más rápido! Uhm, vaya... bueno, veamos el segundo ejemplo antes de sacar conclusiones:

    B) Cuanto tardamos en verificar si existe un elemento a través de su clave:

    private void VerifyOneItemByKey(int keyvalue)
    {
        if (list.Count == 0 || dict.Count == 0) LoadListDict();
        Stopwatch watch = new Stopwatch();
        watch.Start();
        var items = from l in list where l.propInt == keyvalue select l;
        bool v1 = (items.Count() == 0);
        watch.Stop();
        TimeSpan ts1 = watch.Elapsed;
     
        watch.Start();
        bool v2 = dict.ContainsKey(keyvalue);
        watch.Stop();
        TimeSpan ts2 = watch.Elapsed;
     
        this.ResultsTextBox.Text = string.Format(
            "Compare verify one item by key in List vs. Dict. (ms.)\r\nList: {0}\r\nDict: {1}",
            ts1.TotalMilliseconds.ToString(),
            ts2.TotalMilliseconds.ToString());
    }

    En mi estación estos son los tiempos promedio de carga después de ejecutar 5 veces:

    Compare verify one item by key in List vs. Dict. (ms.)
    List: 3,906
    Dict: 3,9111

    Pues parece que en este caso ambos tiempos son también iguales. Entonces definitivamente NO ES MÁS RÁPIDO UTILIZAR UN DICCIONARIO PARA ACCEDER A LOS ELEMENTOS QUE CONTIENE A TRAVÉS DE SU CLAVE:

    Carga Recuperar elemento Existe elemento
    image image image

    Conclusión 

    Pues sinceramente, si no es más rápido el acceso a un diccionario, pero es más lento en su carga y encima no es serializable... ¿en qué casos podemos seguir usando diccionarios en lugar de listas?

    Espero vuestras opiniones,

    ** crossposting desde el blog de Lluís Franco en geeks.ms **
  • How to: Utilizar en modelo de proveedores de datos de ADO.NET 2.0

    (*) Este post es bastante antiguo y lo tenía publicado en otro blog que ya no existe, pero como veo que es un tema recurrente en los foros de MSDN he decidido publicarlo de nuevo.

    A petición del inefable Juansa, vamos a ver cómo funciona el modelo de proveedores de ADO.NET 2.0.

    1

    Estre modelo es ideal para todas aquellas aplicaciones que deben utilizar distintos proveedores de datos (Access, SQL Server, Oracle), ya que disponen de unas factorías de objetos que proporcionan un nivel de abstracción. Por ejemplo, si deseamos usar el proveedor de SQL Server ya no es necesario usar las clases conexión, comando, etc. que hay dentro del namespace System.Data.SqlClient, ahora podemos crear una factoría basada en SQL Server y pedirle que nos cree una clase de tipo connection, comando, etc.

    De este modo, si deseamos cambiar el proveedor de datos basta con cambiar el string del nombre del proveedor y la cadena de conexión. Para muestra un botón:

    Private Sub TestFactoryProviders()    
        Dim ProviderName As String = "System.Data.SqlClient"    
        Dim CnnStr As String = "Data Source=TU_SERVIDOR_SQL;" + _      
            "Initial Catalog=TU_BD;" + _      
            "Integrated Security=True"    
        Dim SQLStr As String = "SELECT * FROM TU_TABLA"    
        'Crea la factoria en base al proveedor especificado    
        Dim Factory As DbProviderFactory = DbProviderFactories.GetFactory(ProviderName)    
        'Crea la conexión del tipo apropiado (según proveedor)    
        Dim Con As DbConnection = Factory.CreateConnection()    
        Using Con        
            'Asigna la cadena de conexión        
            Con.ConnectionString = CnnStr        
            Con.Open()        
            'Crea un command del tipo apropiado (según proveedor)        
            Dim Cmd As DbCommand = Con.CreateCommand()        
            Using Cmd            
                Cmd.CommandType = CommandType.Text            
                Cmd.CommandText = SQLStr            
                'Crea un DataTable y lo llena a partir del             
                'DataReader que devuelve el comando            
                Dim dt As DataTable = New DataTable("dt1")            
                dt.Load(Cmd.ExecuteReader())            
                For Each row As DataRow In dt.Rows                
                    'Haz lo que quieras con los datos... :-P            
                Next        
            End Using        
            Con.Close()    
        End Using
    End Sub

    Según el ejemplo, cambiando los valores de las dos primeras variables podemos utilizar cualquier origen de datos de forma indistinta.
    Happy coding!

    ** crossposting desde el blog de Lluís Franco en geeks.ms **
  • Corrección función getUserLDAPProperties

    _tipex

    Corrección aplicable al post:

    http://geeks.ms/blogs/lfranco/archive/2008/07/08/accediendo-al-directorio-activo-de-la-organizaci-243-n-desde-net-iv.aspx

    Ayer estuve revisando una de las funciones de la librería LDAPservices, que se encarga de devolver los nombres de las propiedades del objeto usuario dentro del AD. Hoy os publico una corrección, ya que observé que la función no devolvía correctamente todos los nombres de propiedades.

    Este bug es producido porque en la función se utiliza un objeto DirectorySearcher para devolver todos los objetos de tipo usuario en el AD, a continuación lee los nombres de las propiedades del primer usuario encontrado y los inserta en una colección. A continuación la función no continúa procesando el resto de usuarios, ya que deberían tener las mismas propiedades, no? Pues no. Para mi sorpresa resulta que sólo se devuelven aquellas propiedades del usuario que han sido informadas:

    public static List<string> 
        getUserLDAPProperties(string LDAPURL)
    {
        List<string> properties =new List<string>();
        DirectoryEntry entries = new DirectoryEntry(LDAPURL);
        DirectorySearcher searcher = new DirectorySearcher(
            entries, "(&(objectCategory=person)(objectClass=user))");
        try
        {
            foreach (SearchResult result in searcher.FindAll())
            {
                foreach (string property in
                    result.GetDirectoryEntry().Properties.PropertyNames)
                {
                    properties.Add(property);
                }
                break;                    
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return properties;
    }

    Para solucionar este bug, hay que procesar las propiedades de todos los usuarios, quedando el código de la función así como sigue:

    foreach (SearchResult result in searcher.FindAll())
    {
        foreach (string property in
            result.GetDirectoryEntry().Properties.PropertyNames)
        {
            if(!properties.Contains(property)) properties.Add(property);
        }
    }

    Como veis, se suprime el break que terminaba el bucle y se añade una condición para que se agreguen a la colección sólo aquellos nombres de propiedad que no se han agregado previamente.

    Saludos y happy coding!!!

    ** crossposting desde el blog de Lluís Franco en geeks.ms **
  • Accediendo al directorio activo de la organización desde .NET (IV)

    homer1Buscar

    Hoy vamos a buscar. Buscar elementos en el AD dentro de nuestra organización, y como lo más habitual es buscar usuarios o grupos he creado algunas funciones para facilitar esta tarea dentro de la clase LDAPServices (os dejo para vosotros ampliarlas para buscar equipos, por ejemplo).

    También veremos cómo extraer los nombres de las propiedades de un objetos del AD, ya que en ocasiones queremos filtrar o devolver el valor de una propiedad de un objeto y no sabemos cómo se llama esta propiedad. Por ejemplo, para devolver el teléfono de un usuario en el AD hay que preguntar por el valor de la propiedad 'telephoneNumber'.

    Tal vez más adelante (si tengo tiempo) lo ampliemos un poco. Me gustaría realizar un post acerca de cómo realizar un mapeador de propiedades para asignar los valores de las propiedades de objetos LDAP a objetos de nuestra aplicación. Esto podría ser muy útil por ejemplo, para importar los valores de nuestros usuarios de AD a una tabla de empleados.

    Antes de empezar quiero comentaros que para poder realizar todo esto va a ser necesario agregar una referencia a System.DirectoryServices a nuestro proyecto. Pero vamos ya con estos nuevos métodos de LDAPServices:

    getLDAPFilterString - Devuelve una cadena de consulta en formato LDAP query, que permite filtrar los objetos que deseamos devolver. Esta función filtra aquellos carácteres no deseados para evitar posible inyección de código LDAP por parte de un usuario (aunque es muy mejorable, estoy seguro que mi JoseMariCariño sería capaz de sacar información de aquí, casi me apuesto algo).

    public enum LDAPFilterType
    {
        UsersAndGroups,
        OnlyUsers,
        OnlyGroups
    }
     
    public static string 
        getLDAPFilterString(LDAPFilterType Type, string Filter)
    {
        Filter = Filter.Replace("&","");
        Filter = Filter.Replace("|","");
        Filter = Filter.Replace("*", "");
        string FilterByName = "(samAccountName=*{0}*)";
        string f = string.Empty;
        switch (Type)
        {
            case LDAPFilterType.OnlyUsers:
                f = "(&(objectCategory=person)(objectClass=user){0})";
                break;
            case LDAPFilterType.OnlyGroups:
                f = "(&(objectCategory=Group){0})";
                break;
            case LDAPFilterType.UsersAndGroups:
                f = "(|(&(objectCategory=person)(objectClass=user){0})(&(objectCategory=Group){0}))";
                break;
        }
        if (Filter == string.Empty)
        {
            return string.Format(f, string.Empty);