Enviar archivos al navegador del cliente

Para mandar el contenido de un archivo al browser o navegador del cliente, y presentarle el cuado de diálogo si desea abrir, guardar o cancelar este archivo, se debe cumplir lo siguiente:


Construir una página sin código html. Esto es muy importante ya que cuando se envíe el contenido del archivo, no debe haberse enviado código html ya que la mezcla de estos (el código y el archivo) hará que el navegador del usuario no entienda bien el contenido.


En el código fuente de esta página, agregar las siguientes líneas (los números son sólo de referencia)


1.- Response.ClearHeaders();


2.- Response.ClearContent();


3.- Response.AddHeader(“Content-Disposition”, “attachment;filename=NOMBRE_DEL_ARCHIVO“);


4.- Response.ContentType = “application/pdf“;


5.- Response.BinaryWrite(ARREGLO_DE_BYTES_CON_EL_CONTENIDO_DEL_ARCHIVO);


6.- Response.Flush();


7.- Response.Close();


En este caso, las líneas 1 y 2 limpian el buffer que se haya llenado con información que no corresponde al archivo. Esto es necesario si en algún momento antes de haber llegado a estas líneas, se hizo algún response.write.


Las líneas 3 y 4 le dicen al navegador del cliente las instrucciones correspondientes al envío de un archivo llamado NOMBRE_DEL_ARCHIVO y del tipo que es. En este caso, se le especifica que es un pdf. Más abajo se desplegarán los tipos más comunes, y un link a una página muy completa con los tipos existentes.


La línea 5 escribe el arreglo de bytes del archivo en el cliente. Posteriormente entregaré una función para sacar un arreglo del bytes de un archivo.


Por último, las líneas 6 y 7 vacían el buffer y cierran el envío al cliente.


Un listado con algunos de los tipos MIME más comunes:


























Texto Plano


text/plain


Imagen gif / jpg


image/gif o image/jpeg


Video Mpeg


video/mpeg


Postscript


application/postscript


Pdf


application/pdf


MS Powerpoint


application/mspowerpoint


MS Excel


application/vnd.ms-excel o application/ms-excel


La URL con muchos de los tipos MIME disponibles es:
http://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html


Patrick
Publicado, Noviembre 2004.
Revisado, Enero 2007.

Cómo instalar Crystal Reports en un servidor web en 6 pasos.


Por favor, no instalar Visual studio en servidores sólo para tener acceso a crystal reports. Esto ya lo he visto hacer muchas veces y no es el camino correcto.[N]


Para instalar Crystal Reports (CR) en un servidor de producción deben se seguir los siguientes pasos. Además, es necesario tener una licencia comprada para que cuando se les solicite el Key, lo ingresen.


La forma de hacer requiere un pequeño truco. Éste consiste en hacer una aplicación web pequeña, que no haga nada en especial, pero que incluya una página con un formulario, y que este formulario incluya un objeto de CR.


Veamos entonces los pasos para lograr la instalación.


1.- Generar la aplicación web simple con una pagina, que haga referencia a los assembies de CR y que tenga un formulario con un objeto.


2.- Crear un proyecto de instalación web dentro de la misma solución donde está el proyecto web simple.


3.- Con el botón derecho del mouse sobre el nuevo proyecto de instalación, seleccionar àAgregar àResultados del proyecto (o en ingles àAdd àWeb Output). En el popup que se les presente, seleccionar el proyecto web en el combobox y de las lista elijan Resultado Principal y Archivos de contenido (en ingles Primary Output y Content Files).


4.- Nuevamente boton derecho del Mouse sobre el proyecto de instalación seleccionar àAgregar àMódulo de combinación (en ingles Merge Module), y seleccionar de la lista de módulos (depende de la versión)


    a.- database_access.msm o Crystal_Database_Access2003.msm (puede haber otro idioma)


    b.- regwiz.msm o Crystal_regwiz2003.msm


    c.- manager.msm o Crystal_Managed2003.msm


5.- Se debe seleccionar ahora el archive regwiz.msm o Crystal_regwiz2003.msm y con el botón derecho llamar a propiedades. En propiedades, expandir el símbolo [+] y donde dice License Key se debe agregar el número de licencia para instalarlo en el servidor. El numero de licencia se puede obtener desde el IDE de Visual Studio, en Ayuda à Acerca de Microsoft Development Environment, seleccionando CR y copiando el texto de la derecha de la línea seleccionada. Es necesario registrar el key en Crystal decisions antes de proseguir. Si han comprado un CR para servidor, el numero de licencia es válido y se puede usar aquí.


6.- Continuar con el proceso de generación del instalador e instalar en el servidor.


Patrick Mac Kay
Octubre 2004.

Arquitectura de aplicaciones y servicios

