jueves, mayo 31, 2007

TDD (El test antes del código) (y 3)

Cogemos el test 1: Dado un -1 (ejemplo de entero negativo) y 2 (ejemplo de entero positivo), obtenemos una excepción (cumplimos la regla de negocio 2)

Codificamos el test:

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;


[TestFixture]
public class CalculadoraTest
{
[Test, ExpectedException()]
public void Calculadora_Test_001()
{
Calculadora calc = new Calculadora();
calc.sumar(-1, 1);
}
}




Y codificamos lo mínimo para que funcione el test (que funcione no significa que pase):

public int sumar(int a, int b)
{
return 0;
}
}


El tets falla. Evidente, no? Esperabamos una excepción y hemos recibido un 0. Rehacemos el código hasta que pase el test.

public int sumar(int a, int b)
{
if (a < 0)
throw new Exception();
return 0;
}


Ahora pasa. Perfecto. Cogemos el segundo test.

Dado un 1 (ejemplo de entero positivo) y un -1 (ejemplo de entero negativo), obtenemos una excepción (cumplimos la regla de negocio 2)

Codificamos el test, comprobamos que falla…

[Test, ExpectedException()]
public void Calculadora_Test_002()
{
Calculadora calc = new Calculadora();
calc.sumar(1, -1);
}


Y cambiamos el código para que también soporte este caso.

public int sumar(int a, int b)
{
if (a < 0 b <0)
throw new Exception();
return 0;
}


Ahora pasa. Genial, pero no hemos acabado. Lanzamos de nuevo TODOS los tests para ver que el cambio no rompe algo que funcionaba.
¿Funcionan? Si, genial. Antes de ir al siguiente código, miramos a ver si se puede refactorizar algo…
¿Crees que aún es pronto? ¿Qué hay pocas líneas? Jaja Vemos que en los dos tests que hay hasta ahora, creamos un objeto Calculadora. Podríamos sacarlo de allí y meterlo a nivel general.
El código quedaría así:

[TestFixture]
public class CalculadoraTest
{
Calculadora calc;

[TestFixtureSetUp]
public void Init()
{
calc = new Calculadora();
}

[Test, ExpectedException()]
public void Calculadora_Test_001()
{
calc.sumar(-1, 1);
}

[Test, ExpectedException()]
public void Calculadora_Test_002()
{

calc.sumar(1, -1);
}
}


Cogemos el test 3: Dado un 1 (ejemplo de entero positivo) y un 3 (ejemplo de entero positivo), obtenemos un 4 (cumplimos la regla de negocio 1 y la 3). Repetimos los pasos anteriores…

Y falla… Bueno, corregimos el código para que pase (OJO a la jugada). Vamos a hacer que siempre devuelva un 4 (salvo que alguno sea negativo).

El test queda:

[Test]
public void Calculadora_Test_003()
{
Assert.IsTrue(calc.sumar(1, 3) == 4);
}


Y el código queda:
public int sumar(int a, int b)
{
if (a < 0 b <0)
throw new Exception();
return 4;
}



Parece que la jugada no tiene sentido, pero paciencia. De momento, no hemos metido ni una función demás en el código. Hace lo que le piden los test. Ni más, ni menos.

Cogemos el 4º test. Dado un 0 (ejemplo de frontera) y un 1 (ejemplo de entero positivo), obtenemos un 1 (cumplimos la regla de negocio 1 y la 3). Repetimos el proceso.
Ahora hacemos que devuelva siempre 2.

public int sumar(int a, int b)
{
if (a < 0 b <0)
throw new Exception();
return 2;
}


El test pasa pero si ejecutamos todos no. Es ahora cuando metemos el a + b.


Y pasan todos los test… genial.

Y así sucesivamente hasta el test 7 (incluido). Pasar estos tests no requerirá cambios en el código de la calculadora, pero el 8º sí.

Dado un 10001 (ejemplo de frontera numero grande) y 1 (ejemplo de entero positivo), obtenemos una excepción (cumplimos la regla de negocio 4). Esto requiere que metamos otra rama en los primeros IF.

Y así sucesivamente… nuevo test, que pase, refactorizamos, nuevo test, que pase, refactorizamos, nuevo test, que pase, refactorizamos, …

Al final nos queda la clase de test así:


[TestFixture]
public class CalculadoraTest
{
Calculadora calc;

[TestFixtureSetUp]
public void Init()
{
calc = new Calculadora();
}

[Test, ExpectedException()]
public void Calculadora_Test_001()
{
calc.sumar(-1, 1);
}

[Test, ExpectedException()]
public void Calculadora_Test_002()
{
calc.sumar(1, -1);
}

[Test]
public void Calculadora_Test_003()
{
Assert.IsTrue(calc.sumar(1, 3) == 4);
}

[Test]
public void Calculadora_Test_004()
{
Assert.IsTrue(calc.sumar(1, 1) == 2);
}

[Test]
public void Calculadora_Test_005()
{
Assert.IsTrue(calc.sumar(1, 0) == 1);
}

[Test]
public void Calculadora_Test_006()
{
Assert.IsTrue(calc.sumar(10000, 1) == 10001);
}

[Test]
public void Calculadora_Test_007()
{
Assert.IsTrue(calc.sumar(1, 10000) == 10001);
}

[Test, ExpectedException()]
public void Calculadora_Test_008()
{
calc.sumar(10001, 1);
}

[Test, ExpectedException()]
public void Calculadora_Test_009()
{
calc.sumar(1, 10001);
}

[Test]
public void Calculadora_Test_010()
{
Assert.IsTrue(calc.sumar(9999, 1) == 10000);
}

[Test]
public void Calculadora_Test_011()
{
Assert.IsTrue(calc.sumar(1, 9999) == 10000);
}
}


Y el código…

public int sumar(int a, int b)
{
if (a <> 10000 a > 10000)
throw new Exception();
return a + b;
}



Curioso que hayamos dedicado más de 50 líneas para testear 3, no? Puede parecer poco rentable, pero AHORA SABEMOS QUE EL CÓDIGO FUNCIONA Y CUMPLE LAS REGLAS DE NEGOCIO. A PARTIR DE AHORA, SI AÑADIMOS MÁS CÓDIGO, SABREMOS QUE EL FALLO ES DE LO QUE VAYAMOS AÑADIENDO, PORQUE EL CÓDIGO DE A SUMA, ESTÁ PROBADO.

Un saludo

TDD (El test antes del código) (2)

Vamos a hacer un ejemplo sencillo. Una calculadora que suma y resta.
Primero, analizamos las reglas de negocio.
1- Dados dos numeros, debe sumarlos o restarlos.
2- Los numeros deben ser enteros y positivos.
3- El resultado debe ser también entero y positivo
4- La entrada no puede tener ningún valor mayor que 10000

Segundo. La lista de test. Nos tomamos nuestro tiempo para hacerla.
Vamos a verificar también que cumplimos las reglas de negocio.

Antes de hacer la lista, es conveniente hacer una lista de valores frontera que pueden ser causantes de problemas o, por una u otra razón, tienen alguna importancia.

En este caso, los valores frontera serían: 0 (por ser el límite entre positivos y negativos, que mencionan las reglas de negocio 2 y 3), 10000 (para comprobar la regla de negocio 4) y -1 y 1, por ser el anterior y el posterior al valor frontera 0 y 10001 y 9999, por ser el anterior y el posterior al valor frontera 10000.

Analizamos también que facetas de IDE de Visual Studio hacen que no tengamos que testear algunas reglas. Por ejemplo, no deberemos testear que pasaría si metemos un número decimal, pues el método SUMA no admitirá esos valores y fallará por sí mismo.

Lista de Test para la SUMA
(1) Dado un -1 (ejemplo de entero negativo) y 2 (ejemplo de entero positivo), obtenemos una excepción (cumplimos la regla de negocio 2)
(2) Dado un 1 (ejemplo de entero positivo) y un -1 (ejemplo de entero negativo), obtenemos una excepción (cumplimos la regla de negocio 2)
(3) Dado un 1 (ejemplo de entero positivo) y un 3 (ejemplo de entero positivo), obtenemos un 4 (cumplimos la regla de negocio 1 y la 3)
(4) Dado un 0 (ejemplo de frontera) y un 1 (ejemplo de entero positivo), obtenemos un 1 (cumplimos la regla de negocio 1 y la 3)
(5) Dado un 1 (ejemplo de entero positivo) y un 0 (ejemplo de frontera), obtenemos un 1 (cumplimos la regla de negocio 1 y la 3)
(6) Dado un 10000 (ejemplo de numero grande) y 1 (ejemplo de entero positivo), obtenemos 10001 (cumplimos la regla de negocio 1 y la 3)
(7) Dado un 1 (ejemplo de entero positivo) y 10000 (ejemplo de numero grande), obtenemos 10001 (cumplimos la regla de negocio 1 y la 3)
(8) Dado un 10001 (ejemplo de frontera numero grande) y 1 (ejemplo de entero positivo), obtenemos una excepción (cumplimos la regla de negocio 4)
(9) Dado 1 (ejemplo de entero positivo) y un 10001 (ejemplo de frontera de numero grande), obtenemos una excepción (cumplimos la regla de negocio 4)
(10) Dado un 1 (ejemplo de entero positivo) y 9999(ejemplo de frontera de numero grande), obtenemos 10000 (cumplimos la regla de negocio 1 y la 3)
(11) Dado un 9999(ejemplo de frontera de numero grande) y un 1 (ejemplo de entero positivo, obtenemos 10000 (cumplimos la regla de negocio 1 y la 3)

No cerramos la lista, porque si encontramos algún otro test, lo añadiremos.

A programar!!!!!

TDD (El test antes del código) (1)

¿Qué es TDD?

TDD son las siglas de Test Driven Development (desarrollo guiado por pruebas), que no es más que una manera de tirar código basandonos en los resultados que debe dar.
Es una de las técnicas del eXtremme Programming, y tiene bastantes ventajas e inconvenientes.
Las ventajas:
El código es más fiable al estar más sometido a tests que el código normal.
Se desarrolla el código justo para que funcione. No se tiran lineas que no aporten valor.
Ahorramos tiempo en fases posteriores a la de build.

Los inconvenientes
La lista de tests con la que contamos debe ser lo más completa posible. Eso requiere conocer muy bien las reglas de negocio.
Hay que tener una cierta experiencia a la hora de desarrollar, que permita completar las (casi siempre) incompletas listas de tests.
Alarga la fase de build (acorta las posteriores), lo que no siempre se entiende en los proyectos.

El proceso para desarrollar con TDD sería:
(1) Analizar las reglas de negocio.
(2) Obtener una primera lista de tests (mala señal si es la definitiva) con lo que deberíamos obtener ante una serie de datos de entrada.
(3) Codificamos el primer test, lo lanzamos y comprobamos que el test falla.
(4) Codificamos lo justo para que el test pase y lo comprobamos.
(5) Pasamos al siguiente test.
(6) Lo codificamos, lo lanzamos y comprobamos que falla.
(7) Codificamos lo justo para pasar este test y comprobamos que pasa.
(8) Comprobamos que pasan TODOS los tests hasta ahora, por si hemos roto algo.
(9) Refactorizamos el código (en el test y el código)
(10) Volvemos a 5

Importante: si creemos que son necesarios más tests, porque hay situaciones que no hemos comprobado, añadimos los tests. No hay que tener miedo a corregir, sino a pensar que está bien algo que no lo está.

miércoles, mayo 30, 2007

SQL Server 2005 y el tipo de datos XML: Configuración del esquema

Escenario inicial

Tenemos una tabla donde una (o más) de las columnas son de tipo XML
Queremos crear un esquema que represente el contenido de esa columna para acelerar las queries que vayan contra esa columna.

Procedimiento

Cogemos un XML que sea representativo, que tenga todos los campos que vamos a necesitar.
Guardamos el fichero en disco.
Usando la aplicación XSD (Visual Studio Tools a Visual Studio 2005 Command Prompt) ejecutamos el comando XSD [nombre_fichero.xml]

Nota: es posible que de fallos porque haya caracteres extraños. Hay que quitar las Ñ, %, $, €, …

Abro el fichero que se ha generado (misma ruta y nombre que el origen, pero con XSD de extensión) con el Notepad.
El contenido es el esquema del XML elegido.
Voy al SQL Server 2005 y hago una nueva query:

IF EXISTS (SELECT *
FROM sys.xml_schema_collections
WHERE name = N'mySchema')
DROP XML SCHEMA COLLECTION mySchema
GO

CREATE XML SCHEMA COLLECTION mySchema
AS
'
CONTENIDO DEL FICHERO XSD
'
GO


Donde pone CONTENIDO DEL FICHERO XSD, meto el contenido del XSD.

Ejecuto la query. Si todo ha ido bien, se habrá añadido a la lista de esquemas de SQL Server.
Voy a la tabla, con el fin de modificarla. Voy a la columna XML y en propiedades, configuro la sección de XML.

Elijo el esquema que acabo de crear, del combo de Schema Collection y configuro Is XML Document como Yes.

Nota: si no aparece es posible que haya fallado el refresco. Cerramos la pestaña y la volvemos a abrir.

Damos a guardar los cambios de la tabla. Si todos los XML que estén presentes (si los hay) concuerdan con el esquema, no habrá problema y se guardará. Si alguno no concuerda, habra que hacer modificaciones en el esquema para que encaje. Uno de los posibles cambios que, seguramente, habrá que hacer, es distribuir los elementos simples (que no tienen sub-elementos), ya que la herramienta XSD suele colocarlos juntos al crear el esquema. De todas maneras, en los errores que da al guardar, nos indica qué campos esperaba encontrar y cuales ha encontrado realmente.

Documentando nuestra base de datos

Muchas veces nos encontramos con una base de datos con gran cantidad de tablas, claves primarias y columnas. Y algunas veces, nos gustaría tener un pequeño inventario de todos estos datos, para poder sacar, de un vistazo, una visión global de la base de datos con la que trabajamos.

Hacer un documentador es muy fácil si sabemos lo que queremos buscar y cómo podemos obtenerlo.

Primero vamos a definir qué queremos buscar. Supongamos que queremos hacer un inventario con las tablas de nuestra base de datos, sus columnas y los procedimientos almacenados que están asociados.

¿Cómo obtenerlos?
Vamos a buscar en 2 vistas que nos ofrece SQL Server: Sys.Objects y Information_Schema (con sus variantes).

Paso 1
Obtener las tablas de la base de datos. Lo haremos con la query:

Select * from sys.objects where [Type]='U'

Para sacar otros elementos, basta con cambiar el tipo o no poner clausula Where, para obtener todas las filas.
Algunos tipos son:

F FOREIGN_KEY_CONSTRAINT
FN SQL_SCALAR_FUNCTION
IT INTERNAL_TABLE
P SQL_STORED_PROCEDURE
PK PRIMARY_KEY_CONSTRAINT
S SYSTEM_TABLE
SQ SERVICE_QUEUE
U USER_TABLE
UQ UNIQUE_CONSTRAINT
V VIEW


Paso 2
Vamos a buscar las columnas de la tabla en cuestión.

Select *
from Information_Schema.columns
Where Table_Name = 'nombreTabla'
And Table_Catalog = 'nombreBaseDeDatos'


Con esta query sacamos toda la información relativa a las columnas de la tabla en cuestión, como el nombre, si puede o no ser NULL, tipo de datos que admite, etc.

Ya tenemos toda la información relativa a las tablas y sus columnas. Ahora vamos a buscar información acerca de los procedimientos almacenados.

Paso 3
Obtener todos los procedimientos almacenados de la base de datos.

Select * From INFORMATION_SCHEMA.ROUTINES
Where Routine_Type = 'Procedure'
And Specific_Catalog = 'nombreBaseDeDatos'


Con esto obtenemos el nombre, la definición del procedimiento, la fecha de creación y de modificación, entre otras cosas…

Paso 4
Obtener los datos relativos a esos procedimientos almacenados. Usaremos la siguiente query para ello:

select *
From INFORMATION_SCHEMA.ROUTINES ISR
Inner Join INFORMATION_SCHEMA.PARAMETERS ISP
On ISR.ROUTINE_NAME = ISP.SPECIFIC_NAME
Where
ISR.Routine_Type = 'Procedure'
And
ISR.Specific_catalog = 'nombreBaseDatos'
And
ISR.Specific_Name = ‘nombreProcedimientoAlmacenado’


Esta query nos dará los parámetros de ese procedimiento almacenado, su tipo, su dirección, su nombre….

Con estos simples pasos puedo ir sacando la estructura de mi base de datos. Tirando de esas queries, es posible que encontréis mucha más información que la que os muestro.

Un saludo,

Buscando el Report ideal... (2)

Cosas a tener en cuenta cuando damos formato a un report

Aquí van unas guías para que no se olvide qué hay que comprobar a la hora de dar formato. Es una checklist para confirmar que no nos dejamos nada cuando demos formato.

- ¿Está el Report dividido en Header, Body y Footer? Si falta algún elemento, justificarlo.
- ¿Está configuarado el ancho y alto de la página, así como los márgenes? Si no, al exportar no va a funcionar.
- ¿Está el mapa del report bien construido, aunque no se muestre en pantalla? Tened en cuenta que, por defecto, al exportarlo a Excel, se exporta el mapa en una hoja aparte. Cuidado que lo que se muestra en el mapa sea significativo (por ejemplo, que no se vean los identificadores que hemos usado para ordenar)
- Comprobar las columnas, que cada celda mantiene la anchura y altura a lo largo de los reports. Esto es, la columna “Nombre de cliente” deberá tener el mismo ancho y alto en todos los reports que hagamos.
- Las imágenes a mostrar son coherentes.
- Si se usa algún elemento para listas (“-“, “*”,…), nos aseguramos que se usa siempre y que se usa siempre el mismo.
- Cada campo se corresponde con su cabecera de columna… que el copy-paste es peligroso…
- Exportar a los formatos a los que el usuario pueda exportar y comprobar que se exporta bien (que no quedan columnas separadas en una sola hoja, que los símbolos que he usado se exportan bien, etc…)
- Hacer pruebas con campos largos y comprobar que están alineados tanto en horizontal como en vertical como deseamos.
- Si usamos drilling-down, expandir todos los campos posibles para ver como queda el report en el peor de los casos.
- Hacer una batería de pruebas en el servidor de reporting, puesto que hay cosas que con la preview del diseñador no se pueden comprobar, como son los tooltips y el javascript que hayamos podido meter.
- Comprobar los links, que van donde van y que encuentran las páginas o reports como esperan.

Una gran idea sería hacer un report con todo lo que queremos y definir una serie de pautas para los siguientes (definir pautas no significa copy-paste). Podríamos definir:
- El ancho que va a tener cada columna
- Los bordes de las celdas que se van a mostrar
- Que celdas tienen tooltip y cuál es el formato
- Como se va a definir la navegación, si la hay (va a ser sobre toda la fila o va a haber una celda concreta para navegar)
- La cabecera del report y el pie de pagina
- Los tipos de letra de cada celda, alineación vertical y horizontal y posibles efectos.
- Preparar unos datos de reporting que nos permitan “someter” a nuestros reports a las más descabelladas situaciones (esas que son imposibles de darse, pero que el cliente siempre lo logra…, y menos mal que muchos ni saben de informática)

Un saludo

martes, mayo 29, 2007

Buscando el report ideal...

¿Qué características debe tener el report ideal? Cuantas más y mejores herramientas tenemos, tenemos tentaciones de dejarnos llevar por aburridos wizards o complicar en exceso los reports.

Mi “mini-guia” del report ideal se basaría en los siguientes puntos:
1. Un report es específico: la información que se muestra es concreta. No debería tener información que no aporte valor al usuario. Tener un mega-report no es una buena solución, aún cuando el usuario crea que sí. La información debe estar estructurada.


2. Un report no debe reflejar TODO el estado de la empresa: hacer un report que no filtra por nada lo que viene de la base de datos, no tiene sentido. Un report que hoy me parece adecuado aunque no filtre por fechas, dentro de 10 años moverá tanta información que no será válido. Vale que en 10 años ese report habrá sido sustituido “n” veces, pero eso no es excusa…


3. Un report debe ser agradable al usuario. Ojo con los scrolls (el scroll horizontal es bastante incómodo para el usuario). Ojo con el formato del texto y ojo con el formato de la tabla.


4. Un report debe permitir cierta interactividad con el usuario. Incluir una cierta dosis de navegación permite al usuario organizar y leer más fácilmente la información.

5. Un report no es infinito. Si quiero mostrar demasiados datos, es posible que tenga que incluir un subreport o un link a otro report.

6. Lo que se muestra no tiene por qué ser lo único que hay. Incluir tooltips para mostrar más información poede ser una buena idea, en su justa medida. Meter tooltips en todos lados equivoca al usuario y le acaba haciendo pensar que tiene que ponerse encima de todos los campos para ver más datos.

7. El usuario debe saber qué va a pasar cuando haga clic en el report. Meter links a otros sitios sin ton ni son, puede equivocar al usuario, a menos que esté muy claro lo que hace.
Javascript, sí, pero sólo si ayuda. Mostrar un pop-up con información extendida, puede ser una buena idea. Mostrar pop-ups en todos lados, no es buena práctica.

8. El report se genera rápido (si es posible). Para ello, debo:

- Delimitar la información a mostrar
- Mejorar las queries que recogen los datos
- Minimizar al máximo el peso del report (imágenes, las justas)

9. La imagen del report es la imagen de la empresa. Lo cual requiere que TODOS los reports matengan unos criterios de formatos. Misma letra, mismo tamaño, mismas imágenes, mismo estilo en header y footer…

10.Un report no es una aplicación web. Si se da demasiada interactividad a un report, de la sensación de que es una página web más y eso no debería ser así. Si es así, es posible que no necesitemos Reporting Services, sino una página ASP.NET.

Un saludo

PD: Pido disculpas a David por usar tantas veces el término "report"... jaja

viernes, mayo 25, 2007

XSLT Debugging?????? Si, y muy fácil



Si hace unos años me hablan de la posibilidad de debuggar .NET al nivel que se debuga ahora con Visual Studio, seguramente preguntaría "¿Qué es eso de .NET?".


Pues bien, en un momento en el que XML parece que se convierte en un standard de intercambio de información y en el que aparecen montones de tecnologías que, bien lo mejoran, bien lo complementan, bien hacen uso de él, voy poco a poco descubriendo herramientas que facilitan la vida.


Si hace poco descubrí la potencia de XSLT, ahora Visual Studio me sorprende con un debugger de XSLT.


¿¿¿Sorprendido??? Yo bastante... :-)




Primero, buscar el botón de XSLT Debugging.
En el menú: Tools-->Customize-->Pestaña de Commands. Busco la categoría de XML. Me aparecerán 4 posibles botones. Arrastro el “Debug XSLT” a la barra de herramienta. También vamos a arrastrar el “Show XSLT Output”, porque vamos a ver un pequeño “truco” al final.

Bien, ahora necesitamos un XSLT, lo abrimos. Doy al botón de debuggar XSLT y, si no tengo definido ningún XML que transformar, me pedirá que lo especifique. Lo selecciono marcando la ruta y ¡empieza la fiesta!

Se muestran dos ventanas: el XSLT y el XSLT output. El XSLT (poned puntos de interrupción, si no no vamos a ver nada… :-) ) aparecerá con la siguiente instrucción a debuggar.

Con F10 voy poco a poco navegando por las instrucciones y, a medida que los elementos HTML se van completando, irán apareciendo en la ventana de la derecha.
Por completar me refiero a, por ejemplo, tablas completas (no filas o columnas), labels, etc…

Si tenemos presente el XML y predecimos el flujo que debería seguir a lo largo de los Xsl:Otherwise y Xsl:If, podemos comprobar que el XSLT funciona como debe.

Mmmm ¿¿¿pero podría usar también la ventana de Watch para ver en que estado están las variables??? La respuesta es SÍ!!!
Pero no es tan facil como arrastrar la variable a la tabla. Ahora la ventana Watch entiende XPath. Es decir, si hay una tag en el XML que es Cliente/Nombre y quiero ver su valor, lo que debo hacer para comprobar que valor tiene ahora mismo esa variable es escribir en la ventana de Watch Cliente/Nombre/text()… XPath puro y duro…

Y también puedo comparar, buscar atributos…. Todo lo que XPath permite para consultar sus nodos. Ojo porque la ventana Watch no es una ventana para hacer queries contra el XML… porque analiza el XML según sonde está la ejecución del XSLT. Esto es, si estoy navegando por Empleados/Empleado, si accedo al nombre, será por medio de ./Nombre.

Y a partir de aquí, imaginación…

Un regalito, el botón de XSLT Output que hemos añadido antes abrirá la salida que produce el XSLT sin debuggar. La buena noticia, que vemos a la vez el XSLT y el resultado que produce. ¿La buenísima? Que lo que cambiemos en el XSLT se traduce directamente en el output.

Si es que programar está chupado….
Más fuentes:

jueves, mayo 24, 2007

Links para Patterns

Hacer lo que otros ya han hecho es lograr despues del tiempo que ellos estuvieron trabajando en ello, llegar al mismo sitio que ellos llegaron (si no un poco menos lejos).

Es así, siempre ha sido y siempre será. Para eso nacen los patterns. "Cosas" que están hechas, probadas, requeteprobadas y documentadas. No se nada de patterns (nada de nada). Lo poco que he hecho en ese campo ha sido usar un par de patterns, pero no considero que sepa nada al respecto. No asbría explicarlo.
Mi amigo Jaime uso un día un Singleton, pero de verdad... sabiendo porqué y cómo. Así da gusto... :-)

Por ello, y a modo de referencia y compendio, ahí van una serie de links de la gente que SÍ sabe de esto:

About the Design Patterns in VB.NET (una buena guia de iniciación, con un poco de todo)
C# - State Pattern Example (un pattern para manejo de estado, en 1.1)
Model-View-Controller in ASP.NET 2.0 (pattern para simplificar la capa UI)

Un saludo

Diseño de aplicacion multi-capa (para arquitectos... y para todos los que teclean)

Buenas,

Este artículo Introduction to Object-Oriented Tiered Application Design, es un gran redumen de qué es y como funciona el N-Tier design, incluyendo algunas consideraciones de validación y seguridad que no están nada mal.

Un artículo que todos deberíamos leer para entender porqué separamos en capas la aplicación... Entender lo que hacemos hace que seamos capaces de entender los retos que plantea.
Vamos a dar una vuelta de tuerca más al control XML de ASP.NET.
Ya hemos visto como configurar un control XML. Ahora vamos a darle un poco de dinamismo.
Vamos a mostrar datos sacado de una base de datos.
Tenemos una tabla de empleados. Así mismo, como quiero que varios clientes puedan consultar la lista, voy a proporcionarles un Web Service que va a dar un XML con la lista de empleados. Así, cada uno podrá customizar como mostrar unos datos.
Esto es, vamos a proporcionar un Web Service que va a proporcionar a lo usuarios un XML con la lista. Luego, cada usuario tendrá un XSLT que mostrará lo que le interese, y como le interese.

Paso 1: La query

Mmmm queremos una query que nos devuelva un XML. En esta web tenéis varias formas de lograr ese output. Nos quedamos con la siguiente:

SELECT (CAST(( SELECT '') +
( SELECT EmpleadoId,
RTRIM([EmpleadoNm]) AS Nombre
,RTRIM([EmpleadoSNm])AS Apellidos
,RTRIM([EmpleadoAge]) AS Edad
,RTRIM([EmpleadoSabiduria])AS Sabiduria
FROM [TestingDB].[dbo].[Empleados]
FOR XML PATH('Empleado')) +
(SELECT '
')
AS XML))


Bien, ya tenemos la lista de empleados, cada uno de ellos con el tag Empleado.

Ahora, a por el web service. Vamos a hacer que nos devuelva un XmlDocument (System.Xml). Esto permitirá al cliente manipularlo antes de darlo a la capa de presentación.

El Web Service sería así:

[WebMethod]
public XmlDocument GetEmpleados()
{

XmlDocument doc = new XmlDocument();

SqlConnection connection = new SqlConnection(@"Data Source=localhost\sqlexpress;Initial Catalog=TestingDB;Integrated Security=SSPI;");

SqlCommand command = new SqlCommand();
command.CommandText = "GetEmpleados";
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Connection = connection;
connection.Open();

XmlReader reader = command.ExecuteXmlReader();

while (reader.Read())
{
doc.InnerXml = reader.ReadOuterXml();

}
reader.Close();
connection.Close();
return doc;
}




Ahora, ¡a consumir el web service!

Creamos un site y añadimos la referencia web. Añadimos un control XML, solo con ID y Runat=”Server”, nada más.
Vamos a usar un fichero cualquiera de transformación.
En la página, vamos a recogerlo con forma de XmlNode.

El codigo en el Load, sería:

protected void Page_Load(object sender, EventArgs e)
{
EmpleadosWS.Service servicioEmpleados = new EmpleadosWS.Service();
XmlNode node = (XmlNode )servicioEmpleados.GetEmpleados();
xmlViewer.DocumentContent = node.OuterXml;
xmlViewer.TransformSource = "~/App_Data/Transformador.xslt";
xmlViewer.DataBind();
}


No olvideis el DataBind!!!!!

Un saludo

Nota: es muy posible que se pueda hacer más optimizado... sobre todo la query. Cualquiera que quiera sugerir como hacer o mejorarlo, por favor, hagálo :-) .... y gracias

Object Oriented Programming con Javascript

"Javascript es un lenguaje de niños", "Con javascript no se puede hacer casi nada" .... ¿Cuantas veces hemos dicho u oido esto?

Yo mismo lo encontraba (aún hoy lo encuentro, porque no se usarlo bien :-( ) tedioso, complicado e inmanejable... Lo que hace el no saber. No obstante, usarlo bien abre muchas posibilidades (usarlo mal hará que acabemos con un proyecto en el que cada fallo implique horas y horas de debugging).

Para usarlo bien, he aqui una serie de guias al respecto:
Create Advanced Web Applications With Object-Oriented Techniques
Ejemplo de menú usando OOP javascript

Un saludo, espero que os guste

FindAll de Generics

Buenas a todos. Una de las ventajas de las listas, son la posibilidad de obtener otras listas con los datos filtrados de una primera.
¿Cómo lo haríamos? Supongamos que tenemos una lista de enteros y que queremos sacar dos listas, una con los pares y otra con los impares. El codigo sería el siguiente:

using System;
using System.Collections.Generic;
using System.Text;

namespace Pruebas_Lenguaje_C_Sharp
{
public class Find_All_Generics
{
public Find_All_Generics(List enteros)
{
if (enteros != null)
__enteros = enteros;
else
throw new Exception("List passed cannot be null");
}
private List __enteros;

public List Enteros
{
get
{
if (__enteros == null)
__enteros = new List();
return __enteros;
}
set
{
__enteros = value;
}
}
public List GetPares()
{
return Enteros.FindAll(FindPares);
}
public List GetImpares()
{
return Enteros.FindAll(FindImpares);
}
// Esta funcion debra devolver true para aquellos elementos que queramos filtrar.
// El metodo FindAll ejecutara este metodo por cada elemento de la lista y, si el
// resultado es True, lo añadirá al resultado
bool FindPares(int entero)
{
return (entero % 2 == 0);
}

bool FindImpares(int entero)
{
return (entero % 2 != 0);
}
}
}


En el Main, ponemos:

static void Main(string[] args)
{
List enteros = new List();

for (int i = 0; i < 10; i++)
enteros.Add(i);

Find_All_Generics exampleList = new Find_All_Generics(enteros);

Console.WriteLine("Pares: ");
foreach (int i in exampleList.GetPares())
Console.WriteLine(i);
Console.WriteLine("Impares: ");
foreach (int i in exampleList.GetImpares())
Console.WriteLine(i);
Console.ReadKey();
}

Y comprobamos el resultado.

Esto es muy útil para realizar filtros sobre las listas. Por ejemplo,
- De una lista de empleados, puedo querer sacar una lista de los que son mayores de 40 años.
- De una lista de eventos deportivos, puedo querer filtrar los que son el domingo que viene
- etc...

Muchísimas gracias a Gloria por enseñarme el "fántastico mundo del FindAll" :-)

XSLT avanzado

Ojo al articulo linkado aqui.


El articulo va de como hacer un arbol para navegar por los elementos de un XML basado en XSLT y algo de Javascript. Mi consejo es que os bajeis el ZIP de ejemplo y le echeis un vistazo.

Os va a gustar :-)

miércoles, mayo 23, 2007

XSLT y el control XML de ASP.NET

Uno de los controles menos ducumentados de ASP.NET en su version 2.0 es el XML Control. Tampoco hay mucho que documentar, pues basta con configurar cual es el origen de datos (el XML) y quien lo va a pintar (el XSLT). Con esto conseguimos varias cosas:
1. Independizamos cada componente de los demás- XML solo sabe de datos, no tiene ni idea de cómo se pintan en pantalla
- XSLT solo sabe como pintarlos. No sabe cuales son. Sólo sabe que espera recibir unos campos, sean los que sean.
- La pagina Web con su control XML no sabe ni que va a pintar ni como. Solo sabe en que posición de la página van.
2. Logramos que cambios en el futuro afecten a una capa solamente, no a todas.

Para aprender a manejar XSLT, conviene ver los siguientes links:


Meter Javascript y código .NET en XSLT
Tutorial de iniciación a XSLT
Trucos y cosas curiosas de XSLT

XQuery-SQL Server 2005 - La guia

Hace tiempo que empecé a escribir sobre XQuery y apunté unos cuantos links útiles. Ahora vamos a dar una vuelta de tuerca y vamos a poner varios ejemplos.

Para ello vamos a usar un XML que tenga, más o menos variedad de cosillas: campos que se repiten, campos que no, elementos simples, complejos... para aplicar lo que aprendamos.

El XML podría ser el que sigue:






















Vale, ya tenemos el XML, vamos a empezar a hacer consultas basicas:

Antes de nada, vamos a ver la estructura que tienen las queries.

for $A in /Encuesta/Contenido/Pregunta
where $A/Respuesta = “SI”
return $A/Texto

Esta query nos va a buscar dentro de todas la preguntas. Va a filtrar las que cumplan que la respuesta es si, y nos va a devolver el enunciado (texto) de la pregunta

Abrimos una nueva Query en SQL Server y escribimos:

select @xmlText.query('for $A in /Encuesta/Contenido/Pregunta
where $A/Respuesta = ''SI''
return $A/Texto
')

Nos devolverá:
¿Le gusta el cine?
¿Le gusta el futbol?

Vamos a refinar un poco… Ahora solo queremos que nos devuelva el contenido, sin tags xml. La query seria:

select @xmlText.query('for $A in /Encuesta/Contenido/Pregunta
where $A/Respuesta = ''SI''
return $A/Texto
').value('(//Texto/text())[1]','VARCHAR(MAX)')

Al ejecutarlo, hay un problema: solo nos devuelve el primero de los campos… Esto nos vale SOLO cuando sabemos que sólo hay un campo. Por ejemplo, en nuestro caso sería una busqueda del nombre de un encuestado.

Vamos a solucionar el problemilla… Vamos a hacer dos técnicas:
Mostrar los dos en el mismo campo: Para ello vamos a hacer uso de una cuncion de XQuery: concat. Vamos a concatenar todas las preguntas.


select @xmlText.query('for $A in /Encuesta/Contenido/Pregunta
where $A/Respuesta = ''SI''
return concat($A/Texto[1]," - ")
')

Mejor, ahora nos devuelve las preguntas concatenadas…

Mostrar una fila por cada coincidencia: es posible que queramos mostrar una fila por pregunta, para ello haremos uso de la siguiente query. Necesitamos que el XML esté en una tabla, porque necesitamos hacer un CROSS. La query sería:

SELECT
cast(R.EncuestaDetails.query('.')
.value('(/Texto)[1]','VARCHAR(MAX)')
AS VARCHAR(100))
AS ClienteNm

FROM
[Encuesta] C
CROSS APPLY
C.[EncuestaXML].nodes('/Encuesta/Contenido/Pregunta)
AS R(EncuestaDetails)

Esto nos devolvería todas las preguntas (una por fila) que haya en los XML de la tabla.

Genial!!!! No?