Archivo de la categoría 'ASP.NET'


Microsoft Ajax Minifier 4.0

Miércoles, 17 Feb, 2010 @ 10:35 | Por Gustavo Cantero (The Wolf) | ASP.NET, JavaScript

ASP.NETUna de las tareas que podemos realizar para reducir el tiempo de descarga inicial de nuestras aplicaciones es la eliminación de comentarios, espacios, puntos y coma y demás caracteres, y renombrar los objetos y variables utilizando nombres más cortos en nuestros archivos de JavaScript, pero realizar esta tarea manualmente esta pesada, repetitiva y propensa a errores, por lo tanto se utilizan herramientas que lo hacen automáticamente. Una de estas herramientas es el Microsoft Ajax Minifier, el cual, en su nueva versión 4.0 que salió ayer, también tiene soporte para archivos CSS, o sea, también elimina espacios, comentarios y caracteres innecesarios de los archivos de estilos.

El Microsoft Ajax Minifier 4.0 lo pueden descargar del siguiente enlace: http://aspnet.codeplex.com/releases/view/40584.

VN:F [1.7.3_972]
Rating: 0.0/10 (0 votes cast)

Autenticación por formularios en ASP.NET

Lunes, 19 Oct, 2009 @ 10:59 | Por Dario Krapp | AJAX, ASP.NET

Prólogo e inicios

En algunas tecnologías web antiguas, como por ejemplo ASP (en el caso de Microsoft) la programación de la autenticación de los usuarios quedaba en manos de los programadores. A pesar de que no es una tarea extremadamente compleja, demandaba tiempo de desarrollo, pruebas y correcciones por cada sitio que se desarrollaba y aunque las técnicas utilizadas para esta funcionalidad se encontraban informalmente estandarizadas, siempre podían existir diferencias en las implementaciones (quien haya caído en la suerte de tener que modificar una aplicación legada, posiblemente sabrá a que nos referimos ), lo que influía en los tiempos y costos del desarrollo y si lo analizamos parecería tener poco sentido el gasto de esfuerzos de desarrollo en una tarea que es tan repetitiva. Quizás esa sea una de las causas por las que ASP.NET provee un sistema de autenticación ya incluido, lo que implica código, desarrollado, testeado, mantenido y listo para usar. En este artículo nos adentraremos en la autenticación por formularios desde sus comienzos en las primeras versiones de ASP.NET hasta la versión 3.5 (y diría que también hasta la versión 4.0, ya que hasta el momento no se han introducido modificaciones sobre estos ítems en la versión 4.0 y según parece, tampoco hay intenciones de hacerlo) y comentaremos algunos otros temas relacionados.

Para empezar diremos que la autenticación es el acto de confirmar que algo o alguien es quien dice ser y autorización es el acto de dar permisos (o denegárselos) a algo o alguien (ya autenticado) sobre un recurso, diremos además que algo o alguien es anónimo si no ha sido autenticado, que la acción de loguearse (login) consta de informar las credenciales a fin de poder autenticarse y la acción de desautenticarse (logout), si me permiten el término aunque no suene de lo mejor, consta de volver a un usuario autenticado al estado de anónimo.

En ASP.NET el modo de autenticación por formularios es aplicable para aplicaciones web que requieren autenticación, siempre que la misma no sea autenticación de Windows, para tales casos ASP.NET ofrecerá la opción de establecer el modo de autenticación Windows, pero ese es otro tema.

ASP.NET a través del modo de autenticación por formularios nos brindará mecanismos y funcionalidades que nos ayudarán a realizar ambas tareas: autenticación y autorización.

Para que el modo de autenticación por formularios funcione, solo debemos activarlo y la forma es simplemente ajustar la propiedad mode del tag authentication (del archivo web.config) en Forms, tal como se muestra a continuación:

<authentication mode="Forms">

El modo de autenticación por formularios creará una cookie con la información básica del usuario (obviamente encriptada), la cual será enviada por el browser en cada solicitud y el Servidor analizará la información de esta cookie para hacer los chequeos de autenticación y autorización necesarios. Este modo de funcionamiento es necesario debido a que en un entorno web las conexiones entre el cliente y el servidor se establecen y se cortan en cada solicitud. Por defecto la cookie tomará el nombre “.ASPXAUTH”.

En el siguiente esquema pueden observarse paso a paso las verificaciones de autenticación y autorización que realizará ASP.NET cuando una solicitud es procesada y se está utilizando autenticación por formularios:


Autenticación por formularios

El proceso graficado será realizado automáticamente por ASP.NET con simple hecho de haber establecido el atributo mode como Forms.
En nuestros ejemplos (que los estaremos realizando sobre Visual Studio 2008 SP1) utilizaremos una aplicación web que inicialmente contendrá la página Default.aspx, que es creada automáticamente junto con el proyecto.

Estructura del proyecto

Si en el ejemplo, luego de haber establecido el modo de autenticación por formularios, intentamos acceder a la página Default.aspx, notaremos que podremos hacerlo sin problemas, pero no debemos preocuparnos, este comportamiento se debe a que aún no hemos definido las reglas de acceso, por ahora cualquier usuario podrá acceder a cualquier recurso del sitio.

Para evitar que esto suceda, deberemos configurar las reglas de acceso. Por ahora, para simplificar lo haremos agregando las siguientes líneas dentro de la sección system.web del archivo web.config:

<authorization>
      <deny users="?"/>
</authorization>

Luego veremos en detalle las opciones disponibles para esta sección de la configuración, pero la sintaxis que utilizamos es bastante intuitiva, estaremos indicando que deberá negársele el acceso a cualquier recurso del sitio a los usuarios anónimos (identificados con el signo “?”, recordemos que un usuario anónimo es aquel que no se ha autenticado).

Si luego de esta modificación intentamos acceder nuevamente a Default.aspx (que es un recurso del sitio) veremos que somos automáticamente redireccionados a la página login.aspx, obteniendo un resultado similar al siguiente en la barra de direcciones del explorador:

http://localhost:6931/login.aspx?ReturnUrl….

Se obtendrá un error dado que la página login.aspx no existe, pero ese detalle mínimo pierde importancia ya que como podemos ver, el mecanismo está funcionando.

Lo que está haciendo ASP.NET es verificar la cookie, y al no encontrarla nos ha enviado a un formulario para que ingresemos nuestras credenciales y podamos autenticarnos.

La pagina login.aspx es la página de redirección que utiliza ASP.NET por defecto, pero es posible modificar este comportamiento definiendo un tag denominado forms dentro del tag authentication en el web.config como se muestra a continuación:

<authentication mode="Forms">
      <forms loginUrl="ingreso1.aspx"></forms>
</authentication>

Y no habrá quien se sorprenda de que luego de esta última modificación, la redirección sea a:

http://localhost:6931/ingreso1.aspx?ReturnUrl…….

Existen varias opciones aplicables al tag forms, algunas de ellas, además de loginUrl son:

  • name: Nombre de la cookie (el valor por defecto es .ASPXAUTH).
  • defaultUrl: pagina de redirección si la autenticación fue satisfactoria y el usuario había ingresado originalmente a la página de logueo (login.aspx por defecto).
  • timeout: tiempo de vida de la cookie.
  • slidingExpiration: si es verdadero, el tiempo de vida de la cookie se reiniciará cada vez que la página es reenviada.
  • cookieless: permite definir si se utilizarán cookies para mantener la información del usuario.
  • requireSSL: En verdadero indicará que el browser enviará la cookie al solo si la conexión es segura (SSL) en caso contrario de no contarse con una conexión segura, no funcionará la autenticación por formulario.
  • domain: Permitirá definir en qué dominio la cookie es válida.
    path: Permitirá definir el path de la cookie.