Después de participar como oradores de una charla de arquitectura de aplicaciones y servicios, mi amigo Luis y yo reflexionamos sobre las preguntas que nos hicieron algunos de los asistentes, y de cómo algunas respuestas eran bastante simples, y en otros casos, no tanto.

 

Como charla introductoria de arquitectura de aplicaciones y servicios, cubrimos al menos las tres capas básicas que debe tener alguna aplicación de mediana o gran envergadura. El diagrama arquitectónico de una aplicación consta de las siguientes capas, componentes y agentes.

 

 

Para un desarrollador novato o con algo de experiencia, que tradicionalmente ha construido aplicaciones monolíticas y aplicaciones que tienen una interfaz y un nivel de aislamiento logrado con un componente, esta separación de capas a primera vista lo llena de confusión.

 

Para simplificar el proceso de aprendizaje de la arquitectura en cuestión, nuestra presentación y la demostración realizada fueron simplificadas a un esquema como el siguiente, complementándolo con la explicación y existencia de las entidades de negocio (omitidas en el diagrama por simplificación). La demostración modificaba una aplicación monolítica hasta dejarla en al menos tres capas.

 

 

Dentro de los temas explicados en la charla, puedo destacar los siguientes:

 

·         Justificación de la existencia de al menos tres capas. Es necesario que cualquier aplicación de mediana o gran envergadura esté separada en las capas de Presentación, Negocio y Datos. Esto es para delimitar responsabilidades entre quien debe realizar ciertas tareas, y por que las capas no deben ni tienen por que saber de cómo y con quien interactúan las otras capas.

 

·         Existencia de las entidades de negocio. Las entidades de negocio representan elementos del mundo real, y que son utilizados por los procesos que componen nuestra aplicación, como por ejemplo ordenes de compra, usuarios o un grupo de permisos.

 

·         Formato de las entidades de negocio. Dentro de las opciones revisadas se presentaron el uso de XML, Datasets tipificados o no y clases desarrolladas en algún lenguaje como C# o VB. Los criterios de definición nos hacen decidirnos teniendo en cuenta necesidades como de ordenación de estas o buscar sobre un grupo, si se requiere hacer bind sobre controles o el nivel de acceso que tendrán (local o remoto – web services). Hay que considerar que no existe un formato único. Cada entidad requiere un estudio propio.

 

·         Comunicación entre las capas. Las distintas capas se comunican en gran medida utilizando entidades de negocio. Hay algunas veces que no es necesario o en definitiva no es posible. Por ejemplo, para realizar eliminaciones no es necesario utilizar una entidad. Con entregar los elementos que identifican el elemento basta. Igual sucede al buscar algún elemento.

 

·         Visibilidad entre las capas. La visibilidad se da desde cualquier capa a la capa inmediatamente inferior. No es posible desde una capa ver a la capa superior ni tampoco saltarse alguna capa hacia abajo y poder acceder a alguna que se encuentre dos o más niveles por abajo, como por ejemplo que interfaz puede acceder a datos directamente. Si bien la propuesta original lo permite, no creo que sea correcto. Esta restricción impuesta generó consultas durante la presentación, la que serán expuestas mas adelante.

 

·         Visibilidad dentro de las capas. Cada capa tiene sus reglas de visibilidad interna. Los casos importantes son la capa de datos y negocio. Es evidente que entre elementos de negocio debe haber comunicación pero no así dentro de los elementos de datos. No es posible que un elemento de datos llame a otro de datos. La razón es simple. La ejecución de algún método de datos que se encuentre dentro de una orquestación realizada desde negocios requiere ciertas validaciones. La orquestación y validación se hace en negocio, por lo tanto,  la capa de datos no puede realizar más acciones que la estrictamente solicitada.

 

·         Ventajas y desventajas de las versiones monolíticas, de dos capas y de tres. Si bien se puede argumentar que desarrollar aplicaciones monolíticas requiere menos tiempo y que en la mayoría de los casos funcionan más rápido, las desventajas que aparecen hacen que no sea factible para aplicaciones de un tamaño importante. En el caso de aplicaciones monolíticas no es posible distribuirla entre varias máquinas, para obtener mejor rendimiento. Las aplicaciones de menos de 3 capas requieren largos y costosos tiempos de mantención o modificación.

 

Algunas de las preguntas que nos llevan a reflexionar sobre la arquitectura están presentadas acá. Las respuestas son equivalentes a las entregadas en la charla, pero se han extendido para su mejor comprensión.

 

Pregunta 1. ¿Si tengo que realizar búsquedas con varios parámetros, pero que no necesariamente se ejecutan en el mismo formulario de UI, donde algunas veces ocupo algunos y en otras ocasiones otros, como debo implementar las funciones de negocio y datos?

 

