Archivo de la categoría '.NET'


Introducción a Task Parallel Library (TPL)

Jueves, 15 Oct, 2009 @ 15:36 | Por Gustavo Cantero (The Wolf) | .NET Framework

Tal como fue expresado por Gordon Moore, cofundador de Intel, en 1965 en la conocida “Ley de Moore”, la cantidad de transistores que poseen las computadoras se duplicaría aproximadamente cada 18 meses (un año y medio), y esta tendencia continuaría durante las siguientes dos décadas. Unos diez años después corrigió su ley diciendo que la cantidad de transistores se duplicará cada 24 meses (2 años). Esta ley fue cumplida durante mucho tiempo (mucho más del que él pensaba al momento de formularla) generando una carrera en la generación de procesadores cada vez más potentes, hasta hace unos pocos años. Al llegar al límite (o muy cerca) del tamaño que se le puede dar a éstos transistores (por lo menos utilizando las técnicas actuales) se comenzaron a generar nuevos procesadores con varios núcleos, o sea, varios procesadores en una misma “pastilla”. Esto dio origen a los primeros procesadores Multi-Core, los cuales hoy en día son muy comunes en los equipos de escritorio y notebooks.

Hasta aquí todo parece ser bueno, pero para los desarrolladores no lo es tanto, ya que las aplicaciones que venimos desarrollando son, por lo general, creadas para ejecutarse en un único hilo (thread), y para poder utilizar el poder de estos nuevos procesadores hay que cambiar la forma de programar. Supongamos que tenemos una aplicación que calcula los números primos existentes entre el 1 y 1.000.000.000, seguramente esta tarea llevaría bastante tiempo, y aunque lo ejecutemos en una máquina con ocho núcleos (o, por ejemplo, cuatros procesadores con doble núcleo) veremos que sólo uno de los procesadores trabajará al 100%, mientras que el resto no realizará ninguna tarea. Para mejorar la performance de esta aplicación en esta máquina lo optimo sería distribuir la tarea en ocho threads para que cada uno se ejecute en un procesador distinto y reducir de esta manera el tiempo en conseguir el resultado.

Crear estos threads y distribuir la carga de trabajo hace que nuestro código sea más largo, difícil de leer, propenso a errores, y el crear ocho threads (como comentamos antes) no siempre es lo mejor, ya que esto depende de la carga de los procesadores y de la cantidad de éstos que dispongamos en cada máquina donde se vaya a ejecutar nuestra aplicación. Para facilitar estas tareas aparece la .NET Task Parallel Library, o como se la conoce, la TPL.

Task Parallel Library

Esta librería fue creada en un esfuerzo en conjunto del Microsoft® Research, el equipo del Parallel Computing Platform y el equipo del Microsoft® Common Language Runtime (CLR). La misma se encuentra incluida en la beta 1 del .NET Framework 4.0, pero puede utilizarse su versión CTP para .NET Framework 3.5 descargándola de la siguiente página: Microsoft Parallel Extensions to .NET Framework 3.5, June 2008 Community Technology Preview.

Cabe mencionar que en .NET Framework 4.0 hay muchas novedades sobre paralelismo, por ejemplo, está Parallel LINQ (también conocido como PLINQ), una librería para utilizar LINQ distribuyendo su carga en los procesadores disponibles, Parallel Pattern Library (o PPL) una librería de patrones utilizados en algoritmos concurrentes, y varias cosas más que exceden el alcance de este artículo.

TPL nos brinda distintas clases y métodos para distribuir y hacer un balance de carga de las tareas que debe realizar nuestra aplicación sobre los distintos procesadores que tengamos disponibles en la máquina en tiempo de ejecución. Por ejemplo, supongamos que tenemos una máquina con un procesador de doble núcleo y nuestra aplicación debe realizar tres tareas, el TPL ejecutará las dos primeras, una en cada núcleo, y al concluir cualquiera de éstas comenzará a ejecutar la tercera tarea en el núcleo disponible. Si esta misma aplicación la corremos en una máquina que posea más de dos núcleos ejecutará las tres tareas al mismo tiempo.

Hay que mencionar que la creación, administración y sincronización de estas tareas y threads por parte del TPL generan una carga extra de trabajo, pero ésta es muy pequeña y la ganancia al utilizar todos los procesadores de la máquina es enorme.

En .NET Framework 4.0 esta librería está incluida en el archivo mscorlib.dll, con lo cual podemos utilizarla sin necesidad de agregar librerías, pero si queremos usarla desde una aplicación desarrollada con .NET 3.5, además de bajar e instalar el CTP desde la dirección mostrada anteriormente, tenemos que referenciar el archivo System.Threading.dll desde nuestro proyecto.

La librería posee complejos algoritmos para la distribución dinámica de la carga entre los procesadores, pero esto sólo representa una posible ejecución en paralelo, ya que en una máquina con un único procesador las iteraciones se ejecutarán de manera secuencial en el mismo thread. Sin embargo hay que tener en cuenta que cuando utilizamos esta librería en una máquina multi-core es muy probable que se ejecuten varias acciones al mismo tiempo, por lo tanto es necesario tratar de evitar el uso compartido de variables entre éstas ya que podrían solaparse, pero para el caso en que esto sea necesario, más adelante mostraré como se puede hacer de manera segura.

VN:F [1.7.3_972]
Rating: 7.7/10 (3 votos cast)

Barra de progreso en el ícono de la aplicación en la barra de tareas de Windows® 7

Martes, 15 Sep, 2009 @ 23:46 | Por Gustavo Cantero (The Wolf) | WPF, Windows 7, Windows Forms

Los que tienen la posibilidad de utilizar Windows 7 habrán notado que trae varias novedades como la vista en miniatura de las aplicaciones minimizadas al pasar el mouse sobre ellas, que algunas de éstas, como el Windows Media Player, en esa vista poseen botones, y otras como el Internet Explorer utilizan el ícono de la barra de tareas como indicador de progreso (en el IE se utiliza para mostrar el progreso de las descargas). Todas estas características y algunas más que aquí no nombro (por ejemplo, hay una característica gracias a la cual, con un dispositivo de tal sólo u$s 30, se puede medir la luz ambiental y, si lo programamos, se puede cambiar el tema de pantalla dependiendo de la misma) son programables a través de las API del sistema operativo para brindarle a nuestras aplicaciones todas las posibilidades que posee. En este artículo hablaré de la posibilidad de mostrar el progreso de una operación como fondo del ícono de la misma en la barra de tareas.
Las aplicaciones que corren sobre Windows 7 pueden mostrar el progreso de una tarea que están ejecutando en el ícono de la barra de tareas, evitando de esta manera que el usuario tenga que abrir la ventana de la misma para verificar el estado del proceso. A continuación detallo los posibles estados que se pueden mostrar:

Aplicación sin barra de progreso Aplicación sin barra de progreso
Aplicación con estado normal Aplicación con estado normal y 50% de progreso
Aplicación con estado pausado Aplicación con estado pausado y 50% de progreso
Aplicación con error Aplicación con error y 50% de progreso
Aplicación con estado indeterminado Aplicación con estado indeterminado

Para modificar el estado y el valor de progreso de nuestras aplicaciones podemos utilizar la interfaz ITaskbarList3 como se muestra a continuación

ITaskbarList3* pTL; //creado anteriormente
HRESULT hr = pTL->SetProgressState(hwnd, TBPF_NORMAL); //hwnd es el puntero de la ventana
pTL->SetProgressValue(50, 100); //Establecemos un 50% de progreso

Para los que desarrollamos en .NET este código en C no nos sirve de mucho, entonces podemos referenciar las clases e interfaces COM o, mejor aún, podemos utilizar las librerías “Windows® API Code Pack for Microsoft® .NET Framework” que ya tienen resuelto este tema. Estas librerías, junto con su código fuente y ejemplos, las podemos bajar de la siguiente dirección: http://code.msdn.microsoft.com/WindowsAPICodePack. Para cambiar el estado y progreso de nuestra aplicación debemos utilizar los métodos SetProgressState y SetProgressValue de la clase Microsoft.WindowsAPICodePack.Taskbar.TaskbarManager respectivamente.
A continuación muestro un ejemplo en WPF de un proyecto en el cual se cambia el progreso de la aplicación modificando el valor de un control Slider, y el estado de la misma a través de un ComboBox. El XAML de éste es el siguiente:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="TaskBarProgress.Window1"
    Title="Ejemplo de barra de progreso en la barra de tareas" Height="170" Width="400"
    WindowStartupLocation="CenterScreen" WindowStyle="SingleBorderWindow" MaxHeight="170"
    MinHeight="170" MinWidth="320" Icon="Scientia.ico">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MinWidth="178" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition Height="Auto" MinHeight="36" />
        </Grid.RowDefinitions>
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="15" FontWeight="Bold" Margin="5" Text="Estado de la aplicación:" />
        <ComboBox x:Name="ComboBox1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="5" SelectionChanged="ComboBox_SelectionChanged">
            <ComboBoxItem>Normal</ComboBoxItem>
            <ComboBoxItem>Pausado</ComboBoxItem>
            <ComboBoxItem>Con error</ComboBoxItem>
            <ComboBoxItem>Indeterminado</ComboBoxItem>
            <ComboBoxItem IsSelected="True">Sin progreso</ComboBoxItem>
        </ComboBox>
        <TextBlock HorizontalAlignment="Right" FontSize="15" FontWeight="Bold" Grid.Row="1" VerticalAlignment="Center" Text="Porcentaje de progreso:" Margin="5" />
        <Slider x:Name="Slider1" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="5" SmallChange="1" Maximum="100" LargeChange="10" TickPlacement="Both" TickFrequency="5" ValueChanged="Slider_ValueChanged" />
        <TextBlock Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="10"><Run Text="Desarrollado por "/><Hyperlink NavigateUri="http://www.scientia.com.ar" RequestNavigate="Hyperlink_RequestNavigate"><Run Text="Scientia® Soluciones Informáticas"/></Hyperlink></TextBlock>
    </Grid>
</Window>

Y el código C# es el que muestro a continuación:

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.WindowsAPICodePack.Taskbar;

namespace TaskBarProgress
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private TaskbarManager windowsTaskbar = TaskbarManager.Instance;

        public Window1()
        {
            if ((Environment.OSVersion.Version.Major < 6) ||
               (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor < 1))
            {
                MessageBox.Show("Esta aplicación de ejemplo requiere Windows® 7 o superior para correr",
                    "Scientia® Soluciones Informáticas",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                Close();
            }
            else
                InitializeComponent();
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            e.Handled = true;
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (ComboBox1.SelectedIndex != 4)
                windowsTaskbar.SetProgressValue(Convert.ToInt32(e.NewValue), 100, this);
        }

        private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            switch ((sender as ComboBox).SelectedIndex)
            {
                case 0: //Normal
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Normal, this);
                    break;
                case 1: //Pausado
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Paused, this);
                    break;
                case 2: //Con error
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Error, this);
                    break;
                case 3: //Indeterminado
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Indeterminate, this);
                    return;
                case 4: //Sin progreso
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.NoProgress, this);
                    return;
            }

            windowsTaskbar.SetProgressValue(Convert.ToInt32(Slider1.Value), 100, this);
        }
    }
}

Obviamente todo el código mostrado funciona solamente sobre Windows® 7 por cual, si queremos hacer una aplicación que también sea compatible con versiones anteriores del sistema operativo, nos conviene verificar la versión de éste antes de utilizar estas características. Esto se puede hacer de la siguiente forma:

if ((Environment.OSVersion.Version.Major > 6) ||
    (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 1)) {
        //Es Windows 7 o superior
} else {
        //Es un sistema operativo anterior a Windows® 7
}

Proyecto de ejemplo de la utilización de la API de Windows 7 para cambiar el estado y progreso de una aplicaciónPor último les dejo una solución con un proyecto WPF y otro con Windows Forms donde se muestra como utilizar esta funcionalidad.

VN:F [1.7.3_972]
Rating: 10.0/10 (1 voto cast)

Hipervínculo en WPF

Lunes, 14 Sep, 2009 @ 23:37 | Por Gustavo Cantero (The Wolf) | WPF

Muchas veces a las aplicaciones de escritorio queremos darle algún diseño o detalle parecido al de las páginas web, por ejemplo, un hipervínculo que abra una página. Para esto WPF tiene un elemento llamado Hyperlink que facilita esta tarea, el cual puede ir dentro de un Paragraph o TextBlock. Un ejemplo de la utilización de este elemento se muestra a continuación:

<TextBlock>
<Hyperlink NavigateUri="http://www.scientia.com.ar">Scientia® Soluciones Informáticas</Hyperlink>
</TextBlock>

El inconveniente que posee este elemento es que fue pensado para utilizarse cuando está dentro de un NavigationWindow, Frame o corriendo en un XBAP dentro de un navegador. Si utilizamos este elemento sin alguno de estos contenedores, al pulsar sobre el enlace simplemente no hará nada. Para solucionar este pequeño problema podemos utilizar el evento RequestNavigate para hacer que al pulsar sobre él se abra el navegador por defecto en la dirección ingresada. El código a continuación muestra cómo quedaría el XAML:

<TextBlock>
<Hyperlink NavigateUri="http://www.scientia.com.ar"  RequestNavigate="Hyperlink_RequestNavigate">Scientia® Soluciones Informáticas</Hyperlink>
</TextBlock>

y acá muestro el handler del evento:

using System.Diagnostics;
...
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
    Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
    e.Handled = true;
}

Espero que les sea de utilidad.

VN:F [1.7.3_972]
Rating: 8.3/10 (3 votos cast)

Utilizar certificados digitales desde .NET

Viernes, 21 Ago, 2009 @ 09:20 | Por Gustavo Cantero (The Wolf) | .NET, Certificados Digitales, Seguridad

Luego de mis artículos sobre certificados digitales, firma digital y hash sólo me queda escribir el último de esta serie en donde me gustaría mostrar cómo buscar, leer y utilizar estos certificados X.509 desde .NET.

Consultar los repositorios de certificados

Como primer paso vamos a recorrer los certificados que tenemos instalados en nuestra máquina. Para esto debemos utilizar la clase X509Store, la cual nos da la posibilidad de consultar un repositorio de certificados, por ejemplo, el repositorio raíz (Root), el repositorio donde están los certificados de las autoridades certificantes (CertificateAuthority), o el repostorio personal (My). También tenemos que elegir la ubicación del certificado, es decir, si vamos a querer consultar los certificados que están a nivel de máquina o de usuario. Tengan en cuenta que si van a utilizar la clase X509Store desde ASP.NET deben leer los certificados que están a nivel de máquina, ya que el usuario ASP.NET rara vez va a tener algún certificado instalado.

Luego de elegido el repositorio que queremos abrir debemos utilizar el método Open para que consulte al mismo.

Una vez abierto el repositorio, la propiedad Certificates contendrá una colección (basada en la clase X509Certificate2Collection) de los certificados almacenados.

Después de utilizar el repositorio no hay que olvidar de cerrar el mismo con método Close.

A continuación muestro cómo abrir el repositorio personal y escribir en la consola de debug los nombres y firmas digitales (hash) de cada certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
...
X509Store objStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
objStore.Open(OpenFlags.ReadOnly);

foreach (X509Certificate2 objCert in objStore.Certificates)
    Debug.Print(objCert.SubjectName.Name + ": " + objCert.Thumbprint);

objStore.Close();

Consultar la información del certificado y sus extensiones

Cada certificado posee una serie de datos (los cuales se describen en el artículo Conceptos de Certificado Digital y Firma Digital y creamos en el artículo Crear certificados de prueba para servidor y cliente). Además de estos datos los certificados poseen extensiones, que definen distintos posibles usos dependiendo de las mismas, y según la extensión poseen distintos datos almacenados. Por ejemplo, en la extensión del tipo “2.5.29.37” (también llamada “Enhanced Key Usage”) se guarda cual va a ser el destino del certificado, por ejemplo para autenticación del cliente.

Estas extensiones están en la colección Extensions del certificado, y para obtener la información almacenada debe castearse según el tipo del mismo. Para saber qué tipo de extensión es la almacenada se puede leer la propiedad Oid, la cual devuelve la clase homónima que representa un identificador de un objeto criptográfico (cryptographic object identifier) y luego, de este objeto, podemos leer la propiedad FriendlyName para obtener el nombre de la extensión.

A continuación hay un código de ejemplo donde se muestra la información de un certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
StringBuilder objSB = new StringBuilder("Detalle del certificado: \n\n");

//Detalle
objSB.AppendLine("Persona = " + objCert.Subject);
objSB.AppendLine("Emisor = " + objCert.Issuer);
objSB.AppendLine("Válido desde = " + objCert.NotBefore.ToString());
objSB.AppendLine("Válido hasta = " + objCert.NotAfter.ToString());
objSB.AppendLine("Tamaño de la clave = " + objCert.PublicKey.Key.KeySize.ToString());
objSB.AppendLine("Número de serie = " + objCert.SerialNumber);
objSB.AppendLine("Hash = " + objCert.Thumbprint);

//Extensiones
objSB.AppendLine("\nExtensiones:\n");
foreach (X509Extension objExt in objCert.Extensions)
{
    objSB.AppendLine(objExt.Oid.FriendlyName + " (" + objExt.Oid.Value + ')');

    if (objExt.Oid.FriendlyName == "Key Usage")
    {
        X509KeyUsageExtension ext = (X509KeyUsageExtension)objExt;
        objSB.AppendLine("    " + ext.KeyUsages);
    }

    if (objExt.Oid.FriendlyName == "Basic Constraints")
    {
        X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)objExt;
        objSB.AppendLine("    " + ext.CertificateAuthority);
        objSB.AppendLine("    " + ext.HasPathLengthConstraint);
        objSB.AppendLine("    " + ext.PathLengthConstraint);
    }

    if (objExt.Oid.FriendlyName == "Subject Key Identifier")
    {
        X509SubjectKeyIdentifierExtension ext = (X509SubjectKeyIdentifierExtension)objExt;
        objSB.AppendLine("    " + ext.SubjectKeyIdentifier);
    }

    if (objExt.Oid.FriendlyName == "Enhanced Key Usage") //2.5.29.37
    {
        X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)objExt;
        OidCollection objOids = ext.EnhancedKeyUsages;
        foreach (Oid oid in objOids)
            objSB.AppendLine("    " + oid.FriendlyName + " (" + oid.Value + ')');
    }
}
Debug.Print(objSB.ToString());

Validar certificados

Una de las tareas comunes que se realizan cuando estamos trabajando con certificados es la validación de los mismos. Esta tarea incluye verificar las fechas de validez, chequear que el certificado no haya sido revocado, etc. Esta validación debe hacerse en todos y cada uno de los certificados de la cadena hasta el certificado raíz.

Para validar la cadena de certificados se puede utilizar la clase X509Chain. Esta clase posee varias propiedades para especificar la forma en la que se quiere hacer la validación, por ejemplo, en la propiedad RevocationFlag se puede establecer si queremos validar sólo el certificado final (X509RevocationFlag.EndCertificateOnly), toda la cadena excepto el raíz (X509RevocationFlag.ExcludeRoot) o la cadena completa (X509RevocationFlag.EntireChain). También puede establecerse la forma en la que se quiere validar las revocaciones a través de la propiedad RevocationMode, pudiendo seleccionar en línea (X509RevocationMode.Online), fuera de línea (X509RevocationMode.Offline) o no chequear las listas de revocados (X509RevocationMode.NoCheck).

Para iniciar la validación, en la instancia de la clase X509Chain, se debe utilizar el método Build pasándole como parámetro el certificado. Luego de esto, en la propiedad ChainStatus, vamos a encontrar la lista de errores en las validaciones o, en caso de estar vacío este vector, significa que el certificado es válido.

Acá les dejo un código de ejemplo donde validamos un certificado y toda la cadena:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
X509Chain objChain = new X509Chain();

//Verifico toda la cadena de revocación
objChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;

//Timeout para las listas de revocación
objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

//Verificar todo
objChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

//Se puede cambiar la fecha de verificación
//objChain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

objChain.Build(objCert);

if (objChain.ChainStatus.Length != 0)
    foreach (X509ChainStatus objChainStatus in objChain.ChainStatus)
        Debug.Print(objChainStatus.Status.ToString() + " - " + objChainStatus.StatusInformation);
else
    Debug.Print("Ok");

Firma de datos

Una de las tareas más comunes para las cuales se utilizan los certificados es para firmar documentos. Para esto casi siempre se utiliza el estándar de sintaxis para mensajes criptográficos PKCS #7, el cual está definido en el RFC 2315.

En .NET, para crear firmas digitales utilizando PKCS #7, se pueden utilizar las clases que están en el namespace System.Security.Cryptography.Pkcs.
Pienso que lo mejor para explicar cómo hacerte esto es a través de un ejemplo, por lo tanto, a continuación muestro un ejemplo donde creo un documento con su firma digital a partir de un texto:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Documento con firma: " + Convert.ToBase64String(bytSigned));

Otra opción, dependiendo de la forma en la que se quiere utilizar el resultado, es la de crear la firma solamente, sin el documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent, true);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Firma digital: " + Convert.ToBase64String(bytSigned));

Verificar firma

Obviamente si creamos un documento con una firma digital es porque, tarde o temprano, vamos a querer verificar esta firma. Para realizar esto utilizamos nuevamente la clase SignedCms, la cual posee el método CheckSignature, y en caso de ser un documento firmado (no la firma “suelta”), en la propiedad ContentInfo.Content va a estar el documento sin firma.

A continuación les dejo los dos ejemplos, la verificación de la firma digital:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytFirma = ... //Acá tenemos que poner la firma digital calculada en el ejemplo anterior
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

SignedCms objDatos = new SignedCms(objContent, true);

//Deserealizamos la firma
objDatos.Decode(bytFirma);

try
{
//Verificamos si la firma concuerda con los datos
objDatos.CheckSignature(true);
Debug.Print("Ok - La firma concuerda con los datos");
}
catch
{
Debug.Print("Error - La firma no concuerda con los datos");
}

Y el ejemplo de la verificación de la firma y obtención del documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDocFirmado = ... //Acá tenemos que poner el documento firmado obtenido en el ejemplo anterior
SignedCms objDatos = new SignedCms();

//Deserializo los bytes PKCS#7
objDatos.Decode(bytDocFirmado);

//Verifico la firma y obtengo el documento
try
{
    objDatos.CheckSignature(true);
    Debug.Print("Ok - La firma concuerda con los datos");
    Debug.Print(Encoding.ASCII.GetString(objDatos.ContentInfo.Content));
}
catch
{
    Debug.Print("Error - La firma no concuerda con los datos");
}

Encriptación y desencriptación utilizando un certificado

Por último voy a mostrar como encriptar y desencriptar documentos utilizando certificados digitales. Para esto, al igual con la firma de documentos, voy a mostrar un ejemplo, que creo es la mejor manera de entenderlo:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
EnvelopedCms objEncryptedData = new EnvelopedCms(objContent);

//Creamos el destino
CmsRecipient objRecipient = new CmsRecipient(objCert);

//Firmamos los datos
objEncryptedData.Encrypt(objRecipient);

//Datos encriptados
byte[] bytResult = objEncryptedData.Encode();

Debug.Pring("Datos encriptados:  " + Convert.ToBase64String(bytResult));

Y ahora voy a mostrar como desencriptar los datos obtenidos en el paso anterior. Para esto previamente tenemos que tener instalado el certificado con el que se encriptaron estos datos, junto con su clave privada, en un repositorio sobre el cual el usuario tenga permisos:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDatos = … //Datos encriptados

EnvelopedCms objEncryptedData = new EnvelopedCms();

//Leemos los datos encriptados
objEncryptedData.Decode(bytDatos);

//Desencriptamos los datos
objEncryptedData.Decrypt();

//Documento original
byte[] bytDoc = objEncryptedData.ContentInfo.Content;

//Mostramos el resultado
Debug.Print("Datos desencriptados: " + Encoding.ASCII.GetString(bytDoc));

Espero que este resumen pueda servirle a cualquier que necesite trabajar con certificados digitales desde .NET.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 8.3/10 (3 votos cast)

Notificación de cambios en enlace a Data Objects con WPF

Martes, 18 Ago, 2009 @ 18:07 | Por Dario Krapp | WPF

En un artículo previo llamado “Enlace a Data Objects en WPF” hemos comentado de que manera es posible enlazar un objeto de datos con uno o varios elementos WPF, hemos utilizado el markup Binding y modificado las propiedades Mode y UpdateSourceTrigger para controlar la transferencia de datos entre los objetos origen y destino.
Retomaremos desde el punto en el que habíamos quedado.
En nuestro articulo previo poseíamos la clase

public class Empl
{
   public int IDEmpleado { set; get; }
   public long? DNI { set; get; }
   public string Apellidos { set; get; }
   public string Nombres { set; get; }
   public DateTime FechaIngeso { set; get; }
}


La siguiente porción de XAML:

<StackPanel x:Name="stackPanelEmpl">
   <TextBox x:Name="txtIDEmpleado" Text="{Binding IDEmpleado}">   </TextBox>
    <TextBox x:Name="txtDNI" Text="{Binding DNI}"></TextBox>
    <TextBox x:Name="txtApellidos" Text="{Binding Apellidos}"> </TextBox>
    <TextBox x:Name="txtNombres" Text="{Binding Nombres}"></TextBox>
   <TextBox x:Name="txtFechaIngreso" Text="{Binding FechaIngeso}"></TextBox>
</StackPanel>


Y las siguientes líneas de código

 objE = new Empl()
   {
      Apellidos = "Perez",
      DNI = 29111222,
      FechaIngeso = new DateTime(2000, 11, 19),
      IDEmpleado = 123,
      Nombres = "Juan"
   };

    stackPanelEmpl.DataContext = objE;


