martes, 28 de febrero de 2012

Criptografía



Navegando por ahí encontré este video que nos explica de una forma sumamente simple cómo es que funciona la criptografía en internet, comenzando con una conexión segura utilizando un algoritmo asimétrico para establecer una llave simétrica.

Los algoritmos de llave asimétrica son los más seguros pero consumen muchos recursos dada la complejidad de su cifrado y descifrado.

Los algoritmos de llave simétrica son los más rápidos pero presentan la problemática de compartir de una forma segura la llave que nos ayudará para cifrar y descifrar.

Espero les sea de mucha utilidad.

viernes, 24 de febrero de 2012

Ícono en control personalizado



Es común que no nos preocupemos por darle una imagen significativa (metáfora visual) a nuestros controles dado que como desarrolladores creemos que son cosas que no interesan mucho, total, funcionan y para eso fueron creados ¿no?

No quiero iniciar una discusión acerca de si es bueno o no, cada quien tiene sus razones y serán tan válidas como sus clientes (otros desarrolladores que utilicen sus controles) acepten las imágenes asociadas a los controles.

Asignarle un ícono diferente al del engrane con el que aparecen de inicio no es complicado, lo único que debemos hacer es:
  1. Generar una imagen de 16x16 en mapa de bits
  2. Guardarle con el mismo nombre de nuestro control, por ejemplo, si nuestro control se llama AsyncSchedule, el archivo deberá llamarse AsyncSchedule.bmp
  3. Agregamos el archivo al proyecto y en sus propiedades (seleccionar archivo en el explorador de soluciones - clic derecho - propiedades) elegimos que es un recurso incrustado.
  4. Recompilamos nuestro ensamblado.
  5. Eliminamos el control de la barra de herramientas y lo volvemos a agregar.
¡Lixto!

En lo personal me gusta que tengan imágenes, le da variedad al contenido de mi barra de herramientas.

martes, 21 de febrero de 2012

Controles extendidos



Controles personalizados es uno de los temas más interesantes y desafiantes de la programación en ASP .NET, todo esto debido a la complejidad y versatilidad que se tiene para poder crearlos.

Podemos dividir los controles personalizados en las siguientes categorías (las pondré en inglés dado que la mayor referencia de información la encontrarán en ese idioma):

1. Web controls.    También llamados "from the scratch", debemos crear cada pedazo de código HTML que conforme a
nuestro control.
2. Composite controls.    Conformado a partir de la agrupación de controles existentes.
3. Inherited controls (extended controls).    Se le agregan o modifican las características funcionales o visuales de controles que ya existen.
4. Templated controls.    Permiten la personalización de los componentes que los conforman... a mi forma de ver, estos son los más complejos.
5. Web user controls.    Recomendados para controles que sólo se utilizarán en un proyecto.

Esta entrada está dedicada a explorar de una forma simple los controles compuestos (composite controls) a través de la creación de un control que nos ayudará a cargar un archivo al servidor sin tener que esperar a que el usuario dé clic en un botón o link en especial para poder enviar el archivo al servidor.

Los controles compuestos son fáciles de identificar dado que heredan de la clase CompositeControl
(System.Web.UI.WebControls)

Vamos a crear un proyecto de tipo "Biblioteca de clases" en Visual Studio y lo llamaremos "MyControls", allí agregamos una clase que llamaremos "ImmediateUploader".

Hacemos referencia al ensamblado System.Web y agregamos las siguientes líneas a nuestro archivo ImmediateUploader.cs:

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

Ahora haremos que nuestra clase ImmediateUploader herede de CompositeControl:

public class ImmediateUploader : CompositeControl

Recordemos que los controles compuestos son aquellos que están formados a partir de controles ya existentes, es decir que toman las características y ventajas que tienen y los agrupan de tal forma de exponer la funcionalidad deseada.

En nuestro caso queremos mostrar un control donde el usuario pueda elegir un archivo de su computadora e inmediatamente después de darle clic al botón "Abrir" u "Ok" comience la carga del archivo.