Respuesta. Estas opciones se nos presentan en la capa de presentación, por lo que debiera ser resuelta en ese mismo lugar. Para eso existe una división en la capa de presentación llamada componentes de proceso de UI (UIPC). La capa de negocios debiera presentar un único método de búsqueda con un set de parámetros fijos o algún parámetro más poderoso parecido a una entidad. La UIPC será la encargada de armar este set de parámetros o esta entidad dependiendo de los datos que posea para esa ejecución específica y posteriormente realizar la llamada a negocio.

 

La capa de negocios no debiera presentar más de una función o método de búsqueda ya que lo que se está realizado, independiente de la cantidad de parámetros, es una ejecución de una regla de negocio (buscar), y que es idéntica en todos los casos. El resto, sería duplicar código y mantenciones, y lo que se busca acá es lo contrario. Pensar en varias funciones de negocio con distintos parámetros de entrada es exactamente de lo que estamos hablando, pero el que debe presentar las funciones es la UIPC, no negocio. Delimitación de responsabilidades.

 

Pregunta 2. ¿Si debo ejecutar un reporte que no es más que una llamada a la base de datos, por que debiera implementar la capa de negocios, si no cumple ninguna funcionalidad?

 

Respuesta. Si bien la pregunta nos puede llevar a pensar que nuestra capa de negocios no es necesaria acá, hecho que justificaría el poder llamar de la interfaz directamente a datos (conversado con anterioridad), debemos fijarnos en el esquema inicialmente presentado y observar las zonas grises de la izquierda. Una de ellas es seguridad. A mi juicio no es posible que la UI acceda a datos sin realizar al menos ciertos chequeos de permisos para poder hacer lo que está tratando de hacer. Como los permisos de ejecución van desde permisos para la comunicación existentes en el Framework hasta la seguridad que hemos desarrollado especialmente para nuestra aplicación, sí se justifica la existencia de la capa e negocios, ya que debe realizar estas validaciones. Preguntémonos, ¿Puede este usuario ver este reporte?, ¿Quién debiera resolver esto?. Delimitación de responsabilidades.

 

Pregunta 3. ¿Qué sucede si necesito que una entidad de negocio se comporte a veces como clase y otras como XML?

 

Respuesta. Una opción a realizar es definirla tanto como clase para que sea utilizada en los casos que es necesario que se comporte así, como también definirla como XML y que se comporte de esa forma cuando sea requisito. Esto nos lleva a contradecirnos en cierta forma. Estamos duplicando código, generando mantención adicional en el futuro y provocando  posibles inconsistencias durante la ejecución de la aplicación. Eso se debe a que es necesario mantener sincronizada ambas versiones. La solución definitiva es implementar en la entidad la opción de serializarse y deserializarse hacia y desde XML. Si bien requiere codificación adicional solucionamos todos los problemas mencionados con anterioridad.

 

La siguiente pregunta no es de arquitectura, pero es bueno incluirla.

 

Pregunta 4. ¿Cual es una buena forma de separar los proyectos?

 

Respuesta. El aplicar la arquitectura no depende de cómo se armen los proyectos. Se puede tener uno o muchos. Todo dependerá de algunos criterios y necesidades. Yo veo tres formas de armar los proyectos o las soluciones. Todas tienen ventajas y desventajas, y dependen de la calidad de los equipos con que se va a desarrollar, del tamaño de la aplicación y de la distribución que se vaya a hacer en producción, es decir, sobre cuantas máquinas correrá mi aplicación.

 

Una opción es tener cuatro proyectos, uno para la UI, otro para Negocio, otro para Datos y el último para Entidades. Las ventajas de esta forma es que si es necesario distribuir en varias máquinas tu aplicación, puedes dejar negocio en una, datos en otra y la UI en otra u otras (granja de servidores). Además tienes menos proyectos y se evitan las posibles relaciones circulares que aparecen en las otras modalidades. Como desventaja, se debe considerar el tamaño de la aplicación. Si ésta crece mucho, los tiempos de compilación pueden llegar a crecer lo suficiente (2 minutos) para ralentizar por completo a un área de desarrollo. Además, si hay muchos desarrolladores con los mismos proyectos, al hacer cambios sobre estos, los demás se ven afectados (incluso usando Source Safe).

 

Una segunda opción es dividir tu aplicación en zonas funcionales o módulos. Para cada módulo tener dos proyectos. Uno con la UI y otro con los tres restantes. Se requiere que idealmente estén separados en carpetas y namespaces como Negocio, Entidad y Datos, y así estar listo para llevar esta modalidad a cualquiera de las otras dos en caso de ser necesaria. La ventaja es que los desarrolladores al trabajar en un módulo tienen mejores tiempos de compilación y no están sujetos a los cambios que puedan hacer los desarrolladores en los otros módulos, o al menos se minimizan los casos. Como desventaja es que no se pueden distribuir en varias máquinas. Puede ocurrir además que un módulo A requiera al B y el B al A también, produciéndose una referencia circular.

 