Para continuar con el ejemplo agregaremos la página ingreso1.aspx que será desde ahora en más nuestra página donde el usuario deberá ingresar sus credenciales para autenticarse (login).

Con estas simples modificaciones en el archivo de configuración hemos conseguido dar un primer paso a incorporarle a nuestro sitio la autenticación por formularios. Ahora que hemos conseguido que nadie pueda ingresar, debemos permitir que algunos usuarios si puedan hacerlo, y en particular aquellos a los que deseemos permitirles el ingreso. En este punto encontraremos dos opciones, una de ellas es encargarnos de autenticar al usuario por nuestra cuenta, lo que implica hacer el chequeo manualmente vía código y la otra será una opción automática con algunas particularidades.

Para el primer caso tomará importancia una clase llamada FormsAuthentication. Esta clase (como su nombre indica) nos ayudará a realizar todas las funcionalidades relacionadas con la autenticación por formularios programáticamente, como por ejemplo autenticar a un usuario y redireccionarlo a la pagina que el mismo había solicitado cuando se le denegó el acceso.

Para realizar la autenticación vía código con la ayuda de la clase FormsAuthentication escribiremos lo siguiente:

protected void Login_Click(object sender, EventArgs e)
        {
            if (Usuario.Text == "Juan" && Clave.Text == "clavedejuan")
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
            {
                LabelError.Text = "Usuario o clave incorrecto";
            }
        }

Hemos asumido que poseemos los cuadros de texto, Usuario, Clave y el botón Login, donde hemos utilizado el evento Click del mismo para agregar el código que puede verse en las líneas superiores. De más está decir que no hemos mencionado a los validadores con el único objetivo de simplificar el ejemplo.

Si probamos el ejemplo intentando ingresar a la página Default.aspx seremos redireccionados a la página Ingreso1.aspx donde deberemos ingresar nuestras credenciales, tal como se muestra a continuación:


Autenticación

Solo podremos acceder a Default.aspx si utilizamos como usuario a “Juan” y como clave a “clavedejuan”.

En el código que terminamos de agregar podemos ver también que nos estamos valiendo del método RedirectFromLoginPage de la clase FormsAuthentication para indicar la autenticación del usuario y la redirección a la página solicitada originalmente por el mismo.

Por otra parte podríamos incluir en la página Default.aspx un botón con la siguiente funcionalidad:

protected void LogOut_Click(object sender, EventArgs e)
        {
            FormsAuthentication.SignOut();
            FormsAuthentication.RedirectToLoginPage();
        }

Al presionarlo se ha utilizado una vez más la clase FormsAuthentication para desautenticar al usuario y reenviarlo a la página de ingreso nuevamente.

Con estas operaciones hemos conseguido que un usuario pueda realizar las operaciones de autenticación, en tal caso será simple reemplazar la sentencia Usuario.Text == “Juan” && Clave.Text == “clavedejuan” que utilizamos en el ejemplo por la llamada a un método que acceda al repositorio que más deseemos para efectuar la validación de las credenciales del usuario.

Si se observa la clase FormsAuthentication se notará que existe un método llamado Authenticate que toma por parámetros un nombre de usuario y una clave y devuelve un valor booleano, podríamos preguntarnos si este método será capaz de realizar la autenticación por nosotros, esta es la segunda opción que habíamos mencionado en algunos párrafos previos y para probar como funciona, podremos modificar el código anterior de la siguiente forma:

protected void Login_Click(object sender, EventArgs e)
        {
            if (FormsAuthentication.Authenticate(Usuario.Text, Clave.Text))
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
                LabelError.Text = "Usuario o clave incorrecto";
        }

Si ahora intentamos ingresar nuevamente, ya no importa que usuario y clave utilicemos, no podremos hacerlo, lo cual es un resultado bastante esperable, si consideramos le hemos delegado el control de la autenticación ASP.NET pero aún no le hemos indicado quienes serán los usuarios válidos de la aplicación. Si se desea utilizar esta opción automática debe considerarse la particularidad de que los usuarios deberán definirse en el propio archivo de configuración web.config, como se muestra en el siguiente ejemplo:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx" >
    <credentials passwordFormat="Clear">
      <user name ="Pepe" password ="clavedepepe"/>
      <user name ="Pedro" password ="clavedepedro"/>
    </credentials>
  </forms>
</authentication>

Luego de agregar estos valores veremos que es posible ingresar con ambos usuarios. El atributo passwordFormat del tag credentials admitirá además de la opción Clear (claves en formato limpio, o sea en texto legible para quien abra el archivo web.config) las opciones MD5 y SHA1 que permitirán utilizar el hash de la clave del usuario utilizando los algoritmos MD5 o SHA1. Claro está que en tal caso deberemos crear un mecanismo capaz de establecer dichos valores, lo bueno es que para obtener los valores hash en ambos formatos la clase FormsAuthentication ofrecerá el método HashPasswordForStoringInConfigFile que permitirá tomar una clave limpia y obtener el valor de hash correspondiente en el formato deseado. En el ejemplo anterior, si deseamos utilizar por ejemplo MD5 deberemos efectuar las siguientes modificaciones:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx">
    <credentials passwordFormat="MD5">
      <user name="Pepe" password="957995AA67183A4D2A91F7DE3EB9A692"/>
      <user name="Pedro" password="B447CA7CBA96E91D68D43C5867522BF0"/>
    </credentials>
  </forms>
</authentication>

Donde los valores de hash se han obtenido efectuando las siguientes llamadas:

string HashedPasswordPepe = FormsAuthentication.HashPasswordForStoringInConfigFile("clavedepepe " , "MD5");

y

string HashedPasswordJuan = FormsAuthentication.HashPasswordForStoringInConfigFile("clavedejuan " , "MD5");

Si bien todo esto funciona, creo que es inevitable pensar que se podría dar un próximo paso, ya que al fin de cuentas con este esquema que terminamos de ver, si bien ASP.NET y la clase FormsAuthentication nos han ayudado a controlar la autenticación y la autorización, la funcionalidad de la autenticación debimos codificarla, y cuando utilizamos la autenticación automática con el método Authenticate debimos definir a los usuarios en el archivo web.config. No sería nada malo que ASP.NET pudiese combinar ambas opciones y permitirnos efectuar la autenticación automáticamente sobre cualquier repositorio que deseáramos utilizar.

VN:F [1.7.3_972]
Rating: 8.6/10 (8 votes cast)

Crear certificados de prueba para servidor y cliente

Viernes, 26 Jun, 2009 @ 16:12 | Por Gustavo Cantero (The Wolf) | ASP.NET, IIS, Seguridad

Cuando estamos desarrollando una aplicación que utiliza certificados de cliente para autenticar a los usuarios debemos crear certificados de prueba para poder probar la aplicación, pero también se debe crear uno para utilizar con SSL en el Internet Information Server, y otro certificado que utilizaremos como entidad emisora de los certificados anteriores.