De primera vista es fácil identificar que necesitaremos un control FileUpload pero para enviar el post de la página con el archivo seleccionado utilizaremos un botón oculto el cual disparará el evento deseado.

En los controles personalizados existe un método llamado CreateChildControls el cual es llamado cuando se necesita que el control genere la jerarquía de controles que lo conformarán.    Dado que CompositeControl viene vacío, aprovecharemos este método para agregar los controles FileUpload y Button:

protected override void CreateChildControls()
{
  FileUpload fu = new FileUpload();
  fu.Width = this.Width;
  fu.ID = "FileUpload";
 
  Button upload = new Button();
  upload.Style.Add(HtmlTextWriterStyle.Display, "none");
  upload.Click += new EventHandler(upload_Click);
  upload.ID = "Upload";
 
  Controls.Add(fu);
  Controls.Add(upload);
 
  fu.Attributes.Add("onchange", "raiseUploadClick_" + this.ClientID + "('" + upload.ClientID + "')");
}

Notemos lo siguiente:

1. Estamos ocultando el botón al asignarle a su estilo un "display" un valor de "none".
2. Estamos agregando el manejador del evento Click del botón para enterarnos del momento en que se realiza el POST por parte del navegador.
3. Estamos agregando un atributo "onchange" a nuestro control FileUpload para que nos avise a través de una función javascript que el usuario seleccionó un archivo.

Para avisarle a la página (donde se estará utilizando nuestro control) que se ha elegido un archivo, nuestro control lanzará un evento llamado FileUploaded.    Para lograrlo escribiremos el siguiente código:

public delegate void FileUploadedHandler(object sender, HttpPostedFile postedFile);
public event FileUploadedHandler FileUploaded;
void OnFileUploaded(HttpPostedFile postedFile)
{
  FileUploadedHandler handler = FileUploaded;
  if (handler != null)
    handler(this, postedFile);
}
void upload_Click(object sender, EventArgs e)
{
  if ((FindControl("FileUpload") as FileUpload).HasFile)
  {
    OnFileUploaded((FindControl("FileUpload") as FileUpload).PostedFile);
  }
}

Por último, vamos a modificar la forma en que nuestro control realizará el Render de su estructura:

protected override void Render(HtmlTextWriter writer)
{
  base.Render(writer);
  StringBuilder sb = new StringBuilder();
  sb.Append("<script type=\"text/javascript\">");
  sb.Append("function raiseUploadClick_" + this.ClientID + "(b){");
  sb.Append("document.getElementById(b).click()");
  sb.Append("}");
  sb.Append("</script>");
  writer.Write(sb.ToString());
}

La llamada base.Render hace que cada uno de los controles que se encuentran en la propiedad Controls de nuestro control compuesto sea traducido al HTML que le corresponde y escrito en el HtmlTextWriter.

Podemos ver que después de la llamada a base.Render estamos escribiendo una función de javascript para que se lance el post de nuestra página a través de la llamada del evento click del botón oculto.

Compilemos nuestro proyecto (Shift + Ctrl + B) y para probarlo haremos los siguiente:

1. Agregaremos un sitio web a nuestra solución (File - Add - New Web Site)
2. Abrimos en modo diseño nuestra página Default.aspx
3. Desplegamos la barra de herramientas y vamos a ver que aparece un control llamado ImmediateUploader, lo arrastramos a nuestra página.

Ejecutemos nuestro sitio web y veremos que al elegir un archivo y abrirlo se dispara el POST de la página.

Cerremos el navegador y finalicemos nuestro ejercicio cachando el evento y agregando metadatos a nuestro control.

Abrimos a nuestra clase ImmediateUploader.cs y agregamos el atributo "DefaultEvent" a nuestra clase de la siguiente forma:

[DefaultEvent("FileUploaded")]
public class ImmediateUploader : CompositeControl

Y también agregaremos un atributo "Description" a nuestro evento de forma tal que el programador que utilice nuestro control entienda para qué sirve o cuándo se dispara nuestro evento:

[Description("Raised when end user select a file from its computer")]
public event FileUploadedHandler FileUploaded;