La tercera opción es una mezcla de la primera y la segunda. Se dividen cada uno de los módulos mencionados en la opción dos en los cuatro proyectos mencionados en la opción uno. Con esta división se tienen las ventajas del primero y del segundo, pero también algunas desventajas. Pueden existir referencias circulares entre los módulos como tener que manejar directa o indirectamente un número muy alto de proyectos. Se minimizan así los tiempos de compilación, pero se agregan tiempos administrativos.

 

 

 

Algunas preguntas que hubiese sido interesante escuchar.

 

Pregunta. ¿Dónde se registra, cómo y quién consume un web service en una aplicación?
Respuesta. Esto lo veremos en la próxima charla.

 

Pregunta. ¿Qué ventajas tengo con esta arquitectura si debo manejar más de un modelo de datos?
Respuesta. Esto lo veremos en la próxima charla.

 

Patrick Mac Kay.
Octubre 2004.

 

Como implementar el método Clone fuertemente tipificado

Para clonar un objeto existente debo implementar en éste, la interfaz ICloneable, la cual posee un único método llamado Clone que debe retornar un tipo object.


El inconveniente principal es que el retorno no es del tipo espcífico (type-safe) sinó que es de tipo objeto. Esto obliga a los clientes de cualquier clase a que deban convertir el resultado al tipo específico, con el consecuente riesgo de errores o fallas en la aplicación.


Si poseo una clase cómo la de más abajo, llamada Usuario y decido clonarla, debo implementar el método Clone(). En el caso de mi clase Usuario, además posee un atributo del tipo de otra clase, llamada Recursos. Tambien es necesario que implemente Clone en las demás clases, sólo si estoy interesado en que se deben clonar esas tambien.


public class Usuario


{


            private string _strNombre;


            private Recursos _objRecursos;

 

            void Usuario(string strNombre, Recursos objRecursos)


            {


                        this._objRecursos = objRecursos;


                        this._strNombre = strNombre;


            }

 

            string nombre { get { return this._strNombre; } set { this._strNombre = value; }}


            Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}

 

}

 

public class Recursos //Implementación omitida por claridad


{


            public Recursos () {}

 

}


Al implementar ICloneable, nuestras clases quedan de la siguiente forma.


El método MemberWiseClone sólo clona internamente todos los tipos que son por valor, y no por referencia. En este caso, Recursos es una referencia en Usuario, por lo que es necesario hacer nosotros la llamada a su método clone.


public class Usuario : ICloneable


{


            private string _strNombre;


            private Recursos _objRecursos;

 

            public Usuario(string strNombre, Recursos objRecursos)


            {


                        this._objRecursos = objRecursos;


                        this._strNombre = strNombre;


            }

 

            string nombre { get { return this._strNombre; } set { this._strNombre = value; }}


            Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}


           


            public object Clone()


            {


                        Usuario _objUsuarioTemp = (Usuario) this.MemberwiseClone();


                        _objUsuarioTemp.recursos =  (Recursos) this.recursos.Clone();

 

                        return _objUsuarioTemp;


            }


}

 

public class Recursos : ICloneable //Implementación omitida por claridad


{

 

            public Recursos () { }

 

            public object Clone()


            {


                        return this.MemberwiseClone();


            }

 

}


El inconveniente que tiene esto es que el resultado del método Clone es un objeto y no es posible cambiar el comportamiento (en esta versión del framework – 1.1 – por lo menos).


¿Qué se puede hacer?


Creamos un método llamado Clone(), pero que no implemente la interfaz sino que haga el llamado a MemberWiseClone(), lo trasforme al tipo de dato específico y luego lo retorne. Además, implementamos la interfaz ICloneable que ejecuta el método que acabamos de crear.


Implementando estos nuevos métodos, el resultado es el siguiente:


public class Usuario : ICloneable


{


            private string _strNombre;


            private Recursos _objRecursos;

 

            public Usuario(string strNombre, Recursos objRecursos)


            {


                        this._objRecursos = objRecursos;


                        this._strNombre = strNombre;


            }

 

            string nombre { get { return this._strNombre; } set { this._strNombre = value; }}


            Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}

 

            object ICloneable.Clone()


            {


                        return this.Clone();


            }


           


            public Usuario Clone()


            {


                        Usuario _objUsuarioTemp = (Usuario) this.MemberwiseClone();


                        _objUsuarioTemp.recursos =  this.recursos.Clone();//No es necesario transformar

 

                        return _objUsuarioTemp;


            }


}

 

public class Recursos : ICloneable //Implementacion omitida por claridad


{

 

            public Recursos ()


            {


            }

 

            object ICloneable.Clone()


            {


                        return this.Clone();


            }

 

            public Recursos Clone()


            {


                        return (Recursos)this.MemberwiseClone();


            }

 

}


El resultado de esto es que, si se trasforma (cast) el objeto a ICloneable, se ejecuta el método de la interfaz, pero si se usa con el tipo específico (objeto tipificado), se utiliza el método público. Véamelo en el ejemplo de más abajo.