El resultado de la ejecución de estas líneas era el enlace del objeto objE a los TextBoxes contenidos en el elemento stackPanelEmpl, pero este enlace tenía un problema potencial, si alguna propiedad del objeto objE era modificada externamente, el cambio no sería transferido a la interfaz gráfica. Un detalle que no habíamos comentado previamente es que la transferencia de datos desde el objeto origen hacia el objeto destino es siempre inmediato, la única excepción es que el Mode haya sido definido como OneTime, es importante recordar que la propiedad UpdateSourceTrigger (como su nombre da a entender) definirá como se actualizará la transferencia de datos en sentido inverso, o sea desde el objeto destino hacia el objeto origen, pero como ya mencionamos hace apenas dos líneas de texto la transferencia de datos desde el objeto origen hacia el objeto destino es siempre inmediata.

En este artículo nos centraremos en resolver el problema de avisar a la interfaz gráfica que una propiedad ha sido modificada y dejar que WPF haga el trabajo.

Quizás, aunque no lo parezca inicialmente, este sea un buen momento para mencionar que son las dependency properties. El uso de propiedades en los lenguajes de programación modernos no es para nada nuevo, se han venido utilizando en tecnologías previas desde hace varios años, pero WPF ha llegado con un nuevo tipo de propiedades denominadas dependecy properties, la diferencia fundamental es que en las dependency properties el valor de las mismas podría depender valores definidos en otros objetos, o sea provenir de diversas fuentes, por ejemplo de definiciones implícitas en el elemento, tanto como de la definición de las mismas en Templates, en Styles, en Resources, en Bindings, en herencia de sus elementos padre, etc. Al ser el valor de las propiedades de los elementos dependientes de tantos factores, no es una mala idea llevar a las propiedades a la mesa de dibujo e intentar redefinir una implementación que sea más eficiente en este contexto y agregarle de paso algunas características extra, como notificación de cambios, validación, etc.

Al utilizarse dependency properties se notará que se emplean como propiedades convencionales, esto no debe engañarnos, en WPF para cada dependency property existirá una propiedad convencional que funcionará como un wrapper y permitirá simplificar su utilización, pero no existe ninguna similitud. Por empezar las dependency properties serán manejadas y mantenidas por WPF, las mismas deberán declararse en un campo estático, público y de solo lectura. Además deberán registrarse, el siguiente ejemplo muestra la declaración de una dependency property en nuestra clase de ejemplo Empl destinada a almacenar los nombres del empleado:

public class Empl
   {
      public static readonly DependencyProperty NombresProperty = DependencyProperty.Register("Nombres", typeof(string), typeof(Empl));


La Dependency property ha sido llamada NombresProperty (el sufijo Property es una convención de WPF) y se ha registrado utilizando el método estático Register de la clase DependencyProperty, los parámetros pasados serán el nombre de la propiedad, el tipo de la misma y el tipo de la clase que registrará la propiedad. Como mencionamos previamente WPF se encargará de manejar y mantener el estado de las dependency properties, por ese motivo las mismas deberán registrarse (en el sistema de propiedades de WPF) y ser accesibles a nivel de clase (readonly asegurará que la dependency property no sea reasignada).

Por el momento solo hemos definido la dependency property, debemos ahora dar el proximo paso que consiste en brindar la posibilidad de establecer y leer su valor, en este punto toma importancia una clase llamada DependencyObject que representará a un objeto que formará parte del sistema de propiedades de WPF y nos brindará métodos para manejar (establecer o leer valores) la dependency property que ya hemos creado. Nuestra clase tomará la siguiente forma:

public class Empl: DependencyObject
   {
      public static readonly DependencyProperty NombresProperty = DependencyProperty.Register("Nombres", typeof(string), typeof(Empl));
      public string Nombres
      {
         set
            {
                SetValue(NombresProperty, value);
            }
            get
            {
                return GetValue(NombresProperty) as string;
            }
        }

	..

	....


Hemos heredado de la clase DependencyObject y hemos utilizado los métodos SetValue y GetValue que la misma nos ha proporcionado para manejar los valores de la dependency property, tambien hemos creado una propiedad wrapper denominada Nombres para manejar los valores de nuestra dependency property NombresProperty en forma más amigable.

La pregunta después de tanto código podría ser por que estuvimos hablando de dependency properties e hicimos todo esto. Pero en este caso (no como siempre sucede) hay un buen motivo, seguramente a estas alturas será de suponerse que utilizar
dependency properties nos permitirá resolver el problema que habíamos dejado pendiente, y efectivamente sucede de esa forma.
Con estas modificaciones la clase Empl podrá informar a elementos de la interfaz gráfica que su propiedad Nombres ha sido modificada, si en este momento la propiedad es modificada externamente, la interfaz gráfica notará el cambio inmediatamente. Ahora solo se deberá aplicar esta solución al resto de las propiedades.

Quienes hayan leído el artículo previo quizás recordarán que al mencionar las dependency properties comentamos que durante su definición era posible establecer los valores por defecto para las propiedades Mode y UpdateSourceTrigger utilizadas en el markup Binding.
Para poder definir estos valores, el método Register que ya hemos utilizado previamente cuenta con una sobrecarga que tomará un parámetro del tipo PropertyMetadata dónde podremos definir (junto con una infinidad de opciones), estas propiedades (en realidad utilizaremos un objeto del tipo FrameworkPropertyMetadata que heredará de PropertyMetadata), el código a continuación muestra una posibilidad para establecer estos valores.

public class Empl : DependencyObject
   {
        public static readonly DependencyProperty NombresProperty;

        static Empl()
        {
            FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata()
            {
                BindsTwoWayByDefault = true,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.LostFocus
            };
            NombresProperty = DependencyProperty.Register("Nombres", typeof(string), typeof(Empl), meta);
        }

	..

	....


Parecería que todo ha quedado resuelto, pero no estará de más preguntarse que pasaria si no nos fuera posible hacer que nuestra clase Empl heredará de DependencyObject (lo cual no es una suposición muy inesperada). En este caso contaremos con otra posibilidad que nos permitirá obtener el mismo resultado, pero esta vez utilizando una interfaz.

La interfaz llamada INotifyPropertyChanged nos brindará un evento denominado PropertyChanged que deberemos invocar al momento de informar que propiedad ha cambiado, en este caso no utilizaremos dependency properties, solamente deberemos invocar al evento pasándole por parámetros el nombre de la propiedad que desearemos informar que ha sido modificada y una referencia al objeto que informará la modificación.
El código para nuestro caso será el siguiente:

public class Empl : INotifyPropertyChanged
    {
        string strNombre;
        public event PropertyChangedEventHandler PropertyChanged;

        public string Nombres
        {
            set
            {
                if (value != strNombre)
                {
                    strNombre = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Nombres"));
                }
            }
            get
            {
                return strNombre;
            }
        }


Nuevamente será necesario extender la funcionalidad a cada propiedad que deseemos informar que ha sido modificada.

Finalmente cabe mencionar que existe una tercera posibilidad, la cual no requiere herencia ni interfaces. En este caso se deberá crear un evento a partir del delegado EventHandler y dispararlo cuando la propiedad haya sido modificada, en nuestro caso deberíamos utilizar las siguientes líneas de código:

    public class Empl
    {
        string strNombre;
        public event EventHandler NombresChanged = delegate { };

        public string Nombres
        {
            set
            {
                if (value != strNombre)
                {
                    strNombre = value;
                    NombresChanged(this, new EventArgs());
                }
            }
            get
            {
                return strNombre;
            }
        }


Un detalle fundamental es que para que el cambio sea informado el nombre del evento no podremos definirlo a nuestro gusto, el mismo deberá llamarse: nombre de propiedad + “Changed”, de aquí que en nuestro caso nos vimos obligados a definirlo como NombresChanged, que a decir verdad, no suena de lo mejor.

Como es de costumbre, espero que este artículo haya sido de utilidad y complemente los puntos pendiente del artículo anterior.

VN:F [1.7.3_972]
Rating: 9.0/10 (3 votos cast)

Como calcular el Hash de un vector de bytes o un string

Martes, 11 Ago, 2009 @ 18:55 | Por Gustavo Cantero (The Wolf) | .NET Framework

Muchas veces necesitamos comparar un array de bytes, verificar que una contraseña es correcta sin almacenar la original o verificar la firma digital de un documento. Para realizar esto se puede crear el Hash del set de datos original y compararlo con el del nuevo. En el caso de la contraseña, se puede almacenar el Hash de la clave original y luego verificar que este dato sea igual al de la clave a comparar, logrando de esta manera que se pueda garantizar que aunque alguien pueda ingresar en la base de datos (incluso el mismo personal de la empresa) no podrán obtener la clave ingresada del usuario.
Para calcular el Hash primero hay que crear una instancia del algoritmo a utilizar, los cuales se encuentran en el namespace System.Security.Cryptography y heredan de la clase HashAlgorithm. Ejemplos de estas clases son RIPEMD160, MD5 y SHA512.

Luego de esto simplemente tenemos que invocar el método ComputeHash de la instancia pasándole el vector de bytes del cual se desea calcular el Hash. En caso de querer calcular el de un texto primero se deberá obtener los bytes del texto, por ejemplo, utilizando el método GetBytes de algún Enconding.

El Hash es otro vector de bytes por lo cual, si queremos mostrarlo en pantalla, podríamos convertirlo a base 64.

A continuación les dejo un ejemplo de cómo calcular el Hash de un string con el algoritmo SHA512 y guardarlo en otro string:

using System.Security.Cryptography;
using System.Text;
using System;
…
string strOriginal = &quot;Cadena de la cual calcular el hash&quot;;
byte[] bytOriginal = Encoding.ASCII.GetBytes(strOriginal);
SHA512 objAlgoritmo = SHA512.Create();
byte[] bytHash = objAlgoritmo.ComputeHash(bytOriginal);
string strHash = Convert.ToBase64String(bytHash);

Por último les dejo un pequeño proyecto hecho con WPF donde se calculo el hash de un texto ingresado utilizando distintos algoritmos.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 7.0/10 (1 voto cast)

Enlace a Data Objects en WPF

Martes, 14 Jul, 2009 @ 12:28 | Por Dario Krapp | WPF

En WPF existe el concepto y la funcionalidad de enlace a datos, al igual que sucedía en tecnologías previas de Microsoft, aunque estos conceptos han evolucionado aun más permitiendo el enlace entre elementos, el enlace a recursos, a objetos ADO.NET, a Linq y a objetos de datos (también conocidos como DataObjects) en este articulo veremos el enlace a este último tipo de objeto.

Un objeto de datos representará una entidad la cual tendrá interacción con la interfaz gráfica, es muy común que dichas entidades sean persistentes, esto significa que sin importar el mecanismo de acceso a datos que se emplee, esta información terminará guardándose y recuperándose de una base de datos, aunque este es un detalle que quedará más allá del objeto de datos (o al menos así debería ser). El objeto de datos inicialmente será no más que una clase con propiedades que modelará a una entidad.
Estoy convencido que será una buena idea crear una clase lo más sencilla posible que utilizaremos como objeto de datos desde aquí en adelante.

Definiremos entonces una clase llamada Empl, que pretenderá con mucha imaginación representar a un empleado.

public class Empl
{
 public int IDEmpleado { set; get; }
 public long? DNI { set; get; }
 public string Apellidos { set; get; }
 public string Nombres { set; get; }
 public DateTime FechaIngeso { set; get; }
}

En nuestra intención de simplificar le hemos agregado solamente unos pocos campos, un identificador, un número de documento o cédula, sus nombres y apellidos y la fecha de ingreso.

En primera instancia utilizaremos esta clase para que los datos de un empleado puedan ser mostrados en la interfaz gráfica. Inicialmente intentaremos mostrar solo un dato, utilizaremos entonces un elemento de XAML denominado TextBox, que definiremos a continuación.

<TextBox x:Name="txtApe" Text="Apellido"></TextBox>

Esta porción de XAML definirá un elemento que mostrará el texto “Apellido” lo cual, como estarán convencidos, está bastante alejado de lo que deseamos hacer, pero sirve para hacer algunas observaciones, la primera es que XAML es un lenguaje basado en XML y diseñado para definir interfaces gráficas (espero que esto no sea una sorpresa), de aquí podemos deducir que la definición de la interfaz grafica estará constituida por nodos y atributos, el valor de cada atributo en el XAML definirá el valor de una propiedad del elemento que se está definiendo, en el ejemplo el atributo Text definirá que el elemento (un TextBox) con nombre txtApe tomará el valor “Apellido” en su propiedad Text.

Esto funcionando de esta forma estará perfectamente bien definido, pero será insuficiente debido a que todas las propiedades deberían tomar valores fijos, lo que haría a XAML muy poco práctico, en nuestro caso para dar un ejemplo deseábamos mostrar un apellido que dependerá del valor de una propiedad de un objeto y con lo que hemos visto hasta ahora hacer esto no es factible.

Es de esperarse entonces que exista una forma de definir valores de atributos dinámicos, WPF utilizará los caracteres “{” y “}” para indicar que el valor del atributo no es fijo, este concepto es denominado “Markup” y existe una amplia variedad de markups en XAML y WPF. Para nuestro caso estaremos interesados en definir un markup que indicará que la propiedad Text de nuestro TextBox estará enlazada a la propiedad Apellidos de un objeto que suministraremos posteriormente, el markup capaz de efectuar dicha operación es el siguiente:

<TextBox x:Name="txtApe" Text="{Binding Path=Apellidos}" />

Lo que indicamos en la línea previa es que deseamos asignar a la propiedad Text del elemento txtApe, un enlace a la propiedad Apellidos de un objeto. Una forma simplificada de definir lo mismo es la siguiente:

<TextBox x:Name="txtApe" Text="{Binding Apellidos}" />

Con esto hemos resuelto una parte del problema, debemos luego llevar a cabo el enlace con el objeto, en este momento es donde la propiedad DataContext se vuelve importante, la propiedad DataContext tomará el objeto (fuente de datos) que se enlazará con el elemento, de esta forma al escribir el código :

Empl objE = new Empl()
{
 Apellidos = "Perez",
 DNI = 29111222,
 FechaIngeso = new DateTime(2000, 11, 19),
 IDEmpleado = 123,
 Nombres = "Juan"
};
txtApe.DataContext = objE;

El objeto objE se enlazará al elemento txtApe (cuyo markup se ha definido como “{Binding Apellidos}”), el resultado final será que el valor definido en la clase para la propiedad Apellidos será enlazado a la propiedad Text del elemento txtApe.

En este punto tenemos que tomar en cuenta otra consideración y es que la propiedad DataContext es una dependency property, y tratando de no entrar en muchos detalles sobre este punto destacaremos que en WPF existen un tipo de propiedades denominadas dependency properties, y la diferencia fundamental con las propiedades convencionales es que sus valores podrían ser (además de accesibles por el propio elemento que las define) ser accesibles y por todos sus elementos hijos en el árbol lógico de elementos.

Recordemos que XAML está basado sobre XML y que cada documento XML podría representarse como un árbol, en el caso de XAML, un documento XAML definirá entre otras cosas un árbol lógico de elementos, por ejemplo la definición:

<Grid x:Name="e1">
  <StackPanel x:Name="e2">
    <TextBox x:Name="e3" ...></TextBox>
    <TextBox x:Name="e4" ...></TextBox>
  </StackPanel>
</Grid>

Indicará que el elemento e1 posee un hijo denominado e2 el cual a la vez posee los hijos e3 y e4, si la propiedad DataContext es definida en el elemento e1, su valor será accesible para el elemento e2, tanto como para los elementos e3 y e4. Las dependency properties son muy comunes en WPF y su objetivo es simplificar el código y mejorar la performance.
Por lo visto anteriormente podríamos hacer los siguientes reemplazos:

<StackPanel x:Name="stackPanelEmpl">
  <TextBox x:Name="txtApe" Text="{Binding Apellidos}"></TextBox>
  </StackPanel>
stackPanelEmpl.DataContext = objE;

Y obtendríamos el mismo resultado, ya que el DataContext definido en el elemento stackPanelEmpl es accedido por el elemento txtApe.
No caben dudas de adonde queríamos llegar con estos pasos, ahora definir el resto de los campos es muy sencillo, solo deberemos agregar más elementos y enlazar las propiedades adecuadamente.

<StackPanel x:Name="stackPanelEmpl">
  <TextBox x:Name="txtIDEmpleado" Text="{Binding IDEmpleado}"></TextBox>
  <TextBox x:Name="txtDNI" Text="{Binding DNI}"></TextBox>
  <TextBox x:Name="txtApellidos" Text="{Binding Apellidos}"></TextBox>
  <TextBox x:Name="txtNombres" Text="{Binding Nombres}"></TextBox>
  <TextBox x:Name="txtFechaIngreso" Text="{Binding FechaIngeso}"></TextBox>
</StackPanel>

Con esto hemos conseguido mostrar los valores lo cual no es poco, pero tampoco es suficiente, ya que frecuentemente desearemos que el camino sea de ida y vuelta, o sea que el usuario pueda modificar valores en la interfaz gráfica y los cambios sean transferidos a la clase subyacente.

Si se verifican los valores del objeto objE, se verá que cuando los valores son modificados en los elementos TextBox, la clase será automáticamente actualizada (hay un detalle sobre esta actualización que veremos posteriormente), para verificar este comportamiento deberá definirse un elemento Button que muestre los valores de objE al ser presionado, luego explicaremos la causa de esta imposición para mostrar los valores. Hay quienes podrían preguntarse como esta magia de actualización funciona, y lo bueno es que hay una explicación relativamente simple.

Cuando hemos definido el markup Binding, si bien todo funciona, nos han quedado algunas fichas fuera del tablero, la primera es el modo de Binding (Mode) y la segunda el modo de actualización (UpdateSourceTrigger).

Antes de ver estos términos es buena idea analizar a los actores que intervienen en un enlace, los mismos son un objeto origen, un objeto destino y un valor o datos que serán transferidos desde el objeto origen hacia el objeto destino, en nuestro ejemplo el objeto objE es la fuente y el elemento TextBox txtApellidos es el destino, el Valor “Perez” representará los datos que se transferirán desde el origen (objE) hacia el destino (txtApellidos), el modo de Binding que se definirá en el markup Binding bajo el atributo Mode, podrá definirse de la forma:

  • “de ida” (OneWay)
  • “de ida y vuelta” (TwoWay)
  • “de vuelta” (OneWayToSource)
  • “Solo una vez” (oneTime)
  • “Valor definido por defecto en la dependency property” (Default)

En el caso de “de ida” (cuando el atributo Mode toma el valor OneWay) los valores podrán transferirse solo desde el origen hacia el destino, en el caso “de ida y vuelta” (TwoWay) los datos podrán transferirse en ambos sentidos, y en el caso “de vuelta” (OneTimeToSource) los datos solo podrán transferirse desde destino hacia el origen.

Viendo esto último podemos deducir que alguien está definiendo que en el caso de los Textboxes el enlace será del tipo TwoWay, la deducción el correcta y ese alguien es justamente la propiedad Text del elemento TextBox que es también una dependency property, (de hecho solo las dependency properties aceptan el markup Binding) , y una vez más sin entrar en detalles sobre las dependency properties solo comentaremos que en una dependency property es posible definir el Mode por defecto que la misma asumirá durante un enlace, pero podemos redefinir el valor por defecto de la siguiente forma:

<TextBox Margin="3" x:Name="txtApellidos" Text="{Binding Apellidos, Mode=OneWay}" />

Luego de hacer este cambio, por más que modifiquemos el apellido en el TextBox txtApellidos, la propiedad Apellidos en el objeto objE no se verá afectada.

Finalmente nos queda por ver el modo de actualización del markup Binding y una buena forma es probar una porción de código para entender cómo funciona, Dijimos previamente que la propiedad Text de elemento TextBox estaba definida por defecto con Mode TwoWay y hemos comprobado que al modificar un valor en el TextBox y luego presionar un botón, el valor de la propiedad de objE era efectivamente modificado, entonces podríamos desear hacer la misma modificación pero esta vez vía código, elegiremos el TextBox txtNombres y ejecutaremos el siguiente código, (por ejemplo al presionar un botón):

objE = new Empl()
{
  Apellidos = "Perez",
  DNI = 29111222,
  FechaIngeso = new DateTime(2000, 11, 19),
  IDEmpleado = 123,
  Nombres = "Juan"
};

stackPanelEmpl.DataContext = objE;
txtNombres.Text = "Modificado";
MessageBox.Show(objE.ToString());

Para nuestra sorpresa veremos que la propiedad Nombres de objE no ha tomado el valor “Modificado” (para que el ejemplo funcione deberá sobrescribirse el método ToString() de la clase Empl para que los valores de sus campos sean devueltos). Pero modificaremos un poco el código y agregaremos un par de líneas extra.

objE = new Empl()
{
  Apellidos = "Perez",
  DNI = 29111222,
  FechaIngeso = new DateTime(2000, 11, 19),
  IDEmpleado = 123,
  Nombres = "Juan"
};

stackPanelEmpl.DataContext = objE;
txtNombres.Focus();
txtNombres.Text = "Modificado";
txtDNI.Focus();
MessageBox.Show(objE.ToString());

En este caso el valor será modificado. De esta prueba podríamos deducir que el valor es actualizado desde el destino hacia el origen cuando el elemento destino pierde el foco y es efectivamente de esa manera (ahora quizás se entienda por que impusimos que debiera tener que presionarse un Button para que los valores fueran mostrados luego de ser modificados en algún TextBox). La propiedad UpdateSourceTrigger definirá de que manera los datos serán modificados en el objeto origen, para que esta propiedad tenga sentido es claro que el Mode deberá ser TwoWay o OneWayToSource, ya que son los casos que admiten la modificación de objeto origen.

Las opciones de UpdateSourceTrigger son:

  • “Cuando el elemento pierde el foco” (LostFocus)
  • “Cuando la propiedad cambia” (PropertyChanged)
  • “Especificado vía código” (Explicit)
  • “Valor definido por defecto en la dependency property” (Default)

Si modificamos el XAML de la siguiente forma:

<TextBox Margin="3" x:Name="txtNombres" Text="{Binding Nombres, UpdateSourceTrigger=PropertyChanged}"></TextBox>

Veremos que ya no es necesario manejar el foco del elemento txtNombres para que la propiedad sea actualizada, nuevamente es la propiedad Text del elemento TextBox es quien define que el modo de actualización por defecto será LostFocus y nadie dudará que este caso la opción mas lógica.

Si se utiliza la opción “Explicit” por ejemplo en el TexBox DNI de la siguiente forma:

<TextBox Margin="3" x:Name="txtDNI" Text="{Binding DNI, UpdateSourceTrigger=Explicit}" />

Los cambios tomarán efecto recién cuando se ejecute por código lo siguiente:

txtDNI.GetBindingExpression(TextBox.TextProperty).UpdateSource();

Ya hemos podido resolver el problema que nos habíamos propuesto, enlazar un objeto de datos a un grupo de elementos de XAML y definir de qué forma deseamos que se comporte la actualización de datos. Dejamos para el final del artículo dos puntos que no habían sido mencionados previamente relacionados con el Mode OneTime y el Mode OneWayToSource, el Mode OneTime creará un enlace desde el objeto origen hacia el objeto destino pero con la particularidad que se transferirá solamente el primer valor que el objeto origen tome, posteriores cambios en el objeto origen serán ignorados, de allí el sugerente nombre (OneTime).

En cuanto al Mode OneWayToSource parecería en primera instancia que su existencia no tiene mucho sentido, ya que podría deducirse que si se intercambian (swap) los objetos origen por destino y se establece el Mode como OneWay no habría diferencias. En realidad no es exactamente de esta forma ya que como habíamos mencionado previamente la propiedad utilizada como destino del enlace deberá ser una dependency property, pero no la propiedad origen, (por ejemplo la propiedad Apellidos de la clase Empl no es una dependency property, la hemos definido como una propiedad convencional). Entonces este tipo de enlace permitirá modificar propiedades que no son dependency properties en un solo sentido, desde la propiedad destino hacia la propiedad fuente.

Todo este esquema que hemos desarrollado funcionará bien cuando la interacción sea exclusivamente entre la interfaz gráfica y un objeto de datos, pero en escenarios de mundo real es posible que un objeto de datos que se encuentra ya enlazado a la interfaz gráfica sea modificado externamente, una forma de simular este comportamiento es agregar un botón que modifique alguna propiedad del objeto objE luego que el enlace ha sido establecido, en este caso el objeto objE no será capaz de informar a la interfaz gráfica que una de sus propiedad ha sido modificada. Esto no es fatal, en el próximo artículo veremos que existen varias posibilidades para resolver este y otros tipos de problemas de enlace de datos. Espero como siempre que este articulo haya sido de utilidad.

VN:F [1.7.3_972]
Rating: 8.8/10 (6 votos cast)

Crear certificados de prueba para servidor y cliente

Viernes, 26 Jun, 2009 @ 16:12 | Por Gustavo Cantero (The Wolf) | ASP.NET, Certificados Digitales, 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 (13 votos 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, Redes

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: 7.6/10 (11 votos 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.8/10 (12 votos 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 voto cast)

Obtener la posición de un elemento en Silverlight

Miércoles, 29 Abr, 2009 @ 18:13 | Por Gustavo Cantero (The Wolf) | Silverlight

Muchas veces necesitamos obtener la posición de un elemento dentro de un control, pero ésta varia dependiendo de los controles “padres” y otros contenidos que pudieran haber en el XAML. Para poder conocer esta posición programaticamente se puede utilizar el siguiente código:

GeneralTransform objGeneralTransform = myObject.TransformToVisual(Application.Current.RootVisual as UIElement);
Point point = objGeneralTransform.Transform(new Point(0, 0));
double topPosition = point.Y;
double leftPosition = point.X;

Espero que les sea de utilidad.

VN:F [1.7.3_972]
Rating: 8.0/10 (3 votos cast)

Transacciones y modos de aislamiento en SQL Server y .NET

Viernes, 03 Abr, 2009 @ 12:20 | Por Dario Krapp | .NET, SQL Server

Comencemos por definir que es una transacción dentro del contexto de SQL Server, una transacción es un conjunto de operaciones Transact SQL que se ejecutan como un único bloque, si alguna operación falla, el bloque completo falla, si todas las modificaciones de los datos realizadas en la transacción tienen éxito, la misma se confirma y el conjunto de modificaciones se hacen permanentes en la base de datos.
Existen tres tipos de transacciones, las transacciones por procedimiento almacenado, las transacciones iniciadas por el cliente y las transacciones COM+ (de las cuales no hablaremos en este artículo).
Las transacciones por procedimiento almacenado tienen lugar en el motor de base de datos mientras que las iniciadas por el cliente tienen lugar en el código, para nuestro caso código .NET. En ambas circunstancias se emplea una estructura similar para el manejo de transacciones:

-- INICIO DE TRANSACCION
-- Comandos
-- ..
-- ...
-- ......
-- SI HUBO ALGUN ERROR CANCELACION DE TRANSACCION
-- SI NO HUBO ERRORES, CONFIRMACION DE TRANSACCION


En el motor de base de datos SQLServer 2005 o 2008, este esquema tomará la siguiente forma

BEGIN TRAN
BEGIN TRY
 Comandos
 ..
 ...
 ......
 COMMIT TRAN
END TRY
BEGIN CATCH
 ROLLBACK TRAN
END CATCH



En versiones previas de SQL Server los comandos de capturas de excepciones (BEGIN TRY, END CATCH, etc.) no se encuentran disponibles, pero existe en cambio la variable @@ERROR (también disponible en versiones posteriores) que puede consultarse luego de cada instrucción para saber si el comando se ejecutó exitosamente.
Desde .NET, empleando clases ADO.NET, el esquema sería el siguiente:

SqlConnection objMyConnection = new SqlConnection(strCnString);
SqlCommand objMyCommand = new SqlCommand(".....");
SqlTransaction objMyTransaction = null;
objMyCommand.CommandType = CommandType.Text;
objMyCommand.Connection = objMyConnection;
objMyConnection.Open();
objMyTransaction = objMyConnection.BeginTransaction(IsolationLevel.ReadUncommitted);
objMyCommand.Transaction = objMyTransaction;
try
{
 //comandos através de objMyCommand
 objMyTransaction.Commit();
}
catch
{
 objMyTransaction.Rollback();
}
finally
{
 objMyConnection.close();
}



Ambos esquemas son semánticamente idénticos. Un punto a considerar es que siempre es preferible que las transacciones se manejen desde el propio motor, ya que se evita el overhead adicional de la transferencia de datos, aunque esta posibilidad no es siempre factible.
Lo más interesante de las transacciones es la concurrencia, y una pregunta frecuente, al menos me parece que lo es, es ¿qué pasa con los datos dentro de una transacción cuando hay concurrencia sobre esos datos?, ¿qué sucede si una transacción modifica datos que aún no ha confirmado y otra transacción lee esos mismos datos en ese momento? ¿Existe una forma de aislar las transacciones para que no haya interferencia entre ellas?, ¿ese aislamiento podrá traer otras consecuencias?. Esos son los puntos que intentaremos recorrer en este articulo, contestando inmediatamente a las primeras preguntas que ya hemos formulado, y la respuesta es (como era de suponerse), si, existe una forma de aislar las transacciones, en realidad existe todo un abanico de posibilidades, conocido como “modos de aislamiento” o “isolation modes”.

Quizás la idea más intuitiva es esperar que no exista ningún tipo de interferencia de datos entre las transacciones, o sea, que cada una sea totalmente aislada de las otras, esto sería esperar que los datos modificados, (inserts, updates, deletes) sean vistos luego de la confirmación, por ejemplo si dentro de una transacción se borran mil registros, y otra transacción lee esos registros en el intervalo de tiempo existente luego del borrado, pero antes de la confirmación, la segunda transacción no debería ver esos registros, ya que si los viera la información sería inconsistente.
Para que esto funcione de esta forma, la segunda transacción debería esperar a que la primera termine de ejecutarse (obviamente si es que la segunda va a leer datos que la primera modificó) ya que es la única manera de asegurarse que los datos estuvisen siendo leídos luego del commit.

La idea no es nada nueva y es conocida como Read Commited y aunque funciona parece que el hecho de que las transacciones deban esperarse entre sí cuando hay cambios en los datos puede ocasionar problemas a medida que la concurrencia vaya incrementándose, no es difícil imaginarse que algunas transacciones recibirán un timeout cuando la concurrencia se incremente.

Una forma rápida de probar como funciona este esquema es crear desde el Visual Studio un método que abra una transacción y ejecute algún comando de modificación sobre una tabla (insert, update o delete), teniendo el cuidado de dejar un break en la línea de código del commit, al ejecutarse el código se abrirá una primera transacción de modificación de datos no confirmada en el servidor SQL Server. Puede entonces desde el Microsoft SQL Server Management Studio abrirse una segunda transacción y ejecutarse una sentencia de consulta de datos sobre la misma tabla (claro está que debe leer datos que hayan sido modificados por la primer transacción) . Veremos entonces que nuestra consulta en el Microsoft SQL Server Management Studio se queda en espera hasta que la línea commit en nuestro proyecto de Visual Studio es ejecutada, si no ocurre un timeout antes.

Transaccion Read Commited VS-MSSMS

un detalle a considerar es que, si una transacción update no modifica los valores de un registro (ya que los valores originales y los nuevos son iguales), el registro no se bloqueará.

Existe otro esquema de trabajo totalmente opuesto, su nombre es inevitablemente Read Uncommited, en este modo de aislamiento pasa todo lo contrario, las transacciones pueden leer los cambios de otras transacciones aunque las mismas aún no se hayan confirmado, pero estarán leyendo datos no confirmados, en este esquema no se producen los bloqueos que se daban en el caso anterior, si hacemos la misma prueba que en el caso previo, pero abriendo la transacción en el Microsoft SQL Server Management Studio como Read Uncommited (Reemplazar SET TRANSACTION ISOLATION LEVEL READ COMMITTED por SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED) veremos que no hay bloqueos y los datos son devueltos inmediatamente.
Quizás ya no se generen problemas de concurrencia, debido a que no hay bloqueos, pero es de esperarse que algo malo pueda suceder, y la frase anterior “pero estará leyendo datos no confirmados” es un indicio, las transacciones Read Uncommited leen los datos que aún no han sido confirmados, pero entonces que pasará si esos datos se cancelan con un Rollback, lo que sucede es que la transacción Read Uncommited leerá datos inválidos, este comportamiento de las transacciones Read Uncommited es conocido como Dirty Read literalmente “Lectura Sucia”, Los bloqueos en las transacciones Read Commited evitan justamente este problema.

Por todo lo visto hasta ahora no faltará quien pueda deducir que entonces las transacciones Read Commited son una mejor opción, ya que, si bien pueden bloquearse entre sí, no hay problemas en la lectura de datos. Quizás este modo de aislamiento sea una mejor opción en la mayoría de los casos, y de hecho es el modo de funcionamiento que traen las transacciones por defecto en SQL Server, pero las transacciones Read Commited pueden ocultar una sorpresa, lo cual no suele ser nada grato en ningún caso imaginable, al menos para mí, y las transacciones en SQL Server no son la exepción, solo debemos preguntarnos ¿Qué sucederá si mientras una transacción Read Commited está leyendo datos, una segunda transacción, supongamos también Read Commited modifica datos, digamos que hace updates, deletes e inserts y los confirma?, claro está que si bien nos aseguramos de no leer datos basura gracias a la espera del commit, de todas formas tendremos problemas, ya que algunos de los datos leídos podrían haberse eliminado, podrían también haberse agregado y modificado otros nuevos y la primera transacción ni se enteraría.
A las filas eliminadas, que aunque la primera transacción ve ya no existen, se las conoce como filas fantasma (phantom rows). Un efecto del escenario planteado es que si la primer transacción vuelve a leer los datos (luego que la segunda ya haya confirmado la transacción) aunque la consulta sea idéntica se obtendrán distintos resultados.
Debido a las filas fantasma (eliminadas) y a las filas actualizadas, no es posible garantizar que dos lecturas consecutivas devuelvan el mismo resultado, esto se lo conoce como “lecturas irrepetibles”, claro está que este problema surge porque las transacciones Read Commited le permiten a otras transacciones modificar datos que ellas ya han leído, recordemos que las transacciones Read Commited a lo sumo se bloquean ellas mismas cuando intentan leer datos modificados, pero no bloquean otras transacciones.

Para resolver estos problemas, existen otros modos de aislamientos aún más restrictivos que Read Commited. El modo de aislamiento Repeteable Read (Lecturas repetibles) permite, como su nombre da a imaginar, resolver el problema de las lecturas irrepetibles, ya que no le permite a otras transacciones eliminar o modificar filas leídas por la transacción actual hasta que la misma haya confirmado los cambios, otras transacciones si podrán agregar nuevos datos, se puede entender al Modo Repeteable Read como el modo Read Commited más la restricción a otras transacciones de eliminar o modificar los datos leídos por la primera, en este caso las transacciones Repeteable Read bloquean además otras transacciones para no permitirles modificar datos que ellas han leído.

Finalmente existe un nivel aislamiento aún más restrictivo que los anteriores conocido como Serializable, este nivel de aislamiento se comporta como el modo Repeteable Read, pero además tampoco le permite a otras transacciones agregar datos, en consecuencia cada transacción se ejecuta en serie una tras otra sin ninguna posibilidad de realización de tareas en paralelo.
Hasta el momento es claro que a un mayor nivel de aislamiento le corresponderá un mayor bloqueo de registros, en consecuencia, una menor escalabilidad.

SQL Server 2005, trajo un nuevo modo de aislamiento conocido como Snapshot, la idea de este modo de aislamiento es que una transacción pueda leer datos sin la posibilidad que se produzcan dirty reads, pero sin establecer bloqueos, o sea, una solución con la estabilidad de un Read Commited pero con los bloqueos de un Read Uncommited, para poder utilizar este modo de aislamiento, a diferencia de los anteriores hay que establecer en la base de datos lo siguiente:

ALTER DATABASE ... SET ALLOW_SNAPSHOT_ISOLATION ON

Desde ese momento todas las transacciones establecidas como snapshot emplearán este nivel de aislamiento, si se desea que este modo sea el modo por defecto y ya no ReadCommited, debe ejecutarse el comando:

ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON

El modo de aislamiento Snapshot mantiene las versiones de los cambios en la base de datos tempdb para cada transacción, cuando una transacción toma datos, los mismos poseen asociado un numero de versión, para que la confirmación tenga éxito es requisito que los datos a modificar no posean un número de versión mayor al que posee la transacción. Si una transacción intenta confirmar datos con una versión de cambios anterior a la actual, la operación fallará.

Un último comentario sobre este modo de aislamiento es que emplea un esquema optimista. En un esquema optimista se espera que la concurrencia a los mismos datos desde varias transacciones sea poco frecuente, por eso no es muy problemático devolver un error cuando se dá dicha situación. En cambio en el enfoque pesimista se espera que haya mucha concurrencia a los mismos datos y por tal motivo se emplean mecanismos de bloqueo para poder evitar el problema. En las aplicaciones donde se espera que una gran cantidad de usuarios concurrentes, por ejemplo las aplicaciones web tan en auje actualmente, la idea de bloquear registros parece cada vez menos atrayente, por eso el enfoque optimista parece haber tomado popularidad, En LINQ para SQL, como tecnología de punta en acceso a datos, no solo se considera el esquema optimista, sino que además de emplearlo como modo de concurrencia por defecto, posee ya incorporadas metodologías reajustar los datos en el caso de fallas por acceso a datos concurrentes, quien esté interesado y aún no haya podido ver nada del tema, puede esperar a que escribamos algún artículo o adelantarse a buscar info de esta tecnología, un tip para los segundos : RefreshMode.KeepChanges y espero como siempre, que este artículo haya sido de utilidad.

VN:F [1.7.3_972]
Rating: 9.8/10 (16 votos cast)

Silverlight 3 Beta 1

Miércoles, 18 Mar, 2009 @ 16:13 | Por Gustavo Cantero (The Wolf) | Silverlight

Para todos aquellos que están dando sus primeros pasos en Silverlight o que ya tienen alguna aplicación implementada en esta tecnología les comento que ya salió la Beta 1 de la versión 3, la cual fue presentada en el MIX09.  A continuación les dejo algunos enlaces para que puedan “jugar” con ella:

A continuación copiamos las novedades que posee esta nueva versión:

What’s New in Silverlight 3 Beta?

Fully supported by Visual Studio and Expression Blend, highlights of new features and functionality of Silverlight 3 include: major media enhancements, out of browser support allowing Web applications to work on the desktop; significant graphics improvements including 3D graphics support, GPU acceleration and H.264 video support; and many features to improve RIA development productivity. Also, in order to fully integrate all the .NET developer tools, Visual Studio 2008, Visual Studio 2010 and Visual Web Developer Express will support a fully editable and interactive designer for Silverlight. New features in Silverlight 3 include:

  • Support for Higher Quality Video & Audio. With support for native H.264/Advanced Audio Coding (AAC) Audio, live and on-demand IIS7 Smooth Streaming, full HD (720p+) playback, and an extensible decoder pipeline, Silverlight 3 brings rich, full-screen, stutter-free media experiences to the desktop. New and enhanced media features in Silverlight 3 include:
    • Live and on-demand true HD (720p+) Smooth Streaming. IIS Media Services (formerly IIS Media Pack), an integrated HTTP media delivery platform, features Smooth Streaming which dynamically detects and seamlessly switches, in real time, the video quality of a media file delivered to Silverlight based on local bandwidth and CPU conditions.
    • More format choice. In addition to native support for VC-1/WMA, Silverlight 3 now offers users native support for MPEG-4-based H.264/AAC Audio, enabling content distributors to deliver high-quality content to a wide variety of computers and devices.
    • True HD playback in full-screen. Leveraging graphics processor unit (GPU) hardware acceleration, Silverlight experiences can now be delivered in true full-screen HD (720p+).
    • Extensible media format support. With the new Raw AV pipeline, Silverlight can easily support a wide variety of third-party codecs. Audio and video can be decoded outside the runtime and rendered in Silverlight, extending format support beyond the native codecs.
    • Industry leading content protection. Silverlight DRM, Powered by PlayReady Content Protection enables protected in-browser experiences using AES encryption or Windows Media DRM.
  • Empowering Richer Experiences. Silverlight 3 contains new 3D graphics, animation features, hardware accelerated effects and text improvements that enable designers and developers to create next generation Web visuals. Additional features include:
    • Perspective 3D Graphics. Silverlight 3 allows developers and designers to apply content to a 3D plane. Users can rotate or scale live content in space without writing any additional code. Other effects include creating a queue in 3D and transitions.
    • Pixel Shader effects. These software based effects include blur and drop shadow. In addition, you can also write your own effect. Effects can be applied to any graphical content. An example would be to make a button appear depressed on rollover you could use a drop shadow effect on the pressed visual state.
    • Bitmap Caching. Silverlight 3 dramatically improves the rendering performance of applications by allowing users to cache vector content, text and controls into bitmaps. This feature is useful for background content and for content which needs to scale without making changes to its internal appearance.
    • New Bitmap API. With Silverlight 3, developers can now write pixels to a bitmap. Thus, they can build a photo editor to do red eye correction, perform edits on scanned documents or create specials effects for cached bitmaps from elements on the screen.
    • Themed application support. Developers can now theme applications by applying styles to their Silverlight 3 applications and changing them at runtime. Additionally, developers can cascade styles by basing them on each other.
    • Animation Effects. Silverlight 3 provides new effects such as spring and bounce. These make animation more natural. Developers can also now develop their own mathematical functions to describe an animation.
    • Enhanced control skinning. Silverlight 3 provides easier skinning capabilities by keeping a common set of controls external from an application. This allows the sharing of styles and control skins between different applications.
    • Improved text rendering & font support. Silverlight 3 allows far more efficient rendering and rapid animation of text. Applications also load faster by enabling the use of local fonts.
  • Improving Rich Internet Application Productivity. New features include:
    • 60+ controls with source code : Silverlight 3 is packed with over 60 high-quality, fully skinnable and customizable out-of-the-box controls such as charting and media, new layout containers such as dock and viewbox, and controls such as autocomplete, treeview and datagrid. The controls come with nine professional designed themes and the source code can be modified/recompiled or utilized as-is. Other additions include multiple selection in listbox controls, file save dialog making it easier to write files, and support for multiple page applications with navigation.
    • Deep Linking. Silverlight 3 includes support for deep linking, which enables bookmarking a page within a RIA.
    • Search Engine Optimization (SEO). Silverlight 3 enables users to solve the SEO-related challenges posed by RIAs. By utilizing business objects on the server, together with ASP.NET controls and site maps, users can automatically mirror database-driven RIA content into HTML that is easily indexed by the leading search engines.
    • Enhanced Data Support Silverlight 3 delivers:
      • Element to Element binding : UI designers use binding between two UI properties to create compelling UI experiences. Silverlight now enables property binding to CLR objects and other UI components via XAML, for instance binding a slider value to the volume control of a media player.
      • Data Forms. The Data Form control provides support for layout of fields, validation, updating and paging through data.
      • New features for data validation which automatically catch incorrect input and warn the user with built-in validation controls.
      • Support for business objects on both client and server with n-Tier data support. Easily load, sort, filter and page data with added support for working with data. Includes a new built-in CollectionView to perform a set of complex operations against server side data. A new set of .NET RIA services supports these features on the server.
    • Improved performance, through:
      • Application library caching, which reduces the size of applications by caching framework on the client in order to improve rendering performance.
      • Enhanced Deep Zoom, allows users to fluidly navigate through larger image collections by zooming.
      • Binary XML allows communication with the server to be compressed, greatly increasing the speed at which data can be exchanged.
      • Local Connection This feature allows communication between two Silverlight applications on the client-side without incurring a server roundtrip: for instance a chart in one control can communicate with a datagrid in another.
  • Advanced Accessibility Features. Silverlight 3 is the first browser plug-in to provide access to all system colors, allowing partially-sighted people to make changes such as high contrast color schemes for ease of readability by using familiar operating system controls.
  • Out of Browser Capabilities. The new out of browser experience in Silverlight 3 enables users to place their favorite Silverlight applications directly onto their PC and Mac, with links on the desktop and start menu—all without the need to download an additional runtime or browser plug-in. Further, the new experience enables Silverlight applications to work whether the computer is connected to the Internet or not—a radical improvement to the traditional Web experience. Features include:
    • Life outside the browser. Silverlight applications can now be installed to and run from the desktop as lightweight web companions. Thus, users can take their favorite Web applications with them, regardless of whether they are connected to the Internet or not.
    • Desktop shortcuts and start menu support. Silverlight applications can be stored on any PC or Mac computer’s desktop with links in the start menu and applications folder, and so are available with one-click access.
    • Safe and secure. Leveraging the security features of the .NET Framework, Silverlight applications run inside a secure sandbox with persistent isolated storage. These applications have most of the same security restrictions as traditional web apps and so can be trusted without security warnings or prompts, minimizing user interruptions.
    • Smooth installation. Because Silverlight applications are stored in a local cache and do not require extra privileges to run, the installation process is quick and efficient.
    • Auto-update. Upon launch, Silverlight applications can check for new versions on the server, and automatically update if one is found.
    • Internet connectivity detection. Silverlight applications can now detect whether they have Internet connectivity and can react intelligently including caching a users’ data until their connection is restored.

New Features in Expression Blend 3 Preview: The designer-developer workflow took another major step forward today with major innovations in Expression Blend 3 including: SketchFlow, a rapid prototyping capability that makes it easy to communicate design intent to stakeholders; design time sample data that enables the design and testing of applications without access to live data; direct import of Adobe Photoshop and Illustrator files; behaviors, extensible and reusable components that add interactivity to applications without writing code; a full code editor supporting C#, VB and XAML; and many more features that support an improved design and development experience.

  • SketchFlow. SketchFlow introduces a new set of features designed to make it easier for you to experiment with dynamic user experiences and create compelling prototypes. SketchFlow also helps communicate design ideas to other stakeholders, and makes it easier to collect in-context annotated feedback. SketchFlow enables the navigation and composition of an application to be modeled in a very visual manner from a simple prototype that uses a series of sketches, to something much more evolved. A prototype can be made as real and interactive as it needs to be to communicate design intent and SketchFlow can leverage all the existing features of Expression Blend.
  • Adobe Photoshop and Illustrator import. The powerful importers for both Adobe Photoshop and Adobe Illustrator enable smooth integration with workflows the designer already has in place. The designer has freedom to view and import Photoshop files layer by layer. Layers can be easily regrouped and elements retain their original formats; layers, layer positions, editable text and vectors remain available for editing within Expression Blend.
  • Behaviors. Add interactivity to your application, without having to write code. Behaviors can encapsulate complex design interactions into reusable components which can be directly applied to a user interface element in the application. Developers have access to a rich API that they can use to write their own triggers, actions, and behaviors for use in their Silverlight and WPF projects.
  • Sample data. Design time sample data makes it easy to build data-connected applications without access to live data. You can generate sample data or import sample data from an XML file and is available to controls on the artboard at design-time. You can extensively customize your sample data details, and you can easily switch between using sample data and using live data at run-time.
  • Improved design and development experience. Expression Blend 3 includes many features that improve the overall design experience including a brand new design surface making Blend more accessible to visual designers. Team Foundation Server support allows easier integration of the Blend user into Team System. Improved animation and easing functions, 3D transforms, visual effects and an improved visual state manager enable a great tooling experience.
VN:F [1.7.3_972]
Rating: 9.0/10 (1 voto cast)

Usando imágenes en Silverlight

Jueves, 12 Mar, 2009 @ 14:44 | Por Gustavo Cantero (The Wolf) | Silverlight

En Silverlight hay tres posibles orígenes desde donde leer imágenes para mostrarlas al usuario: imágenes publicadas en internet, archivos dentro del paquete de la aplicación (en el archivo XAP), y recursos embebidos dentro del ensamblado.
Primero hay que comentar que para que un control, como el Image o el ImageBrush, pueda mostrar una imagen hay que crear un objeto que herede de la clase ImageSource y establecerle la propiedad Source del control.  El objeto que hereda de ImageSource y que comúnmente se utiliza para la carga de imágenes es el BitmapImage, al cual hay que pasarle un Uri con la ubicación de la imagen a utilizar.  Al establecer la propiedad Source de los controles en el XAML el objeto TypeConverter de ésta es el encargado de crear el BitmapImage con la Uri correspondiente.

Imágenes publicadas en Internet

Para mostrar una imagen que está pública en internet en, por ejemplo, un control Image debemos simplemente establecer la URL en la propiedad Source del control:

<Image Source="http://www.scientia.com.ar/images/logo.png" />

Si lo queremos hacer programáticamente debemos crear un BitmapImage y pasarle la URL de la imagen en un objeto Uri.  Un ejemplo sería el siguiente:

Uri uriImage = new Uri(“http://www.scientia.com.ar/images/logo.png”);
BitmapImage objBI = new BitmapImage(uriImage);
Image objImage = new Image();
objImage.Source = objBI;

Utilizar esta manera de lectura de las imágenes tiene el beneficio que, al no estar en el paquete de la aplicación o en su ensamblado, la misma tendrá un tamaño más reducido.  También posee la ventaja de que se pueden reemplazar las imágenes en el servidor web sin modificar el paquete.  Su contra es que se irá a buscar las imágenes a internet cuando deban mostrarse.
Cabe mencionar que Silverlight posee un caché interno de los archivos que baja, por lo tanto si en una aplicación se deben mostrar muchas veces la misma imagen publicada en un servidor web, ésta se bajará solamente una vez.

Imágenes dentro del paquete de la aplicación (archivo XAP)

Para guardar una imagen dentro del paquete de la aplicación para su posterior uso simplemente hay que agregarla al proyecto de Silverlight y establecer la propiedad “Build Action” del archivo como “Content”.  Una vez hecho esto el Visual Studio agregará esta imagen al paquete en cada compilación.
Para utilizar esta imagen en el mismo ejemplo que hicimos en el punto anterior, deberíamos escribir algo como lo siguiente:

<Image Source="/images/logo.png" />

Cabe notar que la URL de la imagen debe estar precedida por una barra “/”.  Para utilizar la imagen programáticamente debemos usar el método GetResourceStream del objeto Application, al cual se le debe pasar como parámetro un Uri del tipo relativo con la dirección de la imagen, y el Stream obtenido lo utilizamos en el método SetSource del BitmapImage:

Uri uriImage = new Uri("images/logo.png", UriKind.Relative);
Stream objImageStream = Application.GetResourceStream(uriImage).Stream;
BitmapImage objBI = new BitmapImage();
objBI.SetSource(objImageStream);
Image objImage = new Image();
objImage.Source = objBI;

Este método permite modificar las imágenes del paquete sin necesidad de volver a compilar el código, solamente hay que descomprimir el archivo XAP (en realidad es un archivo ZIP), reemplazar las imágenes que se necesiten, y volver a comprimir los archivos.  La contra es que, al estar las imágenes dentro del paquete, el tamaño del mismo será mayor al del punto anterior.

Imágenes embebidas en el ensamblado

Por último, para guardar las imágenes como recursos embebidos dentro del ensamblado de la aplicación, hay que agregar los archivos al proyecto y verificar que la propiedad “Build Action” del mismo sea “Resource” (ojo, no “Embedded Resource”), el cual es el valor por defecto que le establece Visual Studio al agregar archivos a un proyecto Silverlight.
Una vez hecho esto podemos utilizar las imágenes desde el código XAML de la misma manera que utilizamos los archivos agregados al paquete, pero sin poner la barra inicial en el Uri del mismo, por ejemplo:

<Image Source="images/logo.png" />

La forma de utilizar esta imagen programáticamente es muy similar a la utilizada en el punto anterior, pero con una sintaxis particular en el Uri, la cual debe tener esta sintaxis: Ensamblado;component/Recurso.  Como ejemplo mostraremos el código del punto anterior, pero utilizando la imagen embebida:

Uri uriImage = new Uri("ImagenesEnSilverlight;component/images/logo.png", UriKind.Relative);
Stream objImageStream = Application.GetResourceStream(uriImage).Stream;
BitmapImage objBI = new BitmapImage();
objBI.SetSource(objImageStream);
Image objImage = new Image();
objImage.Source = objBI;

Proyecto de ejemplo de Uso de imágenes en SilverlightComo siempre, les dejo un proyecto de ejemplo donde se utilizan los tres métodos de lectura de imágenes declarativamente y programáticamente.

VN:F [1.7.3_972]
Rating: 8.9/10 (7 votos 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 votos 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.5/10 (4 votos cast)

Ventanas modales en Silverlight

Martes, 03 Feb, 2009 @ 21:54 | Por Gustavo Cantero (The Wolf) | Silverlight

En Silverlight, como es sabido, no se pueden crear ventanas modales, por lo cual vamos a mostrar como simular esta funcionalidad utilizando un Popup.

El Popup es un objeto del framework de Silverlight 2 que permite mostrar el contenido de un elemento sobre el resto. Con esto podríamos simular la ventana que queremos crear, pero tenemos el problema que alrededor de esta ventana se puede cliquear e interactuar con los elementos que están “por debajo” de ésta. Para evitar esto lo que haremos es crear un elemento “Border” que ocupe toda la página de Silverlight, el cual debe tener un fondo para evitar que el usuario pulse sobre los elementos que están por debajo. Para el fondo podemos utilizar, por ejemplo, el objeto SolidColorBrush con el color negro y una opacidad de 20, para que se pueda ver a través él (si no queremos que se grisee esta zona se puede utilizar el color Colors.Transparent).

El siguiente inconveniente que si el control de Silverlight ocupa un porcentaje de la ventana del navegador (o sea, no tiene un tamaño fijo) y el usuario modifica el tamaño del browser, la sección griseada de la ventana debería modificarse para seguir ocupando el cien por ciento de la página. Para solucionar esto se puede manejar el evento Resized del objeto Application.Current.Host.Content, estableciendo ahí el tamaño del borde del Popup al tamaño actual del objeto Application.Current.RootVisual.

Por último, también tenemos el problema de que, si al Popup le agregamos elementos como un ListBox que tiene interacción con el usuario y necesitamos manejar sus eventos, el Silverlight nos devolverá un error. Para evitar esto simplemente tenemos que agregar el Popup al elemento raíz de nuestra página y, al cerrarlo, quitarlo.

A continuación muestro el código de un método estático

/// <summary>
/// Muestra un objeto en una ventana modal simulada a través de un <see cref="Popup"/>
/// </summary>
/// <param name="Content">Contenido a mostrar en la ventana</param>
public static Popup Show(UIElement Content)
{
    //Botón "cerrar"
    Button btnClose = new Button();
    btnClose.Content = new TextBlock() { Text = "Cerrar" };
    btnClose.HorizontalAlignment = HorizontalAlignment.Center;
    btnClose.Margin = new Thickness(0, 10, 0, 0);

    //Grilla para ubicar el contenido y el botón
    Grid objGridContent = new Grid();
    objGridContent.RowDefinitions.Add(new RowDefinition());
    objGridContent.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
    objGridContent.Children.Add(Content);
    objGridContent.Children.Add(btnClose);
    Grid.SetRow(btnClose, 1);

    //Borde de la ventana
    Border objPopupBorder = new Border();
    objPopupBorder.CornerRadius = new CornerRadius(10);
    objPopupBorder.Background = new SolidColorBrush(Colors.White);
    objPopupBorder.BorderThickness = new Thickness(2);
    objPopupBorder.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 128, 128, 128));
    objPopupBorder.Child = objGridContent;
    objPopupBorder.Padding = new Thickness(10);
    objPopupBorder.Margin = new Thickness(20);
    objPopupBorder.HorizontalAlignment = HorizontalAlignment.Center;
    objPopupBorder.VerticalAlignment = VerticalAlignment.Center;

    //Fondo de la ventana
    Border objBackBorder = new Border();
    objBackBorder.Width = Application.Current.RootVisual.RenderSize.Width;
    objBackBorder.Height = Application.Current.RootVisual.RenderSize.Height;
    objBackBorder.Background = new SolidColorBrush(Color.FromArgb(70, 0, 0, 0));
    objBackBorder.Opacity = 1;
    objBackBorder.Child = objPopupBorder;

    //Popup
    Popup objPopup = new Popup();
    objPopup.Child = objBackBorder;
    ((Application.Current.RootVisual as UserControl).FindName("LayoutRoot") as Grid).Children.Add(objPopup);
    objPopup.Closed += delegate(object sender, EventArgs e)
    {
        //Saco el popup de la grilla
        UIElementCollection objRootElements = ((Application.Current.RootVisual as UserControl).FindName("LayoutRoot") as Grid).Children;
        if (objRootElements.Contains(objPopup))
            objRootElements.Remove(objPopup);
    };
    objPopup.IsOpen = true;

    //Cerramos el popup al pulsar en el botón
    btnClose.Click += delegate(object sender2, RoutedEventArgs e2) { objPopup.IsOpen = false; };

    //Cambio el tamaño del fondo cuando cambia de tamaño el control
    Application.Current.Host.Content.Resized += delegate(object sender, EventArgs e)
    {
        objBackBorder.Width = Application.Current.RootVisual.RenderSize.Width;
        objBackBorder.Height = Application.Current.RootVisual.RenderSize.Height;
    };

    return objPopup;
}

Como siempre, les dejo un proyecto con este código funcionando para que lo prueben. Proyecto de ejemplo de Popup Modal en Silverlight

VN:F [1.7.3_972]
Rating: 8.4/10 (7 votos cast)

Llamar a funciones de JavaScript desde Silverlight y a métodos de Silverlight desde JavaScript

Domingo, 04 Ene, 2009 @ 20:26 | Por Gustavo Cantero (The Wolf) | JavaScript, Silverlight

Ejecutar JavaScript desde Silverlight

Al desarrollar con Silverlight muchas veces necesitamos que nuestro control interactúe con el resto de la página, por ejemplo, con un botón de HTML o con un DIV, colocando dentro algún texto. Para poder lograr esto el framework de .NET que posee el Silverlight nos ofrece la clase HtmlPage, la cual se encuentra en el namespace System.Windows.Browser. Esta clase posee varias propiedades y métodos estáticos para interactuar con el navegador del cliente, por ejemplo, la propiedad BrowserInformation nos brinda información del navegador, pudiendo saber su versión a través de la siguiente propiedad: HtmlPage.BrowserInformation.BrowserVersion, o la plataforma sobre la cual está corriendo con esta otra: HtmlPage.BrowserInformation.Platform.
Para poder interactuar con la página que contiene a nuestro control en el cliente podemos utilizar la propiedad HtmlPage.Window, la cual posee una instancia del objeto window del navegador, pudiendo, por ejemplo, crear un “alert” con la siguiente línea:

HtmlPage.Window.Alert("Hola mundo!");

crear un diálogo de confirmación con esta otra:

bool bolOK = HtmlPage.Window.Confirm("¿Está seguro de hacer eso?");

o simplemente navegar a otra página:

HtmlPage.Window.Navigate("http://www.silverlight.net");

Pero si lo que necesitamos es llamar a una función creada en JavaScript, podemos utilizar el método Invoke, el cual toma como primer parámetro el nombre de la función a ejecutar, y los subsiguientes parámetros se los pasa a la función. El valor devuelto por el método es el retornado por la función ejecutada (si es que devuelve algún valor). Como ejemplo vamos a ejecutar la llamada a la función “alert” pero a través del método invoke:

HtmlPage.Window.Invoke("alert", "Hola mundo!");

Cabe mencionar que para que nuestra aplicación pueda interactuar con la página necesitamos agregar el parámetro HtmlAccess="Enabled" en la creación del objeto de Silverlight.

Ejecutar métodos de Silverlight desde JavaScript

También puede darse el caso que necesitemos llamar a algún método de nuestro control al ejecutarse algún evento de la página, por ejemplo, al pulsarse un botón de HTML, para lo cual necesitamos registrar nuestro objeto para que pueda ser accedido a través de JavaScript y el método (o todos los miembros) de la clase también como accesibles.
Comencemos por el primer paso: para registrar nuestro objeto, por ejemplo, el control, simplemente necesitamos ejecutar el método RegisterScriptableObject, pasándole como primer parámetro el nombre de la propiedad de JavaScript a través de la cual será accedido, y como segundo parámetro la instancia del mismo, por ejemplo:

HtmlPage.RegisterScriptableObject("Page", this);

El siguiente paso es registrar a la clase completa para que pueda ser accedida a través de JavaScript con el atributo ScriptableType, o registrar sólo los métodos que se necesitan usar, a través del atributo ScriptableMember.
A continuación muestro como ejemplo una aplicación Silverlight que posee marcado como accesible desde JavaScript un método que cambia el texto un botón del mismo.

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();
        HtmlPage.RegisterScriptableObject("Page", this);
    }
    [ScriptableMember]
    public void ChangeButtonText(string Text)
    {
        TextBlock objTB = new TextBlock();
        objTB.Text = Text;
        Button1.Content = objTB;
    }
}

Y la página que utiliza este método:

<body style="height: 100%; margin: 0; background-color: Blue">
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <input type="button" onclick="$get('Xaml1').Content.Page.ChangeButtonText('Html')"
        value="Prueba" />
    <div>
        <asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/SilverlightApplication5.xap"
MinimumVersion="2.0.31005.0" Width="500" Height="300" Windowless="true" HtmlAccess="Enabled" />
    </div>
    </form>
</body>

Por último, dejo un ejemplo creado con Visual Studio® 2008 donde se muestra un botón en una aplicación de Silverlight que cambia el texto de un botón de HTML, y un botón de HTML que cambia el texto de un botón de Silverlight.
Proyecto de ejemplo de llamadas entre JavaScript y Silverlight
Espero que este artículo les sea de utilidad.

VN:F [1.7.3_972]
Rating: 8.5/10 (12 votos cast)

Tablas de dispersión, de la teoría a la práctica

Viernes, 05 Dic, 2008 @ 14:06 | Por Dario Krapp | .NET, Algoritmos/TAD

Poco uso práctico tendría la elaboración de programas, si dichos programas no pudiesen interactuar con la información brindada por sus usuarios, dicha información define el problema puntual que un programa o grupo de programas debe resolver, sin importar la clase de problema a solucionar o tipo de algoritmo implementado, los datos que un programa toma para generar cualquier solución y como dichos datos son manejados, son en muchas ocasiones el factor determinante de su éxito o fracaso.
Cuando la cantidad de datos que un algoritmo debe manejar son pocos, la forma de almacenarlos y recuperarlos no determina un factor influyente en la performance del programa. Pero a medida que la cantidad de datos a manejar se incrementa, la velocidad con la que los datos son almacenados y recuperados se vuelve en ocasiones el mayor de los problemas, posiblemente por esta cuestión, es que este problema ha sido abordado en miles de ocasiones por infinidad de personas y no en vano, han encontrado algunas soluciones que podrían sernos de utilidad. Veamos en primera instancia la solución más inocente para el almacenamiento de una gran cantidad de datos, la idea más sencilla para almacenar un grupo de elementos, es sin ninguna duda emplear un arreglo donde en cada posición se almacenará un elemento, esto ya trae problemas desde el principio debido a que no es frecuente conocer cuántos elementos se deben almacenar, y no es muy performante la idea de ir copiando el arreglo cuando una redimension es necesaria.
La algoritmia y diseño de estructuras de datos permite crear a partir de las unidades de almacenamiento más elementales como arreglos y punteros, tipos de datos cada vez más complejos y abstractos como listas, árboles, diccionarios, etc., apoyados unos sobre otros al igual que los ladrillos en una pared. Empleando estructuras de datos es posible intentar resolver el problema de la redimension de los arreglos empleando quizás una lista, de la cuales hay diversas opciones a elegir(simplemente encadenada, doblemente encadenada, circular, etc.) dependiendo del tipo de problema que se deba resolver, esta opción resuelve el problema de la redimension en forma eficiente, ya que es posible crear y eliminar elementos con un costo mínimo, pero no resuelve otro problema que es el de la búsqueda de elementos, es claro que en una lista encontrar un elemento es bastante costoso, ya que se debe ir recorriendo elemento por elemento hasta poder encontrar el deseado, si existen n elementos, el algoritmo deberá recorrer los n elementos (en el peor caso claro está) para encontrar el elemento buscado, esto suele denominarse como O(n) o como que el orden del algoritmo es n, el orden de un algoritmo determina su eficiencia.
En este punto es donde se ha intentado minimizar el problema de las búsquedas. la algoritmia y diseño de estructuras de datos ha aportado diversas soluciones a este problema, por ejemplo la búsqueda binaria ha permitido mejorar el orden desde n a log2(n), un orden logarítmico es una solución bastante buena en contraposición al orden lineal que habíamos obtenido previamente. Un punto a considerar es que en el caso de la búsqueda binaria hay un costo extra en el momento de la inserción de elementos, debido a que los valores deben insertarse ordenadamente, dependiendo de la cantidad de inserciones en contraposición a las búsquedas esta solución puede ser interesante. Otra opción es emplear una Tabla de dispersión, que es una solución que bajo ciertas condiciones resuelve el problema con una performance aceptable y una ventaja que posee es que es relativamente sencilla de implementar.

Para comprender la técnica supongamos que tenemos e1, e2… ei elementos pertenecientes al conjunto E, donde E es el conjunto con el total de elementos con los que operaremos.

Conjunto de elementos

supongamos también que existe una función H capaz de tomar un elemento ei y devolver otro elemento de otro tipo (no importa que tipo sea mientras que soporte la operación de comparación, debe ser posible determinar si dos elementos que devuelve la función H son iguales o no lo son) esta idea es bastante similar a la noción de función matemática que se estudiaba en la escuela primaria, donde existen un dominio, una imagen y una función capaz de relacionar uno o varios elementos del dominio con otro de la imagen, el secreto de esta técnica es la idea de que con nuestra función podamos dividir el conjunto total de elementos E en varios subconjuntos (también llamados “buckets”) para poder buscar entre menos valores, con esta idea tendremos entonces todo el conjunto E dividido en subconjuntos menores donde cada subconjunto posee la siguiente particularidad, para cada uno de sus elementos la función H devuelve el mismo valor, o sea, existe una relación de equivalencia entre los elementos de cada subconjunto.

División en subconjuntos

Aunque aún no conocemos quién es la función H, sabemos que cuanto más podamos dispersar el grupo de elementos de E mejor será la función ya que cuando deseemos encontrar un elemento tendremos que buscarlo entre menor cantidad de elementos dentro de algún subconjunto.
A continuación veremos una de las implementaciones más clásica de tablas de disperción, la misma emplea como estructura soporte de datos un arreglo de subconjuntos.

Arreglo de subconjuntos de E

A la vez cada subconjunto suele implementarse sobre una lista simplemente encadenada, aunque podrían emplearse otras estructuras de datos.

Arreglo de listas encadenadas

La idea de esta implementación es que cada celda del arreglo o bucket contenga un subconjunto de E, si dado un elemento la función de dispersión es capaz de seleccionar una celda dentro del arreglo, simplemente deberemos incluir el elemento en el bucket correspondiente y el problema quedará resuelto, la búsqueda es similar.
La cantidad de buckets a emplear (longitud del arreglo de subconjuntos) también influirá en la performance, en muchas implementaciones es posible elegir la cantidad de buckets que desean emplearse al momento de crear la instancia de la clase.
No cabe duda que la elección de la función de dispersión es el punto clave de la implementación, una mala elección podría hacer que los elementos se almacenen por completo dentro de un mismo bucket quedando todos los demás vacios y en ese caso la estructura que estaríamos utilizando seria una lista simplemente encadenada, pero con aun mas complejidad agregada, hay mucha investigación en este campo y pueden encontrase algunas funciones de hash clásicas, un ejemplo para elementos del tipo de dato cadena de caracteres es djb2, presentada por Dan Bernstein en comp.lang.c


unsigned long
hash(unsigned char *str)
{
 unsigned long hash = 5381;
 int c;
 while (c = *str++)
  hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
 return hash;
}


Y pueden encontrase miles más como sdbm, lose lose, one at time, etc. Cada una de ellas realizando las operaciones más extraordinarias imaginables sobre los datos de entrada, un punto importante es que la función H sirve para solo algunos tipos de datos, si los elementos son un tipo complejo, como por ejemplo “automovil”, “empleado” la función de hash planteada previamente no podrá realizar ningún cálculo.
El uso de funciones de dispersión criptográficas, como por ejemplo SHA-1 también pueden utilizarse, ya que da un buen resultado, aunque su cálculo puede ser costoso.
Existen diversos modelos de tablas de dispersión, en nuestro primer caso estamos viendo el modelo de dispersión abierta (open hashing), pero existen también: Chaining, Coalesced hashing, Perfect hashing, Dynamic perfect hashing, Probabilistic hashing, Robin Hood hashing, etc. Chaining o Hashing cerrado lo veremos más adelante.
Un uso frecuente para las tablas de dispersión es el modelado de diccionarios, un diccionario es una estructura de datos compuesta por un grupo de elementos que poseen una clave y un valor, un diccionario permiten almacenar, buscar, eliminar, determinar si existen valores a través de su clave, la cual debe ser única por cada elemento del diccionario, en estos casos las operaciones de dispersión se efectúan sobre la clave.
Debe quedar claro que si bien los diccionarios pueden implementarse muy bien sobre una tabla de dispersión, también pueden existir otros tipos de datos que podrían implementarse sobre esta estructura como por ejemplo un conjunto, así como también un diccionario podría implementarse sobre un árbol o sobre una lista encadenada (lo cual no sería una muy buena idea en el común de los casos).
Muchos lenguajes modernos utilizan tablas de dispersión, un ejemplo es C# que proporciona una implementación en System.Collections.Generic.Dictionary.
Otra implementación puede encontrarse en System.Collections.Hashtable, pero la misma difiere un poco de lo comentado y la veremos luego.

En el caso de Dictionary de .NET se implementa un diccionario fuertemente tipado (Generics) sobre una tabla de dispersión abierta, siguiendo la idea de valor clave mencionada previamente. un punto interesante es la función de hash, funciona sobre cualquier tipo de dato, por ejemplo:


Dictionary<SampHashKey, string> oD1
Dictionary<int, string> oD2
Dictionary<string, int> oD3


Para poder tener una función de dispersión genérica .NET hace lo siguiente, la clase object posee un método llamado GetHashCode que devuelve un valor entero (int) con un código, la función GetHashCode de alguna forma permite mapear cualquier objeto a un numero entero, GetHashCode es empleado cuando se agrega, busca u opera con un elemento en la tabla de dispersión, para darle una entrada a la función de hash. Quien no esté totalmente convencido puede hacer la siguiente prueba:


public struct SampHashKey
{
 public int a { set; get; }
 public int b { set; get; }
 public int c { set; get; }
 public int d { set; get; }
}
public override int GetHashCode()
{
 MessageBox.Show("llamando a GetHashCode");
 return base.GetHashCode();
}

public void Test()
{
 Dictionary<SampHashKey, string> oD = new Dictionary<SampHashKey, string>();
 oD.Add(sSHK1, "s");
}


Al invocarse al método Add de oD, aparecerá el cuadro de texto con el mensaje “llamando a GetHashCode”. Esta es una idea inteligente y quizás la única posibilidad de poder emplear una misma función de hash para cualquier tipo de dato, permitiéndole a la instancia tabla de dispersión sobre la cual se encuentra implementado el Dictionary seleccionar un bucket donde almacenar los datos para toda clave posible. Esta metodología elegante y simple, sigue apoyándose una buena elección de función de dispersión y en una buena elección de función GetHashCode. Intuitivamente nos es lógico pensar que más allá de lo que haga la función de dispersión con el código de hash que devuelve GetHashCode, si varios objetos devuelven el mismo GetHashCode, todos ellos irán a parar inevitablemente al mismo bucket.¿Pero es posible que varios elementos tengan un mismo HashCode?, consideremos el siguiente ejemplo:


public struct SampHashKey
{
 public int a { set; get; }
 public int b { set; get; }
 public int c { set; get; }
 public int d { set; get; }
}

public void Test()
{

 SampHashKey sSHK1 = new SampHashKey() { a = 1, b = 2, c = 3, d = 4 };
 SampHashKey sSHK2 = new SampHashKey() { a = 2, b = 1, c = 3, d = 4 };
 SampHashKey sSHK3 = new SampHashKey() { a = 2, b = 3, c = 1, d = 4 };
 SampHashKey sSHK4 = new SampHashKey() { a = 2, b = 3, c = 4, d = 1 };
 SampHashKey sSHK5 = new SampHashKey() { a = 3, b = 2, c = 4, d = 1 };
 SampHashKey sSHK6 = new SampHashKey() { a = 3, b = 4, c = 2, d = 1 };

 Hashtable oHT = new Hashtable();
 oHT.Add(sSHK1, "Valor1");
 oHT.Add(sSHK2, "Valor2");
 oHT.Add(sSHK3, "Valor3");
 oHT.Add(sSHK4, "Valor4");
 oHT.Add(sSHK5, "Valor5");
 oHT.Add(sSHK6, "Valor6");

 int iSHK1Hash = sSHK1.GetHashCode();
 int iSHK2Hash = sSHK2.GetHashCode();
 int iSHK3Hash = sSHK3.GetHashCode();
 int iSHK4Hash = sSHK4.GetHashCode();
 int iSHK5Hash = sSHK5.GetHashCode();
 int iSHK6Hash = sSHK6.GetHashCode();

 if (sSHK1.GetHashCode() == sSHK2.GetHashCode() &&
 sSHK2.GetHashCode() == sSHK3.GetHashCode() &&
 sSHK3.GetHashCode() == sSHK4.GetHashCode() &&
 sSHK4.GetHashCode() == sSHK5.GetHashCode() &&
 sSHK5.GetHashCode() == sSHK6.GetHashCode())
   MessageBox.Show("¡Ouch!");
}


Para quien no tenga ganas de cargar el IDE y probar el código en un WinForm, le comento que al invocar al método Test, un cuadro de texto aparecerá en pantalla indicando que varios objetos distintos pueden tener un mismo HashCode y no es para sorprenderse, ya que el ejemplo fue preparado para que esto sucediera.
En casos del mundo real las posibilidades de que esto suceda son menores, en realidad dependerán del objeto key seleccionado y los valores que el mismo posea, pero de todas formas es bueno saber que puede suceder y mejor aun, es saber que se puede solucionar gracias a la flexibilidad del diseño de las clases de .NET.

Consideremos lo siguiente:


public struct SampHashKey
{
 public int a { set; get; }
 public int b { set; get; }
 public int c { set; get; }
 public int d { set; get; }

 public override int GetHashCode()
 {
  return a + Convert.ToInt32(Math.Pow(b, 2)) + Convert.ToInt32(Math.Pow(c, 3)) +   Convert.ToInt32(Math.Pow(d, 4));
 }
}



Sobrecargando el método GetHashCode apropiadamente, hemos conseguido devolver nuevos y distintos valores. Una vez más cabe mencionar que esta sobrecarga de la función GetHashCode sirve solo para este ejemplo puntual, seguramente debe funcionar desastrosamente para otro conjunto de valores. Pero si un caso de este tipo se diera en la vida real, se deberá enfocar hacia una solución similar, evaluar el conjunto de valores posibles y puntualmente encontrar un reemplazo adecuado para el método GetHashCode, la complejidad u orden de este diccionario solo puede calcularse en forma estadística, ya que en el peor de los casos será o(n) cuando todos los elementos se agrupen en un mismo bucket, pero claro que esto no es muy probable. el orden en promedio de un Dictionary es o(n/m) siendo n la cantidad de elementos y m la cantidad de buckets.

Como ya hemos mencionado, existen otros tipos de tablas de dispersión, en las tablas de dispersión cerradas, cada bucket puede contener un solo elemento, por lo que cuando otro elemento es mapeado al mismo bucket se produce una colisión la cual debe resolverse, en tal caso se emplean funciones de rehashing para encontrar una nueva ubicación para el elemento, la función de rehashig suele incluir como parámetro de entrada el numero de intento, ya que no es sabido cuantas colisiones se producirán hasta finalmente poder alojar al elemento.

Como ya habíamos mencionado .NET ofrece otra implementación de tabla de dispersión, pero en este caso no se implementa sobre una tabla de hash abierta. La clase de System.Collections.HashTable que es débilmente tipada sigue empleando el método GetHashCode para poder mapear cualquier objeto, y es otro diccionario implementado sobre una tabla de dispersión con ciertas particularidades, la estructura de datos permite alojar un elemento por celda, por lo que cuando otro elemento sea mapeado a la misma celda se producirá una colisión, la clase empleará una funciones de rehashing hasta que el ítem pueda reubicarse, este esquema posee también lo que se denomina factores de carga “Load Factors” y que se manifiesta como una proporción siendo un valor numérico entre 0.1 y 1.0 (que se establece en el constructor de la clase) e indica la máxima proporción de elementos en los buckets, por ejemplo un factor de carga de 0.5 indica que a lo sumo, la tabla de dispersión tendrá la mitad (0.5) de buckets completa, lo ideal sería pensar que el valor óptimo es 1, ya que le pide a la tabla de hash que intente llenar todos los buckets, pero el valor mágico es 0.72 y es el que se emplea por defecto si no se le establece otro en el constructor, inclusive si se establece el valor 1.0 el mismo será automáticamente escalado a 0.72. Otra particularidad es que la tabla puede crecer de tamaño si los factores de carga se encuentran comprometidos, si bien los factores de carga son costosos de mantener aseguran que los elementos se distribuyan de la forma más pareja posible en los buckets.

A continuación y para cerrar el articulo presentaremos un par de funciones muy interesantes:


H(key) = [GetHash(key) + 1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1))] % hashsize

Hk(key) = [GetHash(key) + k * (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1)))] % hashsize

key = GetHashCode()
hashsize = cantidad de buckets
k = número de intento


Estas funciones son, como han de imaginarse, las funciones de hash y rehash empleadas por la clase HashTable de .NET. las mismas pueden encontrarlas, junto con más información referente en el artículo:
http://msdn.microsoft.com/en-us/library/aa289149.aspx escrito por Scott Mitchell.

 

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