Compilamos de nuevo el proyecto (Shift + Ctrl + B) y abrimos "Default.aspx" en vista diseño para dar doble clic sobre nuestro control ImmediateUploader.    Veremos que nos crea un procedimiento (Default.aspx.cs) que estará ejecutándose cuando el evento FileUploaded de
nuestro control se haya disparado:

protected void ImmediateUploader1_FileUploaded(object sender, HttpPostedFile postedFile)
{
}

¿Qué podemos hacer con postedFile?, depende del objetivo del control en la página, eso queda a su criterio dependiendo de las necesidades u objetivos de su página.

sábado, 18 de febrero de 2012

Volver a crear y sp_recompile



4. Recrear el procedimiento almacenado.

Otra opción que tenemos para obligar a que el plan de ejecución vuelva a calcularse es actualizar o volver a crear el procedimiento almacenado.

No me gustaría plantear estas dos opciones como posibles soluciones al problema de la actualización del plan de ejecución, y esto es dado que no siempre tendremos acceso al
código fuente de los objetos que componen a la base de datos.

Supongamos que estamos trabajando en la optimización de acceso a datos de una empresa de administración de inversiones y muchos de sus procedimientos fueron creados con la
opción WITH ENCRYPTION para proteger su código fuente, es muy probable (o seguro) que no te den acceso al código fuente de los procedimientos dado que podrían tener información
confidencial.

Más que como solución, deberemos entender que esto es una implicación, deberemos aplicar los cambios a los objetos cuando el sistema o el servidor se encuentre en una ventana de
mantenimiento y así afectar en lo mínimo el tiempo de respuesta del servidor.

5. Ejecutar el procedimiento almacenado sp_recompile

Este procedimiento recibe como parámetro el nombre de un objeto (no solamente de un procedimiento almacenado).

Al ejecutarlo con el nombre de un procedimiento almacenado como parámetro, estaremos indicando que su plan de ejecución vuelva a calcularse.

Pero la utilidad de este procedimiento va mucho más allá.    Supongamos que hicimos cambios o mejoras en la estructura de almacenamiento u ordenamiento de una tabla en
particular, si queremos que todos los objetos "precompilados" vuelvan a calcular su plan de ejecución para que tomen en cuenta las recientes modificaciones en la estructura de
nuestra tabla, basta con ejecutar el procedimiento sp_recompile pasándole como parámetro el nombre de nuestra tabla, veamos un ejemplo.

Supongamos que tenemos la siguiente estructura de información:

CREATE TABLE Students(
  Id SMALLINT IDENTITY(1, 1) NOT NULL,
  FirstName VARCHAR(30) NOT NULL,
  LastName VARCHAR(30) NOT NULL,
  DOB DATE NOT NULL,
  Notes VARCHAR(72) NOT NULL,
  RegistrationDate DATE NOT NULL,
  CONSTRAINT pkStudents PRIMARY KEY (Id)
)

Y la llenamos con datos:

DECLARE @FirstName VARCHAR(30)
DECLARE @LastName VARCHAR(30)
DECLARE @RegistrationDate DATE
DECLARE @DOB DATE
DECLARE @Notes VARCHAR(64)
DECLARE @i SMALLINT = 0
DECLARE @Length INT
WHILE @i < 10000
BEGIN
  SET @Length = ABS(CHECKSUM(NEWID()) % 8) + 1
  SET @FirstName = SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, @Length)
  SET @Length = ABS(CHECKSUM(NEWID()) % 8) + 1
  SET @LastName = SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, @Length)
  SET @Length = ABS(CHECKSUM(NEWID()) % 80)
  SET @RegistrationDate = DATEADD(DAY, @Length, '2012-02-01')
  SET @Length = ABS(CHECKSUM(NEWID()) % 800)
  SET @DOB = DATEADD(DAY, @Length, '1980-01-01')
  SET @Notes = CAST(NEWID() AS CHAR(36)) + CAST(NEWID() AS CHAR(36))
 
  INSERT INTO Students (FirstName, LastName, DOB, Notes, RegistrationDate)
  VALUES (@FirstName, @LastName, @DOB, @Notes, @RegistrationDate)
 
  SET @i = @i + 1