Un código de ejemplo en una aplicación de consola, quedaría así.


class Class1


{


            [STAThread]


            static void Main(string[] args)


            {


                        Usuario u1 = new Usuario(“www”, new Recursos());


                        Usuario u2;


                        u2 = u1.Clone(); // No es necesario el cast adicional.

 

                        //Si utilizamos el objeto a través de su interfaz, es necesario realizar el cast adicional.


                        u2 = (Usuario)((ICloneable)u1).Clone();


            }


}


Hemos implementado un método de clonación que retorna tipos específicos en vez de objetos, simplificando el trabajo en los clientes que utilicen estas clases, ya que no son ellos los responsables de realizar la conversión.


Patrick Mac Kay.
Septiembre 2004.

Como impersonar (impersonate) un bloque de código

En algunas oportunidades es necesario ejecutar ciertas tareas o procesos con algún usuario que tenga otros permisos o privilegios distintos al usuario actual con que se está ejecutando el proceso principal o aplicación.


Como práctica realizada de forma habitual, pero que a su vez es muy riesgosa, se ejecutan las aplicaciones con permisos administrativos o de administrador, permitiendo que cualquier tipo de acceso a dispositivos o recursos, se hagan con todos los privilegios.


Una forma de sobrellevar este problema es utilizar impersonalización o suplantación (impersonate) en la ejecución de ciertos procesos como también de funciones o cierto código, es decir, que parte de toda mi aplicación se ejecute con otro usuario y otros permisos. No necesitamos ejecutar toda la aplicación con un permiso, sino que podemos ejecutar sólo algunas líneas con este nuevo usuario.


Veamos como se compone la función y su clase correspondiente para impersonar o suplantar un usuario. La clase no es necesaria crearla si van a implementar esta función dentro de alguna librería de clases de su aplicación.


using System;


using System.Runtime.InteropServices;


using System.Security.Principal;

 

public class Impersonalizacion


{


private const int LOGON32_PROVIDER_DEFAULT = 0;


            private const int LOGON32_LOGON_INTERACTIVE = 2;

 

            [DllImport(“advapi32.dll”, SetLastError=true)]


            public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

 

            [DllImport(“advapi32.dll”, EntryPoint=”DuplicateToken”, ExactSpelling=false, CharSet=CharSet.Auto, SetLastError=true)]


            public static extern int DuplicateToken(IntPtr ExistingTokenHandle, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

 

            public static WindowsImpersonationContext WinLogOn(string strUsuario, string strClave, string strDominio)


            {


                        IntPtr tokenDuplicate = new IntPtr(0);


                        IntPtr tokenHandle = new IntPtr(0);


                        if (LogonUser(strUsuario, strDominio, strClave, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle))


                                   if (DuplicateToken(tokenHandle, 2, ref tokenDuplicate) != 0)


                                               return (new WindowsIdentity(tokenDuplicate)).Impersonate();


                        return null;


            }


}


Si hay cierto código que parece complicado, sólo deben enfocar su atención en una sola función, la llamada WinLogOn. Esta función recibe 3 parámetros, la cuenta de usuario, la clave y el dominio contra el que deberá ser validado el usuario.


Esta función retorna un objeto del tipo System.Security.Principal.WindowsImpersonationContext en caso de ser exitoso el login o null (nothing en VB.NET) en caso de no poder validar el usuario contra el dominio exigido.


Una vez que se tenga el objeto WindowsImpersonationContext, todas las líneas de código que se ejecuten posteriormente, lo harán con los permisos y privilegios de este nuevo usuario, hasta el llamado del método Undo() del objeto de tipo WindowsImpersonationContext. Cuando se llama a Undo(), la aplicación vuelve a correr con el usuario que estaba antes ejecutando la aplicación.


Veamos entonces código donde se muestre la usabilidad de la impersonalización de usuarios. Esta es una aplicación de consola.


class Class1


{


            [STAThread]


            static void Main(string[] args)


            {


                                  


                        WindowsImpersonationContext _objContext = null;

 

                        Console.WriteLine(QuienSoy());


                        _objContext = Impersonalizacion.WinLogOn(“patrick”, “clave”, “tango”);


                        if (_objContext != null)


                        {


                                   Console.WriteLine(QuienSoy());


                                   _objContext.Undo();


                        }


                        Console.WriteLine(QuienSoy());


                        Console.ReadLine();


            }

 

            internal static string QuienSoy()


            {


                        return WindowsIdentity.GetCurrent().Name;


            }


}


NOTA: Es importante garantizar que se produzca el Undo aunque haya alguna excepción de por medio dentro del código que se está impersonando. Si la línea marcada de rojo lanzase una excepción, el código que se ejecute después, que en este caso no son las líneas de más abajo sino que podría ser un catch o un finally de cualquier método que haya llamado a esta función, se hará con los privilegios del usuario específico, y no necesariamente con el usuario que estaba ejecutando la aplicación anteriormente. Esto sucederá por que no se ha realizado el logout o Undo.


Aplicando la corrección el código anterior, éste quedaría:


                        if (_objContext != null)


                        {


                                   try


                                   {


                                                           Console.WriteLine(QuienSoy());


                                   }


                                    finally


                                   {


                                                           _objContext.Undo();


                                   }


                        }


Patrick Mac Kay.
Septiembre 2004.

Como crear una coleccion de clases específicas

A veces hemos necesitado almacenar varios elementos de un tipo no estándar de .NET en alguna colección o arreglo. Hay alternativas que no son las mas ideales, pero que funcionan, como utilizar el objeto ArrayList o definir un arreglo de algún tamaño.Utilizar un ArrayList es muy fácil, pero lo que hace es crear un arreglo de objetos que debemos convertir al tipo necesario. Esta conversión la hace realizando una copia a otro arreglo. Este proceso es lento y consumidor de recursos.Utilizar un arreglo tiene sus ventajas y desventajas. Es evidente que manejar el tipo específico evita tener que hacer conversiones de tipos, pero también definir un arreglo de un tamaño específico nos puede hacer perder recursos (si es mas grande de lo que necesitamos) como también la necesidad de extenderlo si se queda chico.En este artículo veremos como construir una colección de clases específicas. La solución no es la óptima, pero es mejor que las dos anteriores. No es optima por que igual se utiliza “por debajo” una colección de objetos, pero la transformación es mas limpia y menos costosa en recursos. La solución óptima la tendremos en Whidbey, con la inclusión de Generics.

En los ejemplos se utilizará C#, pero la implementación de la colección se hará en tanto en C# como VB.


Veamos el siguiente ejemplo para una clase Documento, que contiene una colección de elementos de tipo Linea. En este caso, la clase específica que queremos convertir en colección es Linea.


Nuestra clase Linea esta definida de la siguiente forma. Tiene una propiedad de texto, con el valor.


public class Linea
{
   
private string _strValor = string.Empty;

    public Linea(string strValor)
    {
       
this._strValor = strValor;
    }
    public string Valor
    {
       
get { return this._strValor; }
    }
}

Para crear una colección de clases específicas, debe crear una nueva clase que herede de System.Collections.CollectionBase y crear los métodos para poder insertar, remover y acceder a los elementos de la colección. La clase base posee un método para remover (RemoveAt) pero que no hace validación de índices al ejecutarse. La clase de colección queda de la siguiente forma.


public class LineaCollection : System.Collections.CollectionBase
{

    public void Add(Linea objLinea)
    {
       
this.List.Add(objLinea);
    }
    public void Remove(int index)
    {
       
if (index <= Count – 1 || index >= 0)
        {
           
this.List.RemoveAt(index);
        }
    }
    public Linea this[int index]
    {
       
get { return ((Linea) (this.List[index])); }
       
set { this.List[index] = value; }
    }
}

¿Como queda nuestra clase Documento?. La propiedad Lineas es de sólo lectura ya que esta entrega la referencia al arreglo, y una vez con la referencia los cambios que se hagan en el arreglo persisten igual.


public class Documento
{
   
private string _strNombre = string.Empty;
   
private LineaCollection _objLineas = null;

    public Documento(string strNombre)
    {
       
this._strNombre = strNombre;
       
this._objLineas = new LineaCollection();
    }
    public LineaCollection Lineas
    {
       
get { return this._objLineas; }
    }
}

En un pequeño ejemplo de uso, creamos un documento, agregamos líneas y después las recorremos con un enumerador.

internal class Demo
{
    public Demo()
    {
        Documento _objDocumento = new Documento(“mi documento de prueba”);
        Linea _objLinea =
null;
        _objDocumento.Lineas.Add(new Linea(“linea 1”));
        _objLinea = new Linea(“Linea 2”);
        _objDocumento.Lineas.Add(_objLinea);
        foreach (Linea _objTemp in _objDocumento.Lineas)
        {
            System.Diagnostics.Debug.Write(_objTemp.Valor);
        }
    }
}

Por último, la misma colección, pero definida en VB.NET queda de la siguiente forma.

Public Class LineaCollection
    Inherits CollectionBase
    Public Sub Add(ByVal objLinea As Linea)
       
Me.List.Add(objLinea)
   
End Sub
    Public Sub Remove(ByVal index As Integer)
       
If index <= Count – 1 OrElse index >= 0 Then
            Me.List.RemoveAt(index)
       
End If
    End Sub
    Public Property Item(ByVal index As Integer) As Linea
       
Get
            Return CType(Me.List.Item(index), Linea)
       
End Get
        Set(ByVal Value As Linea)
           
Me.List.Item(index) = Value
       
End Set
    End Property

End Class

Analizador (profiler) de aplicaciones

Hace algún tiempo leí en uno de esos boletines de noticias, que a los desarrolladores nos gusta coleccionar, pero que rara vez leemos, un MVP de algún país que no recuerdo diciendo que Ants Profiler era una herramienta que todo desarrollador debía utilizar. Como su frase me quedó resonando algunas horas, decidí darle una oportunidad a éste producto y ver de qué se trataba. Ya había tenido algunos acercamientos con productos de los calificados como analizadores o profilers. Mi experiencia no es muy amplia. Sólo conozco dos, el analizador de SQL Server, que por lo general no se usa con toda la utilidad con el que fue concebido y el CLR Profiler, ideal para saber donde y cuanta memoria anda circulando. También se puede monitorear el funcionamiento del GC (Garbage Collector) con éste último. Volviendo a Ants profiler, bajé e instalé el demo disponible en el sitio de Red Gate. Después de algunos minutos ya lo estaba usando (es muy, pero muy simple de usar y configurar). La siguiente es una imagen del producto en pleno funcionamiento.



La idea es simple. Uno ejecuta el analizador, luego ejecuta alguna aplicación o sitio web para analizar y al finalizar la ejecución, te entrega los resultados. La información entregada es directa leer. El resultado se debe tomar como el análisis de alguna función específica de la aplicación que estás monitoreando. Se puede ir navegando de función en función hasta llegar a la de más abajo o a las de nivel superior. En la zona superior se muestra la secuencia de ejecución para llegar a la función que estoy revisando (EstadoDocumentoComprador). Luego nos muestra el tiempo de ejecución de esa función (0,121 segundos). En la zona media un listado de las funciones que llamó la función que se está analizando y por ultimo, aunque no menos importante, el código fuente de la función se estamos monitoreando (EstadoDocumentoComprador).Todo esto lo acompaña también la cantidad de veces que se ejecuta cada función, tiempos promedio, como también mínimo y máximo. Con este nivel de información que podemos obtener de nuestra aplicación, sólo me queda adherirme a la frase del MVP, Ants Profiler es una aplicación que todo desarrollador debe utilizar.


Nota: Al analizar una aplicación web, es necesario que Ants baje y suba el servicio de IIS. Esto deben tenerlo en cuenta para que cuando hagan análisis de éste tipo de aplicaciones, le den tiempo al JIT Compiler de compilar el código la primera vez, para que ésta demora en la ejecución no afecte sus resultados.


Patrick.

Botón "atras" del navegador


El botón “Atrás” o “Back” siempre ha dado problemas. Para los usuarios es muy útil, pero para nosotros (los desarrolladores), un problema no menor que puede hacer tambalear cualquier solución web.


Hasta ahora nunca he visto una solución 100% efectiva (tampoco traigo la panacea), y estudiando el porqué se producen los problemas, la única salida “viable” que se ve es la eliminación definitiva del botón, pero sabemos que eso no va a ocurrir.


Muchas soluciones vistas incluyen abrir una nueva ventana con javascript y esconder los botones. Esta funciona bien hasta que el usuario decide mostrar los botones por que a él le gustan. Los que creen que eliminando los botones evitan su funcionamiento, basan sus creencias en que el usuario no es muy diestro con el navegador, suposición a veces correcta y otras no.


El botón back se puede simular de al menos tres formas que yo conozco.


1.- Presionando el botón derecho del Mouse y eligiendo atrás dentro del menú contextual. Muy simple. Claro, alguien puede suprimir con javascript el hecho de que se pueda presionar el botón derecho, pero ahí coartas la libertad de navegación. Detesto esas páginas.


2.- Con BackSpace o retroceso. Muy simple también. Ahora tendrían que no permitir con javascript el presionar BackSpace, cosa que no tiene lógica.


3.- Haciendo Alt + Flecha Izquierda. Esta se la conocen menos personas. Y es difícil que puedan bloquearla.


¿Que hacer entonces ya que claramente debemos vivir con el problema?


Una solución que he escuchado es hacer tu mapa de navegación del sitio y controlar que el usuario no se salga de este y si aplica back, revisar si se salió y redirigirlo nuevamente a la página. El primer problema viene cuando el usuario tiene configurado el browser para que use la versión del caché y no la del servidor (opción por defecto en IE 5.X). Hasta ahí nomás llega la validación ya que nunca supimos desde el servidor si se salio de la ruta.


¿Que hacemos nosotros en mi empresa?


Aprender a vivir con el problema, es decir, preparando tus páginas a que si el usuario hizo back y tienes información que no debiera estar en esa página (variables sesión, etc), redirigirlo manualmente a otra página, algo así como la opción anterior, pero esta no se dispara cuando hace back sino cuando hace algo después de hacer back.


¿Qué otra opción conozco dentro de las cuales me atrevería a recomendar?


He visto una solución “parche”, pero no tiene ninguna elegancia. Consiste en implementar una sentencia de javascript que haga un forward en la página que no quieres que hagan back. Veamos un ejemplo para que quede mas claro.


Si tienes dos páginas, A y B, y el usuario está en A y luego pasa a B, y no quieres que el usuario de B pueda llegar a A con un back, en A agregas una sentencia en el del tipo .


history.forward();


Esto hará que si el usuario hace back en B, al cargar A lo envía hacia B obligatoriamente. Una gracia que tiene es que al momento de cargar A por primera vez, el script no hace nada y no reclama.


No es una solución ideal, pero te puede salvar en algunos casos.


Patrick Mac Kay
Agosto 2004

Paginación de registros V 1.0



Ayer jueves 5 de agosto, luego de una decepcionante charla que presenciamos junto a unos amigos (Leonardo Garcés y Luís Hereira), decidimos ir a pasar los malos ratos vividos al lugar tradicionalmente utilizado para celebrar, el ShopDog de Pedro de Valdivia con Providencia u 11 de septiembre.


La conversación derivó en variados temas, y después de algunas discusiones bizantinas, caímos en la eterna discusión sobre métodos de paginación. Yo presenté el esquema que actualmente utilizamos en IConstruye. 


Nuestro sistema de paginación funciona de la siguiente forma. Es necesario tener un campo identity y utilizar cache. Una consulta a paginar típica tiene los siguientes elementos


Select
      campos
From
      tablas
Where
      cláusulas


La idea consiste en dividir esta consulta en dos consultas, una que ejecute principalmente la parte del where y la segunda que ejecute principalmente la parte del select y los campos.


Con la primera consulta acotamos a una lista de ids (identitys) de los documentos que cumplen con la restricción where (por eso digo que principalmente ejecuta el where), y almacenamos nuestros N ids en un caché. Si consideramos que cada int pesa 4bytes y almacenamos 500 elementos, tenemos 2kilos (mas algunos bytes de estructura y otros) en un caché. No es mucho. Es mejor que almacenar todo en un caché.


Con la segunda consulta, le pedimos una página cualquiera de x registros. Con esto obtengo los x ids moviéndome sobre el arreglo de enteros almacenado en el cache. Con los ids, ejecuto la segunda consulta, que es mucho mas pesada en el select (y liviana en el where), pero como el resultado son sólo los documentos que quiero, se minimiza bastante el trafico de información que no va a ser considerada después. Traigo así SOLO los x registros de la página solicitada. Para cualquier otra página, ya tengo los ids almacenados en el cache así que no es necesario ejecutar la primera consulta.


Luís menciona que mi sistema está representado por la ecuación Y = m*X + b, es decir una recta con pendiente m y que parte en b. Pueden ver la discusión escrita en la servilleta en la imagen asociada. El costo b es el de la consulta 1 (la de los ids) y por cada pagina X se le pondera por un costo m. Yo argumento que m tiende a cero ya que no hay diferencia significante entre la primera y la última página.



Servilleta de la discusión 


Cuando la discusión se torna inútil nuevamente, Luís propone su método de paginación, obtenido desde http://www.winnetmag.com/Article/ArticleID/40505/40505.html.


Este método se basa en dos consultas, una de ellas anidada en la otra. Un seudo SQL sería algo como


SELECT TOP page_size *
FROM table
WHERE primary_key NOT IN
      (SELECT TOP page_size * (page_number – 1) primary_key FROM table WHERE filter_conditions ORDER BY sort_field)
AND filter_criteria
ORDER BY sort_field


El argumento mío no se deja esperar. Siempre haces dos consultas. Su respuesta, buena en todo caso, es que las primeras páginas son muy rápidas de cargar (por que el select anidado da Top 0 para el primer caso) y que el usuario nunca avanza muchas páginas. Es cierto, pero también ocurre a veces que usuarios llegan hasta altas páginas y ya deja de funcionar bien. En IConstruye utilizamos ese mismo sistema durante un tiempo y habían usuarios que llegaban hasta la página 27.


Cuando la página es muy alta, se pone cada vez mas lento. Este sistema es mas parecido a una exponencial. Verán en la imagen la exponencial y su ecuación ex. Interesante es encontrar el punto donde ex se junta con m*x+b.


¿Cual es mejor?. Dependerá de los requerimientos. Probablemente el sistema perfecto sea una mezcla de ambos, tal como existe el HybridDictionary, que tiene un comportamiento dual dependiendo de la cantidad de elementos. También recuerdo dentro de los algoritmos de ordenamiento, donde el QuickSort era muy rápido, pero para pocos elementos era muy lento. Si se hacia un algoritmo hibrido entre QuickSort y BubbleSort (creo que era el otro), para pocos elementos funcionaba bien el Bubble y para muchos, el Quick.