RSS 2.0 Atom 1.0 CDF  
JASoft.org - September, 2006
El blog de José Manuel Alarcón Aguín. Programación .NET y mucho más...
 

Esto lo lei en Internet y me ha parecido muy interesante, así que como estos días no tengo mucho tiempo de escribir cosas propias lo comparto aquí. Puede resultar de muchísima utilidad.

Hay veces en las que queremos buscar un determinado dato pero no sabemos en qué tabla o campo está, por lo que sería muy útil poder hacerlo en toda la base de datos al mismo tiempo.

El siguiente procedimiento almacenado permite conseguirlo:

CREATE PROC SearchAllTables
(
@SearchStr nvarchar(100)
)
AS
BEGIN

-- Copyright © 2002 Narayana Vyas Kondreddi. All rights reserved.
-- Purpose: To search all columns of all tables for a given search string
-- Written by: Narayana Vyas Kondreddi
-- Site: http://vyaskn.tripod.com
-- Tested on: SQL Server 7.0 and SQL Server 2000
-- Date modified: 28th July 2002 22:50 GMT


CREATE TABLE #Results (ColumnName nvarchar(370), ColumnValue nvarchar(3630))

SET NOCOUNT ON

DECLARE @TableName nvarchar(256), @ColumnName nvarchar(128), @SearchStr2 nvarchar(110)
SET  @TableName = ''
SET @SearchStr2 = QUOTENAME('%' + @SearchStr + '%','''')

WHILE @TableName IS NOT NULL
BEGIN
SET @ColumnName = ''
SET @TableName =
(
SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME))
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > @TableName
AND OBJECTPROPERTY(
OBJECT_ID(
QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)
), 'IsMSShipped'
       ) = 0
)

WHILE (@TableName IS NOT NULL) AND (@ColumnName IS NOT NULL)
BEGIN
SET @ColumnName =
(
SELECT MIN(QUOTENAME(COLUMN_NAME))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(@TableName, 2)
AND TABLE_NAME = PARSENAME(@TableName, 1)
AND DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar')
AND QUOTENAME(COLUMN_NAME) > @ColumnName
)

IF @ColumnName IS NOT NULL
BEGIN
INSERT INTO #Results
EXEC
(
'SELECT ''' + @TableName + '.' + @ColumnName + ''', LEFT(' + @ColumnName + ', 3630)
FROM ' + @TableName + ' (NOLOCK) ' +
' WHERE ' + @ColumnName + ' LIKE ' + @SearchStr2
)
END
END
END

SELECT ColumnName, ColumnValue FROM #Results
END

Para buscar algo basta con escribir lo siguiente:

EXEC SearchAllTables 'texto a buscar'

(sacado de http://vyaskn.tripod.com/search_all_columns_in_all_tables.htm)

Thursday, September 28, 2006 9:06:35 PM (Hora de verano romance, UTC+02:00)  #    Comments [0]   Programación  |  Trackback

Bueno, tras una -creo que merecida- semana de vacaciones regreso a la actividad. Ha sido corta y no me ha llegado a nada, la verdad, pero...

...estos últimos días lo más cerca de la informática que he estado ha sido esto:

En el aeropuerto de Barajas (muy apropiado, porque además el cartelillo en cuestión lleva así más de un mes). El resto del tiempo he estado en el extranjero disfrutando de la ausencia de roaming, en definitiva desconectado por completo.

En los próximos días, y según lo permitan los "marrones" cotidianos, iré publicando nuevas -y espero que interesantes- cosas sobre ASP.NET y tecnologías relacionadas en este Blog.

¡Sigue sintonizado!

JM.

Monday, September 25, 2006 6:50:46 PM (Hora de verano romance, UTC+02:00)  #    Comments [0]   Off-Topic  |  Trackback

Bueno, al final he tardado un poco más de lo que pensaba en sacar algo de tiempo para poner una segunda parte del anterior "post" sobre eventos y monitorización de aplicaciones (y ahora no es que tenga mucho tiempo tampoco), pero es que con el lanzamiento del "Pack Certificación" he estado realmente liado...

Bien, sigamos...

Para configurar elementos de notificación en nuestra aplicación Web ASP.NET 2.0, sólo tenemos que incluir algunos parámetros en el archivo de configuración de la misma, bajo el nodo <healthMonitoring>.

De hecho ASP.NET 2.0 ya viene con algunos de ellos preconfigurados y funcionando. Por ejemplo, todos los errores no gestionados que se produzcan en la aplicación quedan automáticamente guardados en el registrode eventos del sistema, sin que tengamos que hacer nada especial:

Esto es de extrema utilidad porque en caso de producirse un error en la aplicación que no hayamos controlado no tenemos que preocuparnos de pedirle a los usuarios que nos guarden los detalles de lo que ocurreió, como en qué página estaban, qué hacían o qué mensaje de error se produjo (en caso de mostrarlo, cosa que tampoco debiéramos). Basta con comprobar el registro de vez en cuando y ver qué errores se están produciendo. En la ventana como la del ejemplo tendremos todo tipo de detalles del error: petición que se había hecho, tipo de error, nivel de confianza de seguridad, proceso que falló, usuario que se estaba suplantando, si estaba autenticado en Membership o no, dirección IP, traza de la pila, etc... Es decir, todo lo que necesitamos para determinar con cierto éxito el origen del error.

¿Cómo elegimos qué eventos se van a notificar y cómo lo harán?

Como ya he comentado para configurar qué eventos queremos sólo tenemos que incluir en nuestro web.config los ajustes necesarios para ello dentro del nodo <healthMonitoring>.

Ello implica la definición de diversos sub-nodos, algunos de los cuales son opcionales y otros no. Dado que en el archivo web.config global de ASP.NET 2.0 ya vienen incluidas las definiciones de diversos tipos de eventos, no será necesario en la mayor parte de los casos que nosotros los re-definamos. Se incluyen "de serie" los siguientes:

<eventMappings>
  <add name="All Events" type="System.Web.Management.WebBaseEvent, ..." />
  <add name="HeartBeats"
    type="System.Web.Management.WebHeartBeatEvent, ..." />
  <add name="Application Lifetime Events"
    type="System.Web.Management.WebApplicationLifetimeEvent, ..." />
  <add name="Request Processing Events"
    type="System.Web.Management.WebRequestEvent, ..." />
  <add name="All Errors"
    type="System.Web.Management.WebBaseErrorEvent, ..." />
  <add name="Infrastructure Errors"
    type="System.Web.Management.WebErrorEvent, ..." />
  <add name="Request Processing Errors"
    type="System.Web.Management.WebRequestErrorEvent, ..." />
  <add name="All Audits" type="System.Web.Management.WebAuditEvent, ..." />
  <add name="Failure Audits"
    type="System.Web.Management.WebFailureAuditEvent, ..." />
  <add name="Success Audits"
    type="System.Web.Management.WebSuccessAuditEvent, ..." />
</eventMappings>

Es decir, hay una definición ya hecha para todos los tipos de eventos (los que hereden de WebBaseEvent, o sea todos), "latidos" de la aplicación, ciclo de vida de ésta (inicio y fin, reciclajes...), peticiones de recursos, errores, etc... Si definiésemos nuestra propia clase de evento específica podríamos crear una entrada en nuestro web.config análoga a estas para definir el evento.

Además de los propios eventos en el archivo de configuración hay también un área para los proveedores que gestionan los eventos. En concreto los que están defnidos por defecto son estos:

<providers>
 <add name="EventLogProvider" type="System.Web.Management.EventLogWebEventProvider,System.Web..." />
 <add connectionStringName="LocalSqlServer" maxEventDetailsLength="1073741823"
    buffer="false" bufferMode="Notification" name="SqlWebEventProvider"
   type="System.Web.Management.SqlWebEventProvider,System.Web,..." />
 <add name="WmiWebEventProvider" type="System.Web.Management.WmiWebEventProvider,System.Web,..." />
</providers>

Esto, al igual que lo anterior, está copiado y pegado del web.config global.

Como vemos se definen gestores de eventos que los anotan en el registro de sucesos del sistema, en una base de datos SQL Server o en el sistema de instrumentación de Windows (WMI).

Podemos agregar otros tipos de gestores de eventos. Por ejemplo, si añadimos este nodo:

<providers>
   <add name="Proveedor_eMail" type="System.Web.Management.Simple-mailWebEventProvider"
     to="
midir@midominio.com" from="monitor_servidor@midominio.com" maxMessagesPerNotification="1" maxEventsPerMessage="10"
     buffer="true" bufferMode="Critical Notification" subjectPrefix="Web Events"/>
</providers>

De este modo estamos definiendo que queremos usar el proveedor de correo simple que ofrece ASP.NET 2.0 (hay otro más avanzado que nos permite definir cuál será el aspecto y contenido de los mensajes enviados). Le indicamos a qué direcci´no deben llegar las notificaciones, desde qué dirección se envían, cuántos eventos se notificarán por cada mensaje y otra serie de parámetros más avanzados que no voy a tocar ahora.

Ahora definamos las reglas

Bien, una vez que tenemos definidos los eventos que se pueden producir así como los posibles gestores de los mismos, lo único que nos hace falta es definir de qué manera se gestionarán. Para ello empleamos las reglas de notificación. Éstas se definen también con nodos en web.config, dentro de la misma sección, y simplemente relacionan los eventos que tengamos definidos con los gestores/proveedores de esos eventos.

Así, por ejemplo, si queremos que todos los errores no gestionados que se produzcan nos lleguen por correo electrónico de manera automática podemos escribir lo siguiente:

<rules>
    <add name="Todos los errores por e-mail" eventName="All Errors" provider="Proveedor_eMail"/>
</rules>

Esta es la forma más sencilla de definirla. Si nos fijamos lo único que se hace es otorgarle un nombre e indicar el nombre del tipo de eventos que queremos gestionar (en este caso uno de los que vienen por defecto en ASP.NET) y el nombre del proveedor que se quiere usar para gestionarlo (en este caso el que hemos definido manualmente).

Cuestiones más avanzadas

Esta es la punta del iceberg. Se pueden controlar muchos más aspectos a la hora de definir las reglas, como por ejemplo con qué frecuencia queremos que nos lleguen esos mensajes, cuántos eventos queremos que se nos notifiquen (uno de cada 10, como máximo uno cada 5 minutos, etc...), si queremos que se nos notifique sólo cuando se junten, por ejemplo, 5 de eventos deun tipo determinado, per no de uno en uno, y otros muchos aspectos interesantes que evitan que se saturen los recursos de la aplicación, nuestra base de datos o, en el caso del ejemplo, nuestra ya de por si colapsada bandeja de entrada.

Además existe la posibilidad de definir perfiles de comportamiento para las reglas de forma que, en lugar de ir estableciendo muchos parámetros en cada una se le indica qué perfil queremos usar y así se reutilizan éstos entre diversas reglas, lo cual puede resultar muy útil.

en fin, que el sistema de monitorización automática es un mundo en si mismo, y además muy interesante.

Mi intención al escribir este par de 'post's era presentar brevemente el tema y dar unas ideas generales, así como alguna recetilla (como la de notificar por correo electrónico) que los lectores podáis usar de inmediato. El analizar en profundidad todo lo que contiene el sistema de monitorización de ASP.NET 2.0 se sale de lo que me permite el tiempo hoy (y las ganas, la verdad). Tengo intención de escribir un artículo pronto que profundice en todos estos temas, enseñe a crear proveedores propios, etc.. y que saldría publicado en dotNetMania dentro de poco. Si tienes interés en el tema permanece atento y verás lo que da de si este interesante tema.

 

Saturday, September 16, 2006 4:03:13 PM (Hora de verano romance, UTC+02:00)  #    Comments [2]   ASP.NET | Programación  |  Trackback

Bueno, estas es la sorpresa que os decía que os iba gustar más incluso que los nuevos cursos de Bases de Datos de campusMVP... copio y pego directamente desde la web:

Pack "Certificación"

MSDN y campusMVP te ofrecen de forma conjunta esta oportunidad  única de formarte a tu ritmo y desde cualquier lugar en las últimas tecnologías de desarrollo de Microsoft, y obtener tu certificación oficial.

Con nuestro Pack "Certificación" y sólo hasta Octubre de 2006 obtendrás:

· Acceso a 6 cursos on-line, con las tutorías de nuestros MVP y MCT.
· Un manual oficial de Microsoft (MOC) orientado a tu especialidad.
· Un cupón con el que podrás examinarte gratis en cualquier parte del mundo para obtener tu certificación.
· Gestión gratuita de una bonificación con el Estado para que el curso te salga realmente barato.

¡Ya no tienes excusa para no certificarte!
Infórmate ya en la página del Pack "Certificación".

Saludos a todos

Friday, September 15, 2006 12:54:52 PM (Hora de verano romance, UTC+02:00)  #    Comments [1]   Off-Topic  |  Trackback

campusMVPBueno, esto es una pequeña cuña publicitaria enel Blog. No todo iba a ser gratis :-)

Sólo comentar que en campusMVp está abierta la matrícula para los cursos que empiezan el 2 de Octubre. Aparte de los cursos que ya teníamos tengo el gusto de anunciar que hemos incorporado nuevos cursos orientados al desarrollador de bases de datos, tal y como muchos sugerísteis.

Estos cursos los han hecho, como no, tres reconocidos MVPs: Salvadro Ramos, Lluis Franco y Octavio Hernández, que además es también MCT (profesor certificado por Microsoft).

Estoy seguro de que os van a interesar. Puedes echarles un vistazo en www.campusmvp.com

Hoy en día es impensable trabajar de programador y no tener que trabajar con bases de datos. De hecho la mayor parte del trabajo de un programador consiste en gestionar datos. Ello implica tratarlos en todos los niveles: definición y diseño, consulta y modificación, acceso a los mismos desde aplicaciones de escritorio o Web y explotación de los datos a través de informes. Este curso consta de cuatro módulos de altísima calidad que le ayudarán a saber todo lo necesario para desarrollar este tipo de aplicaciones: desde lo fundamental del lenguaje SQL hasta optimización, mantenimiento y explotación avanzada de los datos. Un curso indispensable que todo programador debería seguir.

DESARROLLADOR DE APLICACIONES DE BASES DE DATOS
  1 El lenguaje SQL y Transact-SQL
  2 Administración de SQL Server 2005 para programadores
  3 Acceso a datos con ADO.NET 2.0
  4 Diseño y distribución de informes con Visual Studio 2005 y Crystal Reports
  

Y por cierto, ¡estate atento porque los próximos días vamos a dar otra noticia que te interesará incluso más! :-)

Wednesday, September 13, 2006 8:50:35 PM (Hora de verano romance, UTC+02:00)  #    Comments [1]   Off-Topic  |  Trackback

Este tema siempre me ha encantado: la monitorización de tus aplicaciones para ver si se están comportando como es debido o no.

Esto en su sentido más amplio incluye todo tipo de información: desde contadores de operaciones y rendimiento personalizados hasta gestión automática de notificaciones ante ciertos sucesos y registro de todo lo importante que haya.

MonitorizaciónLa API de ASP 3.0 que usábamos en Krasis ya tenía integrado un sistema de Log automático y explotación del mismo, así como algunas utilidades para gestionar errores de forma automática. En .NET la cosa mejora mucho porque podemos crear contadores de rendimiento personalizados y crear facilmente entradas en el registro de sucesos del sistema.

En ASP.NET 2.0 los chicos de Microsoft se han superado también en este aspecto introduciendo lo que se ha dado en llamar "ASP.NET 2.0 Health Monitoring" o lo que yo llamaría en castellano a falta de una traducción mejor "Servicios de notificación y registro de ASP.NET 2.0".

El sistema ofrece, por un lado, una jerarquía de clases de notificación de eventos y por otro un sistema de proveedores de captura y registro de dichos eventos. Todo esto nos permite obtener muchísima información y estar al día de ella sin necesidad de escribir código.

Notificación de eventos

El sistema de notificación de eventos notifica automáticamente de cuestiones como arranques y paradas de la aplicación, logins fallidos de usuarios, errores de compilación o de configuración, excepciones no gestionadas, etc... Para cada uno de estos eventos hay una clase específica derivada de WebBaseEvent que nos informa de los datos concretos del evento (como por ejemplo el motivo de un error o un reinicio, o los datos de un login fallido).

Lo mejor de todo es que podemos crear nuestros propios eventos creando una clases derivada de la clase mencionada, y así los gestionaremos del modo común, en igualdad de condiciones con los de ASP.NET.

Para notificar un evento "a mano" sólo hay que instanciar la clase apropiada y llamar a su método Raise, que todas ellas poseen.

Registro de eventos

Vale, ya hemos visto que hay eventos, que podemos crear eventos propios y que se pueden lanzar con Raise. ¿Y ahora qué? Pues de poco nos servirá lanzarlos si no los gestionamos de algún modo. Para ello disponemos del sistema de captura y registro de eventos. Éste se basa, como todo en ASP.NET 2.0, en una arquitectura de proveedores que permite aislar la gestión del evento del medio con el que se gestiona. Así, existen de serie algunos proveedores que ya hacen la mayor parte de las cosas que podemos necesitar: un proveedor para enviar por e-mail notificaciones, otro para anotar los eventos generados en una base de datos de SQL Server, otro para anotar en el registro de sucesos del sistema, otro para exponerlos a la instrumentación de Windows (WMI), e incluso uno para mostrarlos en la traza de la página si ésta se encuentra activada (útil para depurar).

Por supuesto podemos crear nuestro propio proveedor de gestor de eventos para hacer lo que necesitemos (por ejemplo que lo anote en un simple archivo de texto con ciertos datos o que lo envíe a una cola de mensajería MSMQ, o lo que sea...).

Se pueden combinar varios proveedores para que gestionen un mismo evento o eventos, de modo que podemos hacer por ejemplo que los anoten en SQL Server y que nos manden un e-mail al mismo tiempo.

También existen reglas de monitorización que definen qué eventos se gestionan y en qué condiciones.

Mañana más...

Uff!, últimamente se me da por escribir de temas más bien largos y siempre necesito varias partes. Mañana tengo intención de continuar con esto y explicar cómo se usa en la práctica todo esto que acabo de contar. ¡Sigue sintonizado!

Wednesday, September 13, 2006 8:21:09 PM (Hora de verano romance, UTC+02:00)  #    Comments [0]   ASP.NET | Programación  |  Trackback

Bueno, esta es una de esas cosas que pueden volver loco a uno antes de encontrar el motivo que lo está provocando.

Supón que tienes una aplicación Web que funciona de maravilla y que utiliza algunas variables de sesión para guardar datos del usuario (por ejemplo un identificador o cualquier otra cosa). Colocas la aplicación en producción en un servidor Windows 2003 Server y todo parece ir perfecto. El servidor es IIS 6.0 y la aplicación está escrita en ASP.NET 2.0. A medida que la cantidad de peticiones y usuarios crece la aplicación funciona de maravilla y se comporta muy bien, gestionando muchas peticiones por segundo sin problema hasta que de repente un día tienes una llamada...

"Disculpe, soy un usuario de su aplicación y me hoy me ha pasado una cosa muy rara. He entrado con mi clave y nada más hacerlo me han aparecido los datos de otra empresa que no es la mía. Es como si se nos hubiesen cruzado las líneas. ¿no le estará pasando esto a alguien más y verán mis datos, verdad?"

Tragas saliva y tratas de justificar lo que ha pasado echñandole la culpa a los proxies-caché de Telefónica y cosas similares, y crees que te has librado. A los pocos días recibes otra llamda parecida... Y otra más algo después. No son muchas pero el problema parece recurrente y ya te empiezas a "acongojar". ¿Qué diablos está pasando aquí? Por más que buscas no hay manera de encontrar el origen del problema.

Ya puedes seguir buscando que no lo vas a encontrar...

El problema está en la caché en modo Kernel (núcleo) de IIS 6.0. Apuesto a que es esto lo primero que se te ha pasado por la mente ;-)

La caché en modo kernel de IIS 6.0

La caché en modo Kernel de IIS 6.0 es una novedad que apareció con IIS 6.0 en Windows 2003 Server y que está pensada para mejorar enormemente el desempeño del servidor al almacenar copias en memoria de recursos que no necesitan una transición entre el modo kernel y el modo usuario del sistema operativo. El "escuchador" de peticiones en modo Kernel de IIS 6.0 puede recibir una petición, comprobar si el recurso está en caché y devolverlo inmediatamente sin pasar por otros elementos más lentos y sin cambiar de contexto de funcionamiento. para que nos hagamos una idea, sólo con esto IIS 6.0 puede duplicar el rendimiento de páginas ASPX almacenadas en caché respecto a la versión anterior. O sea, que es estupendo.

Ahora vamos a estudiar brevemente cómo se comporta la caché de modo Kernel cuando trabajamos con páginas de ASP.NET 2.0  y éstas, claro está, tienen habilitada algún tipo de caché:

  1. Si la página tiene habilitada la caché total porque hemos especificado únicamente el parámetro VaryByParam=“None“, entonces ASP.NEt envía instrucciones a la caché de modo núcleo para que almacene su contenido. Ello hace que las siguientes peticiones de la misma página mientras dure su vida en la caché se envíen directamente desde ésta. Ello hace que sea un proceso rapidísimo y aumentamos mucho el rendimiento. Luego veremos los problemas que tenemos.
  2. Si se usa algún parámetro "Vary" con otros valores (como el propio VaryByparam o VaryByControl y VaryBycustom) no se usará la caché en modo kernel, sólo una caché "normal" de modo usuario, como en ASP.NET 1.x.

Según lo visto siempre que podamos es mejor usar una caché total para sacar partido a esta característica y así mejorar el rendimiento. Lo que ocurre es que no está exento de problemas.

El primero de ellos es que si actualizamos la página (o ya puestos el gráfico, script o cualqueir otro recurso "cacheado" de este modo) no notaremos el cambio en las peticiones hasta que pase un rato. Incluso aunque borremos un archivo puede que durante bastante tiempo sigamos recibiéndolo en el navegador como si aún existiera. Esto puede ser problemático, es evidente.

Otro problema adicional y menos evidente es que, al devolverse el contenido directamente desde la caché, no interviene para nada ASP.NET, por lo que olvídate de que se lancen eventos de la página. Así que si contabas con anotar algo en la base de datos, realizar cualquier acción al inicializar la página, etc... vete despidiendo pues no funcionará. Interesante ¿no?

Retomemos el problema inicial

Bueno, volvamos ahora veamos qué está pasando en el problema de pérdida de sesión (o más bien "cruce" de sesión) aparentemente aleatorio que se está produciendo en nuestra aplicación.

Todo viene por un "bug" existente en el módulo OutPutCacheModule de ASP.NET. Cuando éste en determinadas ocasiones proporciona a la caché de modo kernel el contenido de una página cacheada totalmente "se olvida" de eliminar del contenido la cabecera HTTP que establece la cookie de sesión del usuario y por lo tanto los siguientes que entran a ver la misma página reciben una copia que incluye dicha cabecera. Si esa página es la primera que éstos reciben del servidor la cabecera se toma como el identificador de sesión nuevo del usuario, reenviándose al servidor en las sigueintes peticiones. Dado que esa cabecera es la forma de identificar (por defecto) las sesiones, se les muestra el contenido del otro usuario. Suena un poco complicado pero así es...

Soluciones

La solución mas evidente es la de no usar nunca páginas cacheadas de manera íntegra. Es decir, trata de usar siempre VarByParam o alguno de los otros parámetros y no sólo el VaryByParam="None".

Si no te queda más remedio que usarlo puedes desconectar la caché en modo kernel para tu aplicación. Es muy fácil, sólo tienes que incluir esto en tu web.config:

<httpRuntime enableKernelOutputCache="false">

Puedes ver su documentación aquí.

También puedes cortar por lo sano, ser más radical, y desactivar la caché en modo kernel de golpe para todo el servidor. Sólo tienes que tocar el vbalor del registro UriEnableCache, y ya está.

En general no te recomiendo que desactives esta útil característica. Seguro que puedes encontrar otra solución. De todos modos la próxima vez que te encuentres con un problema grave de cruce de sesiones pregúntate a ti mismo "¿estaré usando caché completa en alguna página?". Si la respuesta es sí casi seguro que has encontrado la solución en este post y me vas a dejar una nota de agradecimiento :-)

Wednesday, September 06, 2006 9:03:55 PM (Hora de verano romance, UTC+02:00)  #    Comments [0]   ASP.NET | Programación  |  Trackback
Copyright © 2008 José Manuel Alarcón Aguín. All rights reserved.