END

Creamos nuestro procedimiento almacenado y lo ejecutamos:

CREATE PROC pGetStudentsByDate(
  @RegistrationDate DATE
)
AS
BEGIN
  SELECT Id, FirstName, LastName
  FROM Students
  WHERE RegistrationDate = @RegistrationDate
END
GO
pGetStudentsByDate '2012-03-24'

Ahora veamos el plan de ejecución de nuestro procedimiento, podemos abrir un nuevo editor para queries y escribimos el código para ejecutar nuestro procedimiento almacenado,
damos clic derecho sobre el editor y elegimos la opción "Display Estimated Execution Time".    Veremos que nuestro procedimiento almacenado está llevando a cabo un barrido
completo de la tabla a través del índice pkStudents.

Vamos a crear un índice sobre nuestra tabla para optimizar la búsqueda de alumnos por fecha de registro:

CREATE NONCLUSTERED INDEX ixStudents_RegistrationDate
ON Students (RegistrationDate)
INCLUDE (FirstName, LastName)

Si volvemos a mostrar el plan de ejecución de nuestro procedimiento, veremos que no ha cambiado; entonces utilizaremos sp_recompile para obligar al cálculo de un nuevo plan de
ejecución, pero lo haremos sobre la tabla para demostrar que también funciona de esa forma:

sp_recompile 'dbo.Students'

Si volvemos a nuestra ventana del query y mostramos el plan de ejecución de nuestro procedimiento almacenado, veremos que ahora está utilizando nuestro índice (seek) para obtener los registros deseados (ixStudents_Registration)

Espero les sea de mucha utilidad.

jueves, 16 de febrero de 2012

WITH RECOMPILE y FREEPROCCACHE



Continuaremos viendo las opciones que tenemos para forzar la compilación de un procedimiento almacenado, y tomando la lista de la entrada anterior (RECOMPILE o no RECOMPILE) comenzaremos con la opción WITH RECOMPILE durante la ejecución de un procedimiento.

2. Ejecutar el procedimiento almacenado con la opción RECOMPILE.

Una vez que ya tenemos el plan de ejecución de nuestro procedimiento almacenado, éste no se calculará de nuevo de forma automática.

¿Por qué nos interesaría forzar la compilación de nuestro procedimiento?

Supongamos que después de un proceso de análisis en el uso de la información, se deberminó crear una vista indexada que tomara las columnas Name y City de nuestra tabla Customers (clic aquí para ver el código para la creación de los elementos) pero como nuestro plan de ejecución ya está calculado, éste jamás tomará en cuenta nuestra nueva estructura de datos.

Para generar la vista indexada que cubra la propuesta del párrafo anterior podemos hacer lo siguiente:

CREATE VIEW vCustomers
WITH SCHEMABINDING
AS
      SELECT Id, Name, City
      FROM dbo.Customers
GO
CREATE UNIQUE CLUSTERED INDEX vCustomers_Id
ON vCustomers (Id)
GO
CREATE INDEX ixVCustomers_City
ON vCustomers (City)
Ejecutemos el código que modifica nuestro procedimiento almacenado para que no tenga el modificador RECOMPILE:
ALTER PROC pFindCustomers(
      @Column VARCHAR(30),
      @Condition VARCHAR(10)
)
AS
BEGIN
      IF @Column = 'City'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE City = @Condition
      END
      ELSE IF @Column = 'RegistrationDate'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE RegistrationDate = @Condition
      END
END

Si ejecutamos una búsqueda por fecha de registro y posteriormente vemos el plan de ejecución de la búsqueda por ciudad, vamos a ver que es el mismo.

Bien, en una nueva ventana del Management Studio vamos a escribir la siguiente sentencia, damos clic derecho sobre el editor y después elegimos “Display estimated execution plan”:

pFindCustomers 'City', '0014' WITH RECOMPILE

Veremos que con ahora ya se estará haciendo uso de nuestra vista indexada

3. Ejecutar DBCC FREEPROCCACHE

La ejecución de esta sentencia eliminará todos los planes de ejecución que hayan sido almacenados obligando a que se vuelva a calcular cada uno de ellos conforme se vayan necesitando.

Dado que cada query será “recalculado”, veremos una degradación importante en el rendimiento de nuestro gestor de bases de datos por lo que recomiendo que se use muy poco.

¿Cuándo lo utilizaría yo?, después de aplicar un “service pack” a la base de datos con mejoras de rendimiento y modificación de estructuras de almacenamiento.

En la siguiente entrada abordaremos los puntos 4 y 5 de la lista.

Comenzando en Blogger


Desde hace tiempo tenía la idea de comenzar un blog que abordara temas de desarrollo web y bases de datos pero entre el trabajo y la flojera inherente al tiempo de descanso simplemente no había podido aterrizar este pequeño y sencillo proyecto.

Creo que ahora es el momento, para darle sentido a esta vida uno debe de cumplir hasta con los objetivos más sencillos que se haya planteado y esa es la razón que me ha impulsado a comenzar con este blog.

Sería obtuso pensar que un comentario emitido será aceptado por todas las personas que lo lean, y teniendo eso en cuenta, no busco el crear empatía con el visitante, simplemente compartir un poco de conocimiento, generar diferencia de opiniones y así enriquecer el contenido de este blog.

¡Bienvenido!... ¡y que viva México!

miércoles, 15 de febrero de 2012

RECOMPILE o no RECOMPILE



La ventaja de utilizar procedimientos almacenados es que el proceso de la planeación de la ejecución del código que lo compone se realiza una sola vez (durante su primera ejecución) y el plan es almacenado para futuras referencias.

El proceso de planear la ejecución de un query es un tanto lento dado que se deben de explorar las estructuras de datos que existen alrededor de la información que se desea consultar, sean las tablas mismas, índices clustered, índices nonclustered, índices de cobertura, vistas indexadas, estadísticas, etc.

Es importante tomar en cuenta que el plan de ejecución habrá sido calculado según la cantidad de información y las estructuras previamente mencionadas al momento de su ejecución.

Dependiendo de la estructura o de la lógica de nuestro procedimiento almacenado deberemos estudiar la posibilidad de obligar a que el plan de ejecución sea recalculado.

Para lograr este objetivo existen muchas opciones:

1. Crear el procedimiento almacenado con la opción RECOMPILE.
2. Ejecutar el procedimiento almacenado con  la opción RECOMPILE.
3. Ejecutar DBCC FREEPROCCACHE.
4. Recrear el procedimiento almacenado.
5. Usar el procedimiento almacenado sp_recompile.

En esta entrada del blog abordaré el punto 1, en unos días terminaré de comentar los siguientes puntos.

1. Crear procedimiento almacenado con la opción RECOMPILE.

Es probable que muchas personas entren en conflicto con esta opción dado que uno de los objetivos principales de los procedimientos almacenados es que su plan de ejecución quede almacenado y así nos evitemos la tarea de estar recalculando el plan de ejecución cada vez que se haga uso de ellos.

Para aterrizar esta idea, utilizaré la siguiente estructura:

CREATE TABLE Customers(
      Id INT IDENTITY(1, 1) NOT NULL,
      Name VARCHAR(80) NOT NULL,
      City VARCHAR(4) NOT NULL,
      RegistrationDate DATE NOT NULL,
      CONSTRAINT pkCustomers PRIMARY KEY (Id)
)
GO
CREATE NONCLUSTERED INDEX ixCustomers_City
ON Customers (RegistrationDate)
La llenaremos con datos ficticios a través del siguiente query:
DECLARE @i INT = 0
DECLARE @Name VARCHAR(80)
DECLARE @City VARCHAR(4)
DECLARE @RegistrationDate DATE
WHILE @i < 30000
BEGIN
      SET @Name = REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', '')
      SET @City = SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, 4)
      SET @RegistrationDate = DATEADD(DAY, CHECKSUM(NEWID()) % 15, GETDATE())
      INSERT INTO Customers (Name, City, RegistrationDate)
      VALUES (@Name, @City, @RegistrationDate)
      SET @i = @i + 1
END

Supongamos que nuestro procedimiento almacenado recibe como parámetro la columna bajo la cual se realizará una búsqueda de información.

CREATE PROC pFindCustomers(
      @Column VARCHAR(30),
      @Condition VARCHAR(10)
)
AS
BEGIN
      IF @Column = 'City'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE City = @Condition
      END
      ELSE IF @Column = 'RegistrationDate'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE RegistrationDate = @Condition
      END
END

Ejecutemos nuestro procedimiento almacenado para buscar a los clientes de la ciudad “0014” (busquen alguna ciudad que exista en los registros de la tabla para que al menos vean resultados)

pFindCustomers 'City', '0014'

Ahora, exploremos los queries que han sido ejecutados para encontrar el plan de ejecución almacenado.

SELECT C.plan_handle, T.text, P.query_plan
FROM sys.dm_exec_cached_plans C
CROSS APPLY sys.dm_exec_sql_text(C.plan_handle) T
CROSS APPLY sys.dm_exec_query_plan(C.plan_handle) P

Buscaremos en la columna “text” algo que comience con “CREATE PROC pFindCustomers …” y daremos clic en la celda de la columna query_plan.

Sólo demos un vistazo superficial al plan de ejecución para verificar algunas tareas que está llevando a cabo: ixCustomers_City (seek) y pkCustomers (scan)

Ahora en una nueva ventana del Management Studio escribimos lo siguiente:

pFindCustomers 'RegistrationDate', '2012-02-07'

Demos clic derecho y elegimos la opción “Display estimated execution plan” y veremos que el plan es el mismo que habíamos visto antes, entonces sin importar que tengamos una cantidad enorme de posibilidades de búsqueda, siempre se utilizará el mismo plan de ejecución el cual sólo era óptimo para la primera ejecución.

Para este tipo de problemas, podemos modificar nuestro procedimiento almacenado para que cada vez que se ejecute se recalcule el plan de ejecución:

ALTER PROC pFindCustomers(
      @Column VARCHAR(30),
      @Condition VARCHAR(10)
)
WITH RECOMPILE
AS
BEGIN ...

He colocado puntos suspensivos dado que lo demás sigue igual.

En una nueva ventana escribimos el query para buscar por ciudad y elegimos la opción “Display Estimated Execution Plan”, veremos que es el mismo plan que ya habíamos visto previamente.

Ahora volvamos a la ventana donde está nuestro query para la búsqueda por “RegistrationDate” y elegimos de nuevo la opción “Display Estimated Execution Plan”… ¡zaz!, podemos ver que el plan de ejecución es diferente y está optimizado para la búsqueda que ejecutamos.

En la siguiente entrada veremos los siguientes puntos de la lista.

martes, 14 de febrero de 2012

Fuentes de información


Dicen que la información es poder, y tener buenas fuentes creo que es una herramienta básica para mantenernos al día, o lo más cercanamente posible a ello.
A continuación presento una lista de las páginas donde usualmente ando navegando para estar al pendiente de las tendencias de los temas que me interesan (y de vez en cuando de los que no)
  1. www.techrepublic.com
  2. www.techworld.com
  3. www.pcnews.com
  4. www.diarioti.com
Ahora que si el objetivo es aprender pues creo que www.experts-exchange.com es una excelente opción, éste es un sitio donde las empresas o algunos usuarios pagan por poder publicar sus preguntas o problemas y los expertos se pelean por ver quién da la solución.
Seguramente pensarán, ¿por qué aprender solucionando problemas de los demás?, por el simple hecho de que al plasmar una idea, los demás expertos te dirán por qué sí o por qué no es una buena opción tu opinión... y a través de ello podrás tener una retroalimentación enorme.
Y bueno, dado que toda mi vida profesional he trabajado con tecnologías Microsoft, estoy plenamente convencido de que la documentación en línea es una excelente fuente de información, lo que es el MSDN.
Quisiera terminar esta entrada con un comentario que alguna vez me dijo una persona a la que respeto y admiro mucho: "la mejor tecnología, es la que uno domina"