Para crear estos certificados de prueba utilizaremos la herramienta MakeCert, la cual crea archivos de certificado con sus pares de claves (pública y privada). Esta herramienta también asocia el par de claves al nombre especificado de una empresa y crea un certificado X.509 que enlaza el nombre especificado por un usuario con la parte pública del par de claves.

Cabe mencionar que esta aplicación, además de crear el archivo con el certificado, lo instala en uno de los repositorios de Windows, los cuales pueden verse, por ejemplo, desde el Internet Explorer, en el menú “herramientas”, “opciones de internet”, “contenido”, “certificados”, lo que abre una ventana como la que se muestra a continuación.

Certificados

Esta es una de las herramientas que se incluyen en el Windows SDK, el cual viene con Visual Studio o puede bajarse de forma independiente de la siguiente dirección: http://msdn.microsoft.com/es-ar/windows/bb980924.aspx.

Como primer paso vamos a crear el certificado de la entidad emisora con los siguientes parámetros:

  • Vamos a llamarlo “Scientia Root Auth” con la opción “-n” utilizando la nomenclatura definida en X.500
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Lo vamos a guardar a nivel de máquina (opción “-sr LocalMachine”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Lo vamos a utilizar para firmar (opción “-sky signature”)
  • Este certificado va a estar “autofirmado”, o sea, no va a ser creado por otra entidad emisora

Entonces, utilizando los parámetros descriptos en esta lista, el comando a ejecutar queda así:

makecert -pe -n "CN=Scientia Root Auth" -ss my -sr LocalMachine -a sha1 -sky signature -r "ScientiaRootAuth.cer"

Para confiar en este certificado, al ser autofirmado, tenemos que copiarlo al repositorio raíz de entidades de certificación de confianza (Trusted Root Certification Authorities), para lo cual vamos a utilizar otra herramienta que viene en el Windows SDK llamada “Certification Manager” (CertMgr). Esta herramienta puede utilizarse desde su interfaz gráfica o a través de la línea de comandos. Nosotros vamos a utilizarla a través de la línea de comandos para copiar el certificado:

certmgr -add -all -c "ScientiaRootAuth.cer" -s -r LocalMachine Root

Ahora vamos a crear el certificado para utilizar SSL en el Internet Information Server con el protocolo https:

  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Vamos a utilizar la nomenclatura definida en X.500 para nombrarlo “localhost” (opción “-n”)
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Lo vamos a guardar a nivel de máquina (opción “-sr LocalMachine”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Lo vamos a utilizar para intercambio de identidades (opción “-sky exchange”)
  • Lo vamos a utilizar para autenticar el servidor (opción “-eku 1.3.6.1.5.5.7.3.1”)
  • Vamos a utilizar como entidad emisora el certificado creado en el primer paso (opción “-in “Scientia Root Auth”)
  • La cual está en el repositorio personal (opción “-is my”)
  • A nivel de máquina (opción “-ir LocalMachine”)
  • Establecemos el proveedor del CryptoAPI con la opción “-sp “Microsoft RSA SChannel Cryptographic Provider””
  • Y el tipo de de proveedor CryptoAPI con la opción “-sy 12”

Entonces, utilizando los parámetros descriptos en esta lista, el comando a ejecutar queda así:

makecert -pe -n "CN=localhost" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in "Scientia Root Auth" -is my -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 localhost.cer

Ahora podemos configurar el Internet Information Server para que utilice este certificado. En este ejemplo vamos a utilizar el IIS versión 5.1, aunque esta tarea puede realizarse con cualquier versión. Desde las propiedades del sitio web, en la solapa “Seguridad de directorios”, vamos a ver una sección llamada “Comunicaciones seguras”.

Propiedades IIS

Ahí vamos a poder pulsar en el botón “Certificado de servidor…” y se nos va a abrir un “wizard” para crear, elegir o importar un certificado. En este ayudante vamos a seleccionar “asignar un certificado existente” y, en el paso siguiente, vamos a elegir el certificado llamado “localhost” (el creado en el paso anterior), luego ingresaremos el puerto que utilizaremos para SSL (por defecto es el 443), y listo, finalizamos el ayudante y ya podemos utilizar https en nuestro IIS.

Ahora, si necesitamos requerir que el usuario ingrese un certificado para autenticarse o para leer los datos del mismo, tenemos que crearlo. Para esto volveremos a utilizar la herramienta MakeCert, pero con algunos parámetros distintos:

  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Vamos a utilizar la nomenclatura definida en X.500 para ingresar el nombre del usuario, el país, la empresa, la unidad organizativa y el mail (opción “-n”)
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Como es un certificado personal lo vamos a guardar a nivel de usuario (opción “-sr CurrentUser”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Lo vamos a utilizar para intercambio de identidades (opción “-sky exchange”)
  • Lo vamos a utilizar para autenticación del cliente (opción “-eku 1.3.6.1.5.5.7.3.2”)
  • Vamos a utilizar como entidad emisora el certificado creado en el primer paso (opción “-in “Scientia Root Auth”)
  • La cual está en el repositorio personal (opción “-is my”)
  • A nivel de máquina (opción “-ir LocalMachine”)
  • Establecemos el proveedor del CryptoAPI con la opción “-sp “Microsoft RSA SChannel Cryptographic Provider””
  • El tipo de de proveedor CryptoAPI con la opción “-sy 12”
  • Por último le vamos a establecerle 25000000 como número de serie (opción “-# 25000000”)

Ahora juntamos todos estos parámetros y el comando nos queda:

makecert -pe -n "CN=Gustavo Cantero, O=Scientia Soluciones Informaticas, OU=Desarrollo, E=g.cantero@Scientia.com.ar, C=AR" -ss my -sr CurrentUser -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.2 -in "Scientia Root Auth" -is Root -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -# 25000000 GustavoCantero.cer

Bien, ahora para que el IIS le pida este certificado al usuario tenemos que habilitar SSL en la carpeta o sitio donde necesitamos el certificado, y establecerle que el usuario puede o debe seleccionar un certificado de cliente para autenticarse. Esto se hace desde la misma solapa “Seguridad de directorios” de las propiedades del sitio (o carpeta del sitio), pero pulsando sobre el botón “Modificar” de la sección “Comunicaciones seguras”, ahí vamos a pulsar sobre “Requerir canal seguro (SSL)” para que sólo pueda utilizarse a través de https, y “Requerir certificados de cliente”, para que el usuario deba seleccionar un certificado.

Comunicaciones seguras


Dependiendo de la versión de IIS y del sistema operativo también vamos a poder vincular los certificados del cliente a usuarios del dominio y otras opciones más avanzadas, las cuales exceden el propósito de este artículo.

Ahora, al acceder al sitio vamos a ver que nos muestra que tenemos un certificado en el cual no confiamos, esto es porque el certificado raíz no está en el repositorio de confianza del Internet Explorer. Para seguir podemos pulsar sobre “Continuar con este sitio”, y luego de esto el navegador va a abrir una ventana para que elijamos un certificado de cliente, y en esa ventana vamos a ver el último certificado que creamos.

Como último paso vamos a leer el certificado y a mostrar la información básica desde una página de ASP.NET con el siguiente código agregado en el evento Load de la página:

HttpClientCertificate objCert = Request.ClientCertificate;
if (objCert.IsPresent)
{
    if (objCert.IsValid)
    {
        System.Security.Cryptography.X509Certificates.X509Certificate2 objCert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(objCert.Certificate

        Response.Write("Persona = " + objCert.Subject + "<br/>");
        Response.Write("Emisor = " + objCert.Issuer + "<br/>");
        Response.Write("Válido desde = " + objCert.ValidFrom + "<br/>");
        Response.Write("Válido hasta = " + objCert.ValidUntil + "<br/>");
        Response.Write("¿Es válido ? = " + objCert.IsValid + "<br/>");
        Response.Write("Tamaño de la clave = " + objCert.SecretKeySize + "<br/>");
        Response.Write("Nombre del certificado del servidor = " + objCert.ServerSubject + "<br/>");
        Response.Write("Emisor del certificado del servidor = " + objCert.ServerIssuer + "<br/>");
        Response.Write("Número de serie = " + objCert.SerialNumber + " - " + int.Parse(objCert2.SerialNumber, NumberStyles.HexNumber) + "<br/>");
        Response.Write("Hash = " + objCert.CertEncoding + "<br/>");
    }
    else
        Response.Write("El certificado no es válido");
}
else
    Response.Write("No se ha encontrado un certificado");

Por último les dejo un archivo batch para crear los tres certificados en un paso.
En el próximo artículo mostraré algunos usos más avanzados de los certificados desde .NET.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 9.8/10 (11 votes cast)

Obtener la ciudad y el país de una dirección IP

Domingo, 24 May, 2009 @ 15:24 | Por Gustavo Cantero (The Wolf) | ASP.NET, GIS

Muchos habrán notado que al ingresar en algunos sitios web éstos modifican su contenido, incluyendo idioma, publicidad, textos, etc., dependiendo del país desde el que estamos ingresando, por ejemplo, al ingresar en www.linksys.com desde Argentina éste nos redirecciona a www-ar.linksys.com, o al ingresar a www.google.com nos reenvía a www.google.com.ar. Para poder hacer esto la aplicación web busca la IP con la que estamos ingresando en una base de datos donde previamente se cargaron rangos de IP con su respectivo país.

Hasta acá todo parece muy obvio, el problema surge cuando nos preguntamos: ¿Dónde conseguimos estas bases de datos? La respuesta es sencilla: en internet.

En internet hay varios sitios que ofrecen, gratuitamente o como servicio pago, archivos con rangos de IP con su respectivo país. Muchos de estos sitios ofrecen además web services para poder consultar contra su base de datos la ubicación correspondiente a una dirección IP sin necesidad de tener los datos alojados en nuestro sitio. En este artículo voy a mostrar cómo obtener la ubicación consultando los datos en una base de datos local.

Uno de los sitios desde donde se pueden bajar estos datos es WebHosting.Info (www.webhosting.info), un sitio dedicado a dar servicios y estadísticas sobre ISP, WHOIS, Reverse IP Lookup, etc. Los datos que estos sitios ofrecen casi siempre están en archivos CSV, donde el país está codificado en ISO 3166 y la dirección IP está como un entero sin signo de 4 bytes llamado “número IP”.

Para obtener la lista de códigos de países según la ISO 3166 pueden ver en el sitio mismo de ISO, http://www.iso.org/iso/country_codes, o en Wikipedia, http://es.wikipedia.org/wiki/ISO_3166-1.
Para convertir la dirección IP en un número IP (un entero de 4 bytes) es una simple fórmula matemática donde lo que tenemos que hacer es un “shift left” de cada número para “posicionarlo” en el octeto correspondiente o, en español, moverlo hasta el byte correspondiente. Como cada byte representa 256 números (de 0 a 255), lo que debemos hacer es multiplicar cada octeto de la dirección por 256 elevado a la posición de ese byte. Supongamos que nuestra dirección a convertir es A.B.C.D, entonces lo que debemos hacer es:

(A * 256^3) + (B * 256^2) + (C * 256^1) + (D * 256^0)

Si reemplazamos las constantes por su resultado nos quedaría lo siguiente:

(A * 16777216) + (B * 65536) + (C * 256) + D

Ahora supongamos que la IP de mi cliente es la 190.2.8.88, entonces deberíamos hacer:

(190 * 16777216) + (2 * 65536) + (8 * 256) + 88 =
   3187671040    +   131072    +    2048   + 88 = 3187804248

Cabe mencionar que para obtener la dirección IP del usuario que se está conectando a nuestro sitio se debería leer el encabezado de la petición http. Para los desarrolladores de .NET esto se puede hacer simplemente utilizando la propiedad UserHostAddress del objeto Request del Page, por ejemplo, en C# sería:

string strIP = Page.Request.UserHostAddress;

o, para los usuarios de Visual Basic.NET:

Dim strIP as string = Page.Request.UserHostAddress

Obviamente la IP está en un string, con lo cual debería dividir cada octeto de la dirección, convertirlo a un tipo de dato numérico y hacer la fórmula. A continuación les dejo un ejemplo en C#:

//Convierto la IP a un entero de 32 bits
string[] strBytes = Page.Request.UserHostAddress.Split('.');
uint intNumeroIP=
    (uint.Parse(strBytes[0]) * 16777216) +
    (uint.Parse(strBytes[1]) * 65536) +
    (uint.Parse(strBytes[2]) * 256) +
    uint.Parse(strBytes[3]);

y, para los usuarios de Visual Basic.NET:

'Convierto la IP a un entero de 32 bits
Dim strBytes As String() = Page.Request.UserHostAddress.Split('.')
Dim intNumeroIP As UInteger = _
    (UInteger.Parse(strBytes(0)) * 16777216) + _
    (UInteger.Parse(strBytes(1)) * 65536) + _
    (UInteger.Parse(strBytes(2)) * 256) + _
    UInteger.Parse(strBytes(3))

Nótese que en el ejemplo utilicé un entero sin signo, esto es porque el número IP no lleva signo. También cabe notar que, aunque cada octeto se puede representar con un byte, yo lo convierto a un entero sin signo para evitar la conversión del tipo de dato al almacenarlo en la variable final.

Ahora con este número en mente lo podemos buscar en el archivo con la información de la localización de las IPs.  Obviamente lo mejor es guardar previamente la información de este archivo en una base de datos. Como mínimo deberíamos guardar la dirección IP y el país. Muchos de estos archivos de IP nos traen una “dirección desde” y una “dirección hasta” por cada línea del archivos, pero con almacenar la “dirección desde” ya nos alcanza.
Para los usuarios de motores de bases de datos como Oracle o MySql no tendrán problema con guardar los números IP en campos enteros de 4 bytes sin signo, pero para los usuarios de SQL Server se nos complica, ya que este motor no permite utilizar este tipo de datos. Para solucionar este inconveniente podemos hacer dos cosas: guardar el número en un campo del tipo entero de 8 bytes o a cada número IP restarle 2147483648.

Ahora sólo nos quedaría buscar la IP del usuario en la base, con un script sencillo como el siguiente para SQL Server:

SELECT TOP 1 Pais
FROM ListaIPs
WHERE IP >= 3187804248
ORDER BY IP DESC

Si en SQL Server elegimos utilizar un entero de 4 bytes (int) y restarle la mitad para no utilizar un entero de 8 bytes (bigint) habría que utilizar el valor 1040320600 en lugar de 3187804248.

Si quisiéramos hacer esta misma consulta pero utilizando MySql como motor de base de datos deberíamos utilizar un script como el siguiente:

SELECT Pais
FROM ListaIPs
WHERE IP >= 3187804248
ORDER BY IP DESC
LIMIT 1

Bien, hasta acá ya sabemos cómo obtener el país correspondiente a una dirección IP pero, como el título bien dice, también podemos obtener la ciudad correspondiente a esta dirección. Para esto la solución vuelve a ser sencilla, solamente tenemos que buscar en internet donde se puede conseguir una lista de IPs correspondiente a cada ciudad.  Un lugar donde se puede obtener esta lista es en IP Location Tools (http://iplocationtools.com), donde se puede conseguir la lista de país por IP y ciudad por IP, en formato CSV y en SQL. La única diferencia con el proceso anterior es que en la base de datos, además o en lugar de guardar el país, se debería guardar la ciudad.

Espero que este artículo les haya sido de utilidad y, como siempre, los animo a escribir sus consultas, sugerencias y comentarios.

VN:F [1.7.3_972]
Rating: 9.3/10 (6 votes cast)

Nueva versión del ASP.NET AJAX Control Toolkit

Sábado, 16 May, 2009 @ 00:31 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET

Este mes salió a la luz una nueva versión del ASP.NET AJAX Control Toolkit, y este nuevo release viene nuevos tutoriales, la corrección de más de 20 bugs y tres nuevos controles: HTMLEditor, ComboBox y ColorPicker.  Cabe mencionar que esta nueva versión sólo está disponible para .NET Framework 3.5 y Visual Studio 2008.

HTMLEditor

Gracias a la compañía Obout (www.obout.com), que hizo el desarrollo de este control, ahora es posible crear y editar contenido HTML fácilmente en el navegador, utilizando una barra de herramientas con varios botones para cambiar el estilo del contenido.

ajax-htmleditor

ComboBox

En este caso el agradecimiento va dirigido a Dan Ludwig, quien desarrolló el control ComboBox de esta librería, el cual combina una lista desplegable y un cuadro de texto, con la ayuda del conocido “autocomplete”.

ColorPicker

Por último nos queda el control creado por Alexander Turlov, el cual es un Extender que se puede adjuntar a un TextBox para agregarle la funcionalidad de poseer un popup para seleccionar un color.

Nuevos tutoriales

Los nuevos tutoriales, junto con los que ya existían, se pueden encontrar en la siguiente dirección: http://www.asp.net/learn/ajax/

VN:F [1.7.3_972]
Rating: 7.9/10 (8 votes cast)

Editores de WebParts contraibles

Miércoles, 06 May, 2009 @ 00:48 | Por Gustavo Cantero (The Wolf) | ASP.NET, WebParts

Hace un tiempo desarrollamos un tablero de control para un cliente, el cual nos pedía que éste fuera personalizable por el usuario, tanto en contenido y estética, como en la posición de los controles a visualizar, motivo por el cual nos decidimos a construirlo con WebParts. La primera versión tuvo buena aceptación y gustó mucho, por lo cual en la segunda versión nos pidieron que hubieran más componentes y que éstos tuvieran muchas más opciones de configuración. Hasta acá todo iba bien, pero nos dimos cuenta que al agregar muchos WebParts al catálogo éste se hacía incómodo de utilizar (el catálogo tiene más de 200 componentes), y al agregarle muchas opciones de edición a éstos componentes era incómoda la edición, ya que esta se hacía muy extensa y el usuario debía “scrollear” mucho la página. Para resolver estos inconvenientes hicimos dos nuevos controles que utilizamos en ese desarrollo: un catálogo de componentes que utiliza un árbol (del cual hablaré en otro artículo), y una zona de edición donde cada EditorPart es “contraíble” para ocupar menos espacio en la página. Sobre éste último hablaremos en este artículo.

La idea será hace una zona de edición que se asemeje a la imagen a continuación:

Para modificar la forma en que se van a ver los editores de los componentes hay que crear una nueva “zona de edición”, la cual debe heredar de EditorZone y llamaremos CollapsibleEditorZone. En esta clase cambiaremos el método que devuelve el “chrome” a utilizar en esta zona, el cual modificaremos para que le agregue la opción de contraer y expandir como se ve en la imagen anterior. A nuestro “chrome” lo vamos a llamar EditorPartChrome, por lo tanto, el código de la clase CollapsibleEditorZone debería quedar como se muestra en el código que se encuentra a continuación:

using System.Web.UI.WebControls.WebParts;

/// <summary>
/// Nuevo editor de webparts
/// </summary>
public class CollapsibleEditorZone : EditorZone
{
    /// <summary>
    /// Escribe el control
    /// </summary>
    /// <param name="writer">Objeto sobre el cual escribir</param>
    protected override void Render(System.Web.UI.HtmlTextWriter writer)
    {
        writer.Write("<span class="CollapsibleEditorZone">");
        base.Render(writer);
        writer.Write("</span>");
    }

    /// <summary>
    /// Obtiene una referencia a un nuevo objeto <see cref="CollapsibleEditorChrome"/>
    /// que se utiliza para representar los editores
    /// </summary>
    /// <returns>Un objeto <see cref="CollapsibleEditorChrome"/></returns>
    protected override EditorPartChrome CreateEditorPartChrome()
    {
		return new CollapsibleEditorChrome(this);
    }
}

Ahora que tenemos la nueva zona tenemos que crearle el “chrome” del que hablamos antes. Esta nueva clase debe heredar de EditorPartChrome y debe tener el constructor (para que utilice el de la clase base) y dos métodos: uno para escribir el contenido de cada editor a utilizar y otro para dibujar el título de cada uno de éstos, en donde van a estar los botones para contraer y expandir el contenido. Estos botones van a ejecutar un JavaScript que modifica el estilo “display” de un DIV que envuelve el editor, para evitar hacer postbacks innecesarios.

Pero no todo es tan sencillo como parece, supongamos que el usuario expande algunos editores, modifica algunos parámetros del componente y luego pulsa sobre “aplicar” para ver los cambios, la aplicación va a hacer un postback y aplicará los cambios sobre las propiedades de los WebParts, pero al volver a dibujar los editores éstos se verán contraídos nuevamente. Para solucionar esto agregamos un INPUT HIDDEN por cada editor donde guardamos del lado del cliente el último estado del editor, así cuando hacemos un postback simplemente tenemos que verificar el valor de este campo para saber si estaba expandido o contraído.

A continuación muestro el código de esta clase:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

/// <summary>
/// PartChrome de editores para hacerlos contraibles
/// </summary>
public class CollapsibleEditorChrome : EditorPartChrome
{
    /// <summary>
    /// Constructor del chrome
    /// </summary>
    /// <param name="Zone">Zona</param>
    public CollapsibleEditorChrome(CollapsibleEditorZone Zone)
        : base(Zone)
    {
    }

    /// <summary>
    /// Representa un control <see cref="EditorPart"/> completo con todas sus secciones.
    /// </summary>
    /// <param name="Writer">Objeto <see cref="HtmlTextWriter"/> que recibe el contenido de editorPart.</param>
    /// <param name="EditorPart">Control que se está procesando en la actualidad.</param>
    public override void RenderEditorPart(HtmlTextWriter Writer, EditorPart EditorPart)
    {
        if (EditorPart == null)
            throw new ArgumentNullException("EditorPart");

        PartChromeType enuPartChromeType = Zone.GetEffectiveChromeType(EditorPart);
        Style objPartChromeStyle = CreateEditorPartChromeStyle(EditorPart, enuPartChromeType);

        objPartChromeStyle.AddAttributesToRender(Writer, Zone);
        Writer.RenderBeginTag(HtmlTextWriterTag.Fieldset);

        if (enuPartChromeType == PartChromeType.TitleAndBorder || enuPartChromeType == PartChromeType.TitleOnly)
            RenderTitle(Writer, EditorPart);

        Style objPartStyle = Zone.PartStyle;
        objPartStyle.AddAttributesToRender(Writer, Zone);
        Writer.AddAttribute(HtmlTextWriterAttribute.Id, EditorPart.ClientID + "_div");
        if (EditorPart.Page.Request.Form.Get(EditorPart.ClientID + "_state") == "1")
            Writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "inline");
        else
            Writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none");
        Writer.RenderBeginTag(HtmlTextWriterTag.Div);

        RenderPartContents(Writer, EditorPart);

        Writer.RenderEndTag(); // div
        Writer.RenderEndTag();// fieldset
    }

    /// <summary>
    /// Representa el título de un control <see cref="EditorPart"/>.
    /// </summary>
    /// <param name="Writer">Objeto <see cref="HtmlTextWriter"/> que recibe el contenido de editorPart.</param>
    /// <param name="EditorPart">Control que se está procesando en la actualidad.</param>
    private void RenderTitle(HtmlTextWriter Writer, EditorPart EditorPart)
    {
        string strDisplayTitle = EditorPart.DisplayTitle;

        if (!String.IsNullOrEmpty(strDisplayTitle))
        {
            Style objTitleTextStyle;

            TableItemStyle objPartTitleStyle = this.Zone.PartTitleStyle;
            Style objStyle = new Style();
            objStyle.CopyFrom(objPartTitleStyle);
            objTitleTextStyle = objStyle;

            objTitleTextStyle.AddAttributesToRender(Writer, Zone);

            string strDescription = EditorPart.Description;
            if (!String.IsNullOrEmpty(strDescription))
                Writer.AddAttribute(HtmlTextWriterAttribute.Title, strDescription);

            string strAccessKey = EditorPart.AccessKey;
            if (!String.IsNullOrEmpty(strAccessKey))
                Writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, strAccessKey);

            Writer.RenderBeginTag(HtmlTextWriterTag.Legend);

            if (EditorPart.Page.Request.Form.Get(EditorPart.ClientID + "_state") == "1")
                Writer.AddAttribute(HtmlTextWriterAttribute.Src, EditorPart.ResolveClientUrl("~/Images/Minus.gif"));
            else
                Writer.AddAttribute(HtmlTextWriterAttribute.Src, EditorPart.ResolveClientUrl("~/Images /Plus.gif"));

            Writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "collapseExpandEditor('" + EditorPart.ClientID + "');");
            Writer.AddAttribute(HtmlTextWriterAttribute.Width, "9px");
            Writer.AddAttribute(HtmlTextWriterAttribute.Height, "9px");
            Writer.AddAttribute(HtmlTextWriterAttribute.Align, "middle");
            Writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "hand");
            Writer.AddAttribute(HtmlTextWriterAttribute.Id, EditorPart.ClientID + "_img");
            Writer.RenderBeginTag(HtmlTextWriterTag.Img);
            Writer.RenderEndTag(); // img
            Writer.AddAttribute(HtmlTextWriterAttribute.Name, EditorPart.ClientID + "_state");
            Writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden");
            Writer.AddAttribute(HtmlTextWriterAttribute.Value, EditorPart.Page.Request.Form.Get(EditorPart.ClientID + "_state"));
            Writer.RenderBeginTag(HtmlTextWriterTag.Input);
            Writer.RenderEndTag(); // input

            Writer.WriteFullBeginTag("B");
            Writer.Write(' ');
            Writer.Write(strDisplayTitle);
            Writer.WriteEndTag("B");
            Writer.RenderEndTag(); // legend
        }
    }
}

Cabe mencionar que para mostrar los botones de “más” y “menos” de cada editor la aplicación web debe tener las imágenes “plus.gif” y “minus.gif” dentro de la carpeta “images”.
Ahora faltaría mostrar el JavaScript que utiliza estas clases del lado del cliente para ocultar y mostrar los editores:

function collapseExpandEditor(id) {
    var editor = $get(id + '_div');
    var img = $get(id + '_img');
    var input = $get(id + '_state');
    if (editor.style.display == 'none') {
        editor.style.display = 'inline';
        img.src = 'Images/Minus.gif';
        input.value = '1';
    } else {
        editor.style.display = 'none';
        img.src = 'Images/Plus.gif';
        input.value = '';
    }
}

En este ejemplo nosotros utilizamos el ScriptManager de ASP.NET, por eso se ve que usamos el método $get(), pero si prefieren no usarlo simplemente lo tienen que cambiar por document.getElementById().
Por si no se dieron cuenta, en la clase CollapsibleEditorZone creamos un SPAN donde metemos todos los editores, el cual tiene un estilo llamado igual que la clase. Este estilo lo utilizamos para agregarle el borde a cada uno de los editores:

.CollapsibleEditorZone FIELDSET
{
border: 1px solid #777777;
padding: 2px 2px 2px 2px;
}
.CollapsibleEditorZone FIELDSET FIELDSET
{
border: 1px solid #aaaaaa;
margin-bottom: 6px;
}

Lo último que quedaría hacer es agregar esta clase en alguna página, la cual debería quedar algo como lo que se muestra a continuación:

<wp:CollapsibleEditorZone ID="cezEdit" runat="server" VerbButtonType="Button"
    HeaderCloseVerb-Visible="false" Width="100%">
    <ApplyVerb Text="Aplicar" />
    <OKVerb Text="Aceptar" />
    <CancelVerb Text="Cancelar" />
    <ZoneTemplate>
        <asp:PropertyGridEditorPart runat="server" />
    </ZoneTemplate>
</cwp:CollapsibleEditorZone>

Nótese que el prefijo “wp” debe apuntar al namespace donde se encuentra la clase CollapsibleEditorZone.

Espero que este control les sea de utilidad y como siempre los invito a dejar sus opiniones y comentarios.

VN:F [1.7.3_972]
Rating: 8.0/10 (1 vote cast)

Usar variables de sesión en HttpHandlers

Jueves, 26 Feb, 2009 @ 13:26 | Por Gustavo Cantero (The Wolf) | ASP.NET

Ayer, luego de escribir el artículo “Usar variables de sesión en servicios web de WCF”, recordé otro lugar donde, por defecto, no se pueden utilizar variables de sesión, pero que muchas veces son necesarias: los HttpHandlers.  Estas clases representan un “handler” que puede accederse a través del navegador pero no necesariamente devuelven una página en HTML o XHTML.  Un ejemplo clásico de una implementación de estas clases es para generar imágenes dinámicamente o buscarla en la base de datos y enviársela al browser, para realizar algún proceso y reenviar al usuario a otra URL o para generar XML para algún componente del lado del cliente, por ejemplo, de Silverlight o Flash.
El esqueleto de la clase es bastante sencillo, es algo como esto:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Download : IHttpHandler {
    public void ProcessRequest(HttpContext Context) {
       ...
    }
    public bool IsReusable {
        get { return false; }
    }
}

Si dentro del método ProcessRequest queremos acceder al objeto System.Web.HttpContext.Current.Session nos va a devolver “null”.  Para que este objeto no sea nulo y nos devuelva la colección de variables de sesión simplemente hay que hacer que nuestra clase, además de heredar de la interfaz IHttpHandler, herede de la interfaz IRequiresSessionState, quedando el esqueleto algo como esto:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Download : IHttpHandler, IRequiresSessionState {
    public void ProcessRequest(HttpContext Context) {
       ...
    }
    public bool IsReusable {
        get { return false; }
    }
}

Con esta pequeña modificación en la herencia de la clase ya podremos acceder al objeto Session sin necesidad de implementar ningún método ni propiedad nueva, ya que el motor de ASP.NET utiliza esta interfaz como una marca para saber si tiene que cargar o no las variables de sesión.

VN:F [1.7.3_972]
Rating: 7.3/10 (3 votes cast)

Usar variables de sesión en servicios web de WCF

Miércoles, 25 Feb, 2009 @ 20:47 | Por Gustavo Cantero (The Wolf) | ASP.NET, WCF

Muchas veces necesitamos guardar información sensible por cada sesión de usuario de una aplicación web, y muchas de esas veces decidimos guardarlas en variables de sesión, ya que son seguras y rápidas, aunque consumen recursos del servidor, pero con un uso medido son una buena solución.  El problema surge cuando estas variables deben ser utilizadas por servicios web de WCF (Windows Communication Foundation), y que al intentar acceder a ellas a través del objeto System.Web.HttpContext.Current.Session nos devuelve la clásica excepción Object reference not set to an instance of an object.  Esto es debido a que la propiedad Current del objeto HttpContext es nula.  La causa de este comportamiento es debido a que en el WCF se desacopló los servicios y sus operaciones de ASP.NET, ya que el transporte de éstos no tiene que ser necesariamente http.

Para poder acceder al contexto web desde un servicio de WCF hay que utilizar el atributo AspNetCompatibilityRequirements para especificar el modo de compatibilidad que va a tener con ASP.NET.  Por ejemplo, si tenemos un servicio llamado “ServiceTest”, y queremos que tenga acceso a las variables de sesión, el mismo debería quedar como esto:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TestService : ITestService {
  //operaciones del servicio
}

Pero esto no es todo, todavía hay que escribir un poco más.  Hasta ahora marcamos a nuestro servicio como que “puede” utilizar la compatibilidad con ASP.NET, pero si en este punto consultamos la propiedad Current del HttpContext veremos que sigue siendo nula, y peor aún si en lugar de poner “Allowed” en el atributo de compatibilidad pusimos “Required”: se nos va a generar una excepción.  Esto es causado porque aún falta configurar la aplicación para utilizar la compatibilidad con ASP.NET.  Para que los servicios web puedan utilizar esta característica hay que modificar el web.config agregando (o modificando) la siguiente línea dentro de <configuration><system.serviceModel>:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

Una vez hecho esto ya deberíamos poder acceder a nuestras variables de sesión con un código parecido al siguiente:

object sessionVar = System.Web.HttpContext.Current.Session["MiVariable"];
VN:F [1.7.3_972]
Rating: 8.0/10 (1 vote cast)

Eliminar archivos temporales de ASP.NET

Jueves, 18 Sep, 2008 @ 12:10 | Por Gustavo Cantero (The Wolf) | .NET, ASP.NET, IIS

Cuando accedemos por primera vez a una aplicacion web hecha con .NET el IIS compila las páginas y guarda los assemblies generados (junto con los de la carpeta bin) en una carpeta temporal de cache, la cual está en el mismo lugar donde se instala el framework.  Rara vez sucede que el IIS no refresca esta cache, y cuando actualizamos nuestras páginas o assemblies, éstas no se ven reflejadas.  Para asegurarnos que esta actualización suceda debemos limpiar esta cache, y para hacerlo debemos seguir los siguientes pasos:

  • Detener el IIS, lo cual se puede hacer con el comando "IISReset /stop"
  • Eliminar el contenido de la carpeta “C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files”
  • Iniciar nuevamente el IIS, lo cual se puede hacer con el comando "IISReset"

Luego de esto, al ingresar en nuestra aplicación, el IIS vuelve a compilar el sitio y generar los archivos de cache.

VN:F [1.7.3_972]
Rating: 9.9/10 (7 votes cast)

“Alert” personalizado

Miércoles, 09 Jul, 2008 @ 01:03 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET

Desarrollando una aplicación web para un cliente hicimos que todas las ventanas de la misma se mostraran con un ModalPopUp de la librería AJAX Control Toolkit de Microsoft, utilizando dentro de ésta el mismo diseño que para el resto del sitio, logrando así una imagen homogénea en toda la aplicación.  El primer inconveniente que tuvimos fue cuando nuestro cliente nos solicitó que TODOS los popup de la aplicación fueran iguales, incluidos los de los validadores, los cuales se muestran con un SummaryValidator en un “alert” de JavaScript.  Reemplazar esto fue sencillo, ya que simplemente creamos una función llamada “Alert” (nótese que se diferencia de la función original de JavaScript en que la primer letra está en mayúscula) que escribía el mensaje que se le pasara a través de un parámetro dentro del ModalPopUp, lo mostraba, y lo colocaba sobre el resto de los posibles ModalPopUp que hubiesen cambiando el zIndex de los layers, de la misma manera que lo mostramos en el artículo “Deshabilitar controles de la página hasta que finalicen los UpdatePanels”.  Luego simplemente reemplazamos la llamada al alert con el siguiente código:

window.alert = Alert;

El código JavaScript quedaba como se muestra a continuación:

function Alert(msg) {
    $get('divAlertContent').innerText = msg;
    popUpShowed = $find('Alert');
    popUpShowed.show();
    popUpShowed._backgroundElement.style.zIndex += 10;
    popUpShowed._foregroundElement.style.zIndex += 10;
}

y en la página colocamos un código parecido al siguiente (al presentado aquí le quitamos el diseño para que quede más legible):

<asp:Button ID="btnAlert" runat="server" Style="display: none" />
<cc1:ModalPopupExtender ID="mpeAlert" runat="server" Enabled="True" PopupControlID="panAlert" DropShadow="true" TargetControlID="btnAlert" OkControlID="btnAcceptAlert" BehaviorID="Alert" />
<asp:Panel ID="panAlert" runat="server" HorizontalAlign="Center" Style="display: none" Width="370px" Height="100px">
    <div id="divAlertContent"></div>
<br />
    <asp:Button ID="btnAcceptAlert" runat="server" Text="OK" />
</asp:Panel>

Esto daba como resultado que todos los “alerts” de la aplicación se mostraran en una ventana del mismo diseño.
El siguiente pedido que nos hizo el cliente fue que, al igual que los alerts “tradicionales”, éstos pudieran cerrarse al pulsar la tecla ESC.   Para lograr esto modificamos la función anterior para que al mostrar el ModalPopUp guardara una referencia al mismo en una variable global llamada popUpShowed.  Luego agregamos una llamada a una función nuestra en el evento “pageLoad” del framework de AJAX, la cual chequea si el usuario pulsó la tecla ESC y, en caso de que popUpShowed no sea nula, cerramos el PopUp al que referencia.
A continuación muestro una página de ejemplo con todo el código para que mostrar la función “Alert” funcionando.

<%@ Page Language="C#" AutoEventWireup="true" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>">
<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
    <title>Página de ejemplo</title>

    <script type="text/javascript">
    var popUpShowed = null;
    function pageLoad() {
        document.body.onkeypress=keyPressHandler;
        window.alert=Alert;
    }
    function showPopUp(popUp){
        popUpShowed = $find(popUp);
        popUpShowed.show();
    }
    function Alert(msg){
        $get('divAlertContent').innerText = msg;
        popUpShowed = $find('Alert');
        popUpShowed.show();
        popUpShowed._backgroundElement.style.zIndex += 10;
        popUpShowed._foregroundElement.style.zIndex += 10;
    }
    function keyPressHandler(e){
        if (popUpShowed != null){
            var kC  = (window.event) ? event.keyCode : e.keyCode;
            var Esc = (window.event) ? 27 : e.DOM_VK_ESCAPE;
            if(kC==Esc){
                popUpShowed.hide();
                popUpShowed = null;
            }
        }
    }
    </script>

    <style>
        .ModalBackground {
            background-color: Gray;
            filter: alpha(opacity=70);
            opacity: 0.7;
        }
        .ModalPopup {
            background-color: #eeeeee;
            border-width: 1px;
            border-style: solid;
            border-color: Gray;
            padding: 3px;
            font-size: small;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <input type="button" value="Ejemplo" onclick="alert('Ejemplo desde javascript')" />
    <asp:Button ID="btnAlert" runat="server" Style="display: none" />
    <cc1:ModalPopupExtender ID="mpeAlert" runat="server" Enabled="True" PopupControlID="panAlert"
        DropShadow="true" TargetControlID="btnAlert" OkControlID="btnAcceptAlert" BehaviorID="Alert"
        BackgroundCssClass="ModalBackground" OnOkScript="popUpShowed = null" />
    <asp:Panel ID="panAlert" runat="server" HorizontalAlign="Center" Style="display: none"
        Width="370px" Height="100px" CssClass="ModalPopup">
        <div id="divAlertContent">
        </div>
        <br />
        <asp:Button ID="btnAcceptAlert" runat="server" Text="OK" />
    </asp:Panel>
    </form>
</body>
</html>

Por último les dejo un proyecto hecho con Visual Studio 2008 con un ejemplo del “Alert”

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS

VN:F [1.7.3_972]
Rating: 8.3/10 (6 votes cast)

Deshabilitar controles de la página hasta que finalicen los UpdatePanels

Jueves, 19 Jun, 2008 @ 00:30 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET

Hace unos meses comenzamos a desarrollar para un cliente una aplicación web más o menos compleja, con .NET 3.5 y bastante uso de AJAX. En esta aplicación hay páginas que utilizan varios UpdatePanels, y algunos de estos realizan procesos complejos que pueden llegar a demorar varios segundos, es por esto que nuestro cliente nos pidió que, al iniciar estos procesos, se bloquee la página para que el usuario no pueda ejecutar ninguna otra acción en la misma hasta tanto no finalice el proceso pendiente.  Obviamente no queríamos ejecutar un script “a mano” cada vez que se ejecutaba una acción y otro al finalizar la misma, así que buscamos la manera de automatizarlo un poco.  Comenzamos buscando los eventos propios del motor de AJAX de Microsoft® y encontramos que podemos capturar los del RequestManager cuando inicia una petición al servidor y cuando la misma finaliza.  Esto lo podemos hacer utilizando los métodos add_initializeRequest y add_endRequest de la instancia actual del PageRequestManager, la cual podemos obtener con el siguiente código: Sys.WebForms.PageRequestManager.getInstance().
En el primer evento mostrábamos un ModalPopUp (control que muestra una “ventana” en la página y deshabilita todos los demás controles de la misma) de la librería ASP.NET AJAX Control Toolkit de Microsoft®, el cual contenía un mensaje que decía “Aguarde un momento…”, y en el segundo evento lo cerrábamos.  A continuación se muestra como quedaba el PopUp en la página:

<cc1:ModalPopupExtender ID="mpeLoading" runat="server" BehaviorID="idmpeLoading" PopupControlID="pnlLoading" TargetControlID="btnLoading" EnableViewState="false" DropShadow="true" BackgroundCssClass="ModalBackground" />
<asp:Panel ID="pnlLoading" runat="server" Width="300" Height="50" HorizontalAlign="Center" CssClass="ModalPopup" EnableViewState="false" Style="display: none">
<br />Aguarde un momento...</asp:Panel>
<asp:Button ID="btnLoading" runat="server" Style="display: none" />

y acá está el código JavaScript:

function initializeRequest(sender, args){
    $find('idmpeLoading').show();
}
function endRequest(sender, args){
    $find('idmpeLoading').hide();
}
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(initializeRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);

Cabe aclarar que la variable idmpeLoading del código anterior contiene el identificador del ModalPopUp a mostrar.
Hasta este punto todo funcionaba bien, el problema surgió cuando utilizamos paneles modales con UpdatePanels dentro, en ese momento la ventana con el mensaje “Aguarde un momento…” se “dibujaba” debajo del panel que estaba utilizando el usuario, no cumpliendo con su finalidad, ya que de esta manera no se deshabilitaban todos los controles de la página.
Paso siguiente revisamos el código del ModalPopUp (el mismo se puede bajar desde el mismo link que mostré antes) y encontramos que a los paneles se les estable un valor fijo en el estilo zIndex para mostrarlos sobre los demás controles.  Luego revisamos las propiedades del objeto de JavaScript que representa el panel y encontramos que posee dos objetos interesantes: _backgroundElement y _foregroundElement.  Estos representan los DIVs del fondo y del contenido de PopUp respectivamente, entonces para hacer que estos objetos de HTML se posicionen sobre todos los demás simplemente tenemos que incrementarles la propiedad zIndex de su estilo.  Entonces, nuestro código JavaScript queda como se muestra a continuación:

function initializeRequest(sender, args){
    var mpeLoading = $find(idmpeLoading);
    mpeLoading.show();
    mpeLoading._backgroundElement.style.zIndex += 10;
    mpeLoading._foregroundElement.style.zIndex += 10;
}
function endRequest(sender, args){
    $find(idmpeLoading).hide();
}
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(initializeRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);

Por último, para hacer que esta funcionalidad esté presente en todas las páginas de nuestra aplicación, lo agregamos en la página “master” del sitio, logrando que en cualquier página (que utilice esta master) posea la funcionalidad aquí descripta.

En la siguiente imagen muestro como queda una aplicación de ejemplo que cree con Visual Studio 2008 para este artículo:

A continuación dejo el proyecto con el código fuente para quien quiera utilizar esta utilidad:

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS

VN:F [1.7.3_972]
Rating: 8.0/10 (4 votes cast)