Archivo de la categoría '.NET'


Betas de exámenes de certificación de .NET 4.0

Miércoles, 17 Mar, 2010 @ 18:17 | Por Gustavo Cantero (The Wolf) | .NET, Certificaciones


El día de hoy Microsoft ha lanzado una invitación a los desarrolladores a dar GRATUITAMENTE los exámenes de certificación para .NET Framework 4.0 en su modalidad beta. Quienes aprueben alguno obtendrán la certificación de la misma manera que si fueran los exámenes finales. Los exámenes disponibles son:

  • Exam 71-511, TS: Windows Applications Development with Microsoft .NET Framework 4
  • Exam 71-515, TS: Web Applications Development with Microsoft .NET Framework 4
  • Exam 71-513: TS: Windows Communication Foundation Development with Microsoft .NET Framework 4
  • Exam 71-516: TS: Accessing Data with Microsoft .NET Framework 4
  • Exam 71-518: Pro: Designing and Developing Windows Applications Using Microsoft .NET Framework 4
  • Exam 71-519: Pro: Designing and Developing Web Applications Using Microsoft .NET Framework 4

Cabe mencionar que hay un cupo limitado utilizar los código de promoción para rendir gratuitamente estas certificaciones, por lo cual los insto a inscribirse lo antes posible para no quedar afuera.

Los códigos de promoción de cada examen y demás información lo pueden encontrar en el blog de Microsoft Learning: http://borntolearn.mslearn.net/btl/b/weblog/archive/2010/03/17/register-for-visual-studio-2010-beta-exams.aspx.

Es una oportunidad que no se puede perder.

Suerte a todos!

Agregado el 19/03/2010
En el día de hoy agregaron 150 lugares más para cada examen utilizando los mismos códigos, los cuales copio acá:

Examen Beta Code
71-511 511BC
71-515 515AA
71-513 513CD
71-516 516B1
71-518 518PE
71-519 519ZS

Suerte a todos!

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

Introducción a la programación con XNA 3.1 y C#

Jueves, 04 Mar, 2010 @ 14:16 | Por Gustavo Cantero (The Wolf) | XNA

XNAXNA es un conjunto de herramientas y librerías de Microsoft que facilita el desarrollo de videojuegos para PC, XBOX y Zune, y cada vez está siendo utilizado más por estudiantes y principiantes para aprender a programar utilizando C# como lenguaje.

A Simple Introduction to Game Programming With C# and XNA 3.1 En el libro “A Simple Introduction to Game Programming With C# and XNA 3.1″ se explica como desarrollar videojuegos a aquellos que no tienen ningún conocimiento de programación, enfocado en los conceptos y fundamentos de XNA.

A quien le interese puede descargar gratuitamente de http://www.lulu.com/product/download/a-simple-introduction-to-game-programming-with-c%23-and-xna-31/5438606 o, si lo prefieren, pueden consultarlo y leerlo on-line en http://xnagamemaking.com. Si prefieren tener el libro “físico”, pueden comprarlo en http://www.lulu.com/content/7658212.

El centro de desarrollo de XNA pueden encontrarlo en http://msdn.microsoft.com/es-ar/aa937791.aspx y pueden descargar el XNA Game Studio 3.1, su documentación y otras herramientas de http://creators.xna.com/es-ar/downloads

VN:F [1.7.3_972]
Rating: 7.5/10 (2 votos cast)

Microsoft Ajax Minifier 4.0

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

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

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

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

Traductor gratuito de recursos de .NET

Jueves, 14 Ene, 2010 @ 02:25 | Por Gustavo Cantero (The Wolf) | .NET Framework

Scientia® Resource TranslatorEn Scientia® Soluciones Informáticas desarrollamos una aplicación sencilla pero muy útil utilizando los servicios de traducción que ofrece BingTM: creamos un traductor de recursos de .NET. La idea es muy sencilla, abre un archivo de recursos seleccionado por el usuario (un archivo .resx) y crea los archivos de recursos en los idiomas requeridos. Por ejemplo, podemos tomar un recurso con textos en español y crear los archivos con los textos en inglés, portugués, japonés y tailandés.

Cabe mencionar que este software lo creamos sin fines de lucro, o sea, para utilizarlo y distribuirlo gratuitamente.

A continuación les dejo una captura de la pantalla:

Ventana del Scientia® Resource Translator

A modo de prueba traducimos los textos de la misma aplicación, y ahora soporta más de 20 culturas, lo cual se puede visualizar en el menú de selección de idioma:

Menú de idiomas del Scientia® Resource Translator

Para quien desee utilizarlo o probarlo lo invito a descargarlo de la página de nuestra empresa: http://www.scientia.com.ar/descargas.aspx

Espero que esta herramienta les sea de utilidad.

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

Globalización y localización en Silverlight

Viernes, 08 Ene, 2010 @ 16:30 | Por Gustavo Cantero (The Wolf) | Silverlight

Si uno programó con ASP.NET y comienza a desarrollar una aplicación en Silverlight en múltiples idiomas lo primero que se pregunta es “¿Cuál es la sintaxis de XAML equivalente al <%$ Resources:xx, yy %> de ASP.NET?”, y no es lindo cuando buscamos por internet y no encontramos ninguna respuesta.  Pues bien, en este artículo voy a mostrar cómo se puede hacer para poder poner textos localizables directamente en el XAML como si se lo estuviéramos asignando a un control ASP.NET en la definición del mismo.

Antes que nada, para aquellos que no están familiarizados con los términos del título de este artículo, voy a comentar a qué nos referimos: hablamos de “globalización” cuando nos referimos a las acciones relacionadas con el desarrollo de aplicaciones que se adaptan a culturas diferentes, en cambio la localización hace referencia a todos aquellos procesos de traducción de los distintos recursos para una cultura específica.

Bien, comencemos creando los textos en distintos idiomas, para esto necesitamos crear un archivo de recursos en nuestro proyecto Silverlight al que llamaremos “Textos.resx”.  Al crear el archivo el Visual Studio® nos abre el editor de recursos y veremos que se podemos cargar varios textos, pudiendo ingresar un nombre, valor (el texto en sí) y algún comentario opcional por cada uno.  El nombre es importante porque se utiliza para crear automáticamente una propiedad para acceder al valor del texto.  Veremos en el árbol del proyecto que además del archivo “Textos.resx” se crea un “Textos.Designer.cs” o (“Textos.Designer.vb” en caso de usar Visual Basic®), aquí el IDE crea una clase para la obtener programáticamente de forma fácil los valores de los textos.  Por ejemplo, supongamos que creamos un texto con el nombre “Boton1”, al guardar el recurso vamos a ver que existe una clase llamada “Textos” con algunas propiedades estáticas, entre las cuales está “Boton1”, la cual nos devolverá el valor del texto.

Un punto a tener en cuenta es la opción “Modificador de acceso” (AccessModifier) que se encuentra en la barra de herramientas del editor de recursos, cuyo valor por defecto es “internal”, lo que significa que la clase que se crea para acceder a los recursos va a ser del tipo “interno”, o sea, no se va a poder acceder desde afuera, pero para poder utilizar nuestros recursos directamente en el XAML necesitamos cambiar este valor a “public”.

AccessModifierResource

Ahora bien, supongamos que en este archivo de recursos estamos cargando los textos en español, pero queremos que nuestra aplicación Silverlight también pueda mostrarlos en inglés, entonces vamos a crear en el mismo lugar donde está nuestro archivo de recursos, otro llamado “Textos.en.resx”.  Nótese que este nuevo archivo antes de su extensión tiene la cadena “en”, esto significa que contendrá los textos en “inglés”.  Podemos crear cuantos recursos necesitemos utilizando esta nomenclatura para el idioma, pero .NET no sólo nos permite crear recursos por idioma, sino por cultura, por ejemplo, permitiéndonos crear recursos en inglés americano (llamando al archivo “Textos.en-US.resx”) o en inglés británico (“Textos.en-GB.resx”).  Debajo del artículo les copio una tabla con las distintas culturas soportadas por .NET Framework (para más información se puede ver la documentación de la clase CultureInfo en http://msdn.microsoft.com/es-ar/library/system.globalization.cultureinfo(VS.95).aspx).

Ahora que ya tenemos creados nuestros recursos en español e inglés, debemos modificar el archivo de proyecto para especificar los lenguajes que va a soportar nuestra aplicación.  Para hacer esto necesitamos editar el archivo .csproj, para lo cual pulsaremos el botón derecho de nuestro mouse sobre el proyecto en nuestro IDE y seleccionaremos la opción “Descargar proyecto” (Unload project), lo cual hará momentáneamente inaccesible los archivos de nuestro proyecto pero nos permitirá, pulsando nuevamente el botón derecho, seleccionar la opción “Editar …” (Edit …) para poder modificar el archivo en el editor del Visual Studio®.  Una vez abierto el archivo veremos que se trata de un XML, el cual contiene una etiqueta llamada “SupportedCultures”, la cual obviamente contiene las culturas soportadas por nuestra aplicación.  Aquí escribiremos todas las culturas que queremos que soporte nuestra aplicación, en nuestro ejemplo:

<SupportedCultures>es;en</SupportedCultures>

Cabe mencionar que las culturas deben ingresarse separadas por un punto y coma, inclusive las que son del mismo idioma, por ejemplo, si quisiéramos que nuestra aplicación soporte las culturas español, español de argentina e inglés, deberíamos ingresar:

<SupportedCultures>es;es-ar;en</SupportedCultures>

Luego de grabar el archivo volvemos a pulsar el botón derecho sobre el proyecto, elegimos la opción “Recargar proyecto” (Reload project).

Bien, hasta acá establecimos los posibles lenguajes, pero vamos a ver cómo utilizar los recursos directamente en el XAML.  La idea básica es crear el objeto que devuelve los textos como un recurso de la aplicación, pero no podemos usar el que creó automáticamente el Visual Studio® ya que, aunque las propiedades estáticas son públicas, su constructor es interno.  Una de las formas de solucionar esto es modificar la clase cambiando el “internal Textos() {}” por “public Textos() {}”, pero cada vez que modifiquemos algún texto el IDE nos va a volver a generar esta clase y vamos a necesitar cambiar nuevamente el constructor.  Para evitar esto podemos crear otra clase que simplemente cree este objeto (al cual va a tener acceso porque es interno) y lo devuelva a través de una propiedad pública, como muestro a continuación:

public class Recursos {
    private static Textos objTextos = new Textos();
    public Textos Textos {
        get { return objTextos; }
    }
}

Ahora podemos agregar nuestra clase como recurso de la aplicación, para lo cual tenemos que abrir el archivo “App.xaml” de nuestra y dentro de “Application.Resources” agregar nuestro objeto de recursos:

<Application.Resources>
<local:Recursos xmlns:local="clr-namespace:Globalizacion" x:Key="Recursos" />
</Application.Resources>

Nótese que el atributo “xmlns:local” define el namespace de nuestra clase, por lo tanto, si su clase Recursos está en uno distinto deberán ingresar el correcto.

Hecho esto ya podemos utilizar nuestros recursos en cualquier XAML de nuestra aplicación haciendo “binding” entre las propiedades de nuestros controles y la del objeto de recursos con el texto a mostrar.  Por ejemplo, a continuación muestro el código para establecerle a la propiedad “Text” de un TextBlock el valor del texto “Prueba” de nuestros recursos:

<TextBlock Text="{Binding Textos.Prueba, Source={StaticResource Recursos}}" />

La sintaxis, si bien no es igual a la de ASP.NET, nos permite establecer textos localizados directamente en nuestros XAML, sin necesidad de hacerlo programáticamente.

Ahora lo único que nos queda por hacer es establecer de alguna forma el idioma que queremos mostrar en nuestra aplicación.  Una forma sería cambiar el UICulture de nuestra aplicación programáticamente de la siguiente forma:

Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");

Pero si queremos automatizarlo podemos hacer que nuestra página ASP.NET le pase el idioma que está utilizando, cambiando el parámetro “uiculture” del objeto Silverlight en HTML.  Si queremos que además de nuestros textos la aplicación adapte los formatos de fecha, números, etc., también debemos establecer la cultura al parámetro “cultura”.  Por ejemplo, en el siguiente código HTML le establezco la cultura inglés a nuestra aplicación:

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
    <param name="source" value="ClientBin/Globalizacion.xap" />
    <param name="onError" value="onSilverlightError" />
    <param name="background" value="white" />
    <param name="minRuntimeVersion" value="3.0.40624.0" />
    <param name="autoUpgrade" value="true" />
    <param name="uiculture" value="en" />
    <param name="culture" value="en" />
    <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
        <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
            style="border-style: none" />
    </a>
</object>

Como hablamos antes de “automatizar” la cultura, podemos hacer que el ASP.NET elija automáticamente la cultura a mostrar según la configuración del navegador del cliente estableciendo el valor “auto” a las propiedades “uiculture” y “culture” en la etiqueta “globalization” de nuestro web.config:

<globalization uiCulture="auto" culture="auto"/>

Si “globalization” no existe en el web.config habrá que crearlo dentro de “system.web”.

Por último, debemos pasarle al Silverlight la cultura seleccionada por el ASP.NET:

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
    <param name="source" value="ClientBin/Globalizacion.xap" />
    <param name="onError" value="onSilverlightError" />
    <param name="background" value="white" />
    <param name="minRuntimeVersion" value="3.0.40624.0" />
    <param name="autoUpgrade" value="true" />
    <param name="uiculture" value="<%=System.Threading.Thread.CurrentThread.CurrentUICulture%>" />
    <param name="culture" value="<%=System.Threading.Thread.CurrentThread.CurrentCulture%>" />
    <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
        <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
            style="border-style: none" />
    </a>
</object>

Y listo, nuestra aplicación ya se adapta a la configuración del navegador del usuario y cambia sus textos y formatos.

Aquí les dejo el proyecto realizado con Visual Studio® 2008 para bajar y las capturas de la página donde se ve cómo cambia la cultura entre español, inglés y español de Argentina (qué es la configuración de mi PC) al utilizar la selección automática (nótese la diferencia en el formato de hora con la cultura “español”).

Cultura en español

Cultura en inglés

Cultura en auto

Espero que el artículo les haya sido de utilidad y como siempre los insto a dejar sus consultas, dudas o sugerencias.

Culturas soportadas por .NET

Cultura Referencia
“” (cadena vacía) Referencia cultural de todos los idiomas
af Afrikaans
af-ZA Afrikaans (Sudáfrica)
sq Albanés
sq-AL Albanés (Albania)
ar Árabe
ar-DZ Árabe (Argelia)
ar-BH Árabe (Bahréin)
ar-EG Árabe (Egipto)
ar-IQ Árabe (Iraq)
ar-JO Árabe (Jordania)
ar-KW Árabe (Kuwait)
ar-LB Árabe (Líbano)
ar-LY Árabe (Libia)
ar-MA Árabe (Marruecos)
ar-OM Árabe (Omán)
ar-QA Árabe (Qatar)
ar-SA Árabe (Arabia Saudí)
ar-SY Árabe (Siria)
ar-TN Árabe (Túnez)
ar-AE Árabe (Emiratos Árabes Unidos)
ar-YE Árabe (Yemen)
hy Armenio
hy-AM Armenio (Armenia)
az Azerí
az-Cyrl-AZ Azerí (Azerbaiyán, Cirílico)
az-Latn-AZ Azerí (Azerbaiyán, Latín)
eu Vasco
eu-ES Vasco (España)
be Bielorruso
be-BY Bielorruso (Belarús)
bg Búlgaro
bg-BG Búlgaro (Bulgaria)
ca Catalán
ca-ES Catalán (España)
zh-HK Chino (Hong Kong RAE, RPC)
zh-MO Chino (Macao RAE)
zh-CN Chino (RPC)
zh-Hans Chino (simplificado)
zh-SG Chino (Singapur)
zh-TW Chino (Taiwán)
zh-Hant Chino (tradicional)
hr Croata
hr-BA Croata (Bosnia-Herzegovina)
hr-HR Croata (Croacia)
cs Checo
cs-CZ Checo (República Checa)
da Danés
da-DK Danés (Dinamarca)
dv Divehi
dv-MV Divehi (Maldivas)
nl Holandés
nl-BE Neerlandés (Bélgica)
nl-NL Neerlandés (Países Bajos)
en Inglés
en-AU Inglés (Australia)
en-BZ Inglés (Belice)
en-CA Inglés (Canadá)
en-029 Inglés (Caribe)
en-IE Inglés (Irlanda)
en-JM Inglés (Jamaica)
en-NZ Inglés (Nueva Zelanda)
en-PH Inglés (Filipinas)
en-ZA Inglés (Sudáfrica)
en-TT Inglés (Trinidad y Tobago)
en-GB Inglés (Reino Unido)
en-US Inglés (Estados Unidos)
en-ZW Inglés (Zimbabue)
et Estonio
et-EE Estonio (Estonia)
fo Feroés
fo-FO Feroés (Islas Feroe)
fa Persa
fa-IR Farsi (Irán)
fi Finlandés
fi-FI Finés (Finlandia)
fr Francés
fr-BE Francés (Bélgica)
fr-CA Francés (Canadá)
fr-FR Francés (Francia)
fr-LU Francés (Luxemburgo)
fr-MC Francés (Mónaco)
fr-CH Francés (Suiza)
gl Gallego
gl-ES Gallego (España)
ka Georgiano
ka-GE Georgiano (Georgia)
de Alemán
de-AT Alemán (Austria)
de-DE Alemán (Alemania)
de-DE_phoneb Alemán (Alemania, ordenación de la libreta de teléfonos)
de-LI Alemán (Liechtenstein)
de-LU Alemán (Luxemburgo)
de-CH Alemán (Suiza)
el Griego
el-GR Griego (Grecia)
gu Gujarati
gu-IN Gujarati (India)
he Hebreo
he-IL Hebreo (Israel)
hi Hindi
hi-IN Hindi (India)
hu Húngaro
hu-HU Húngaro (Hungría)
es Islandés
is-IS Islandés (Islandia)
id Indonesio
id-ID Indonesio (Indonesia)
it Italiano
it-IT Italiano (Italia)
it-CH Italiano (Suiza)
ja Japonés
ja-JP Japonés (Japón)
kn Kannada
kn-IN Kannada (India)
kk Kazajo
kk-KZ Kazajo (Kazajistán)
kok Konkani
kok-IN Konkani (India)
ko Coreano
ko-KR Coreano (Corea)
ky Kirguís
ky-KG Kirguís (Kirguistán)
lv Letón
lv-LV Letón (Letonia)
lt Lituano
lt-LT Lituano (Lituania)
mk Macedonio
mk-MK Macedonio (Macedonia, Ex-República Yugoslava de Macedonia)
ms Malayo
ms-BN Malayo (Estado de Brunéi Darussalam)
ms-MY Malayo (Malasia)
mr Marathi
mr-IN Marathi (India)
mn Mongol
mn-MN Mongol (Mongolia)
no Noruego
nb-NO Noruego (Bokmål, Noruega)
nn-NO Noruego (Nynorsk, Noruega)
pl Polaco
pl-PL Polaco (Polonia)
pt Portugués
pt-BR Portugués (Brasil)
pt-PT Portugués (Portugal)
pa Punjabi
pa-IN Punjabi (India)
ro Rumano
ro-RO Rumano (Rumanía)
ru Ruso
ru-RU Ruso (Rusia)
sa Sánscrito
sa-IN Sánscrito (India)
sr-Cyrl-CS Serbio cirílico (Serbia y Montenegro)
sr-Latn-CS Serbio latino (Serbia y Montenegro)
sk Eslovaco
sk-SK Eslovaco (Eslovaquia)
sl Esloveno
sl-SI Esloveno (Eslovenia)
es Español
es-AR Español (Argentina)
es-BO Español (Bolivia)
es-CL Español (Chile)
es-CO Español (Colombia)
es-CR Español (Costa Rica)
es-DO Español (República Dominicana)
es-EC Español (Ecuador)
es-SV Español (El Salvador)
es-GT Español (Guatemala)
es-HN Español (Honduras)
es-MX Español (México)
es-NI Español (Nicaragua)
es-PA Español (Panamá)
es-PY Español (Paraguay)
es-PE Español (Perú)
es-PR Español (Puerto Rico)
es-ES Español (España)
es-ES_tradnl Español (España – alfabetización tradicional)
es-UY Español (Uruguay)
es-VE Español (Venezuela)
sw Suajili
sw-KE Swahili (Kenia)
sv Sueco
sv-FI Sueco (Finlandia)
sv-SE Sueco (Suecia)
syr Sirio
syr-SY Sirio (Siria)
ta Tamil
ta-IN Tamil (India)
tt Tatar
tt-RU Tatar (Rusia)
te Telugu
te-IN Telugu (India)
th Tailandés
th-TH Tailandés (Tailandia)
tr Turco
tr-TR Turco (Turquía)
uk Ucraniano
uk-UA Ucraniano (Ucrania)
ur Urdú
ur-PK Urdú (Pakistán)
uz Uzbeko
uz-Cyrl-UZ Uzbeko (Uzbekistán, Cirílico)
uz-Latn-UZ Uzbeko (Uzbekistán, Latino)
vi Vietnamita
vi-VN Vietnamita (Vietnam)
VN:F [1.7.3_972]
Rating: 10.0/10 (1 voto cast)

Imprimir con Silverlight 4 beta

Martes, 01 Dic, 2009 @ 00:37 | Por Gustavo Cantero (The Wolf) | Silverlight

Una de las características más votadas en el sitio Microsoft® Connect por los desarrolladores que utilizamos Silverlight es la posibilidad de imprimir. Esto ahora es posible utilizando Silverlight 4 beta y la nueva clase PrintDocument del espacio de nombres System.Windows.Printing.

Primero vamos a hacer una introducción rápida sobre como imprimir desde Silverlight. La clase PrintDocument básicamente envía a la impresora el contenido de cualquier objeto UIElement, el cual debe ser establecido a través de uno de los parámetros del evento PrintPage que se dispara luego de llamar al método Print. Nótese que al tratarse de objetos UIElement se puede imprimir cualquier elemento gráfico que estemos utilizando en nuestras aplicaciones, por ejemplo, Image, TextBlock o incluso grillas complejas con elementos variados o con más grillas en su interior.

Supongamos que queremos imprimir, desde un botón puesto en el XAML, la pantalla de la aplicación (sería algo así como una captura de la aplicación), para esto podríamos poner el siguiente código en el XAML:

<UserControl x:Class="Print.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="Texto de prueba" Margin="10" FontSize="30">
            <TextBlock.Effect>
                <DropShadowEffect />
            </TextBlock.Effect>
        </TextBlock>
        <Button Content="Imprimir" Click="Button_Click" />
    </StackPanel>
</UserControl>

Y este otro en el código:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Printing;

namespace Print
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            PrintDocument objDoc = new PrintDocument();
            objDoc.PrintPage += (s, args) =>
            {
                args.PageVisual = this;
            };
            objDoc.Print();
         }
    }
}

En el código de C# podemos ver tres pasos sencillos realizados para imprimir la página Silverlight completa: creamos el objeto PrintDocument, creamos un delegado anónimo para el evento PrintDocument que establezca la propiedad PageVisual (aquí es donde hay que establecer el elemento a imprimir) y por último llamamos al método Print. Al llamar a este método el sistema operativo nos muestra el diálogo de impresión.

Dialogo impresión

Para probar la impresión yo lo envié a la impresora virtual llamada “Microsoft XPS Document Writer”, para que me cree un archivo XPS y poder verlo luego desde mi navegador:

Impresión XPS

Bien, como ejemplo sirve, pero cómo hacemos para imprimir otra cosa que no sea la aplicación o para imprimir varias páginas? Eso lo voy a explicar a continuación.

La clase PrintDocument en realidad posee tres eventos: StartPrint, PrintDocument y EndPrint, los cuales se ejecutan antes, durante y después de realizar la impresión respectivamente.

El primer evento, StartPrint, se dispara luego de llamar al método Print y de que el usuario cliqueado “Imprimir” en el diálogo de impresión. Este evento es utilizado para configurar lo necesario antes de la impresión, por ejemplo, acomodar controles, ocultar elementos, etc.

El siguiente evento a ejecutar es PrintDocument, al cual se le pasa como parámetro un objeto del tipo PrintPageEventArgs. Este objeto posee tres propiedades interesantes:

  • PageVisual: en esta propiedad debemos establecer el objeto del tipo UIElement que queremos que se imprima en la página actual, por ejemplo, una grilla con información, una imagen, etc. Este elemento a imprimir no necesariamente debe estar en el XAML, podemos crearlo programáticamente y enviarlo luego a imprimir.
  • PrintableArea: aquí nos pasa el tamaño del área imprimible que disponemos. Cabe mencionar que este valor variará dependiendo de la impresora seleccionada por el usuario y del tamaño del papel elegido.
  • HasMorePages: en esta propiedad podemos establecer si luego de la página que estamos enviando a imprimir hay más páginas. En caso de que establezcamos como true el valor de esta propiedad, luego de finalizado el delegado actual, se disparará nuevamente el evento PrintDocument para imprimir las siguientes páginas.

Por último nos queda el evento EndPrint, donde podemos realizar cualquier acción que necesitemos hacer luego de finalizada (o enviada al spooler) la impresión. Este evento recibe, como uno de sus parámetros, un objeto del tipo EndPrintEventArgs, el cual tiene una propiedad llamada Error que es donde el Framework nos devuelve cualquier excepción generada al momento de la impresión. Si en el ejemplo anterior ante un error quisiéramos mostrarle al usuario el mensaje de la excepción, deberíamos modificar el código del evento Click del botón por el siguiente:

PrintDocument objDoc = new PrintDocument();
objDoc.PrintPage += (s, args) =>
{
    args.PageVisual = this;
};
objDoc.EndPrint += (s, args) =>
{
    if (args.Error != null)
        MessageBox.Show(args.Error.Message, "ERROR", MessageBoxButton.OK);
};
objDoc.Print();

Regresando a la clase PrintDocument, ésta posee también (además del método Print que ya vimos) una propiedad llamada DocumentName, donde podemos establecer el nombre de nuestro documento, el cual se verá en la cola de la impresora.

Por último les dejo el proyecto de ejemplo realizado con Visual Studio® 2010 beta 2.

VN:F [1.7.3_972]
Rating: 10.0/10 (2 votos cast)

Silverlight 4 Beta

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

Microsoft® Silverlight
En el día de hoy, 18 de noviembre de 2009, en Los Angeles en el Professional Developer’s Conference 2009 (conocido como PDC09), Microsoft anuncia la disponibilidad de la beta de Silverlight® 4, justo 8 meses después de que en el MIX09 se presentara la beta de Silverlight® 3 (el 18 de marzo).

Entre las nuevas características que nos trae esta nueva versión encontramos:

  • Soporte para impresión, incluyendo una vista previa del documento
  • Más de 60 controles personalizables
  • Mejoras en la localización de los textos
  • El CLR ahora permite ejecutar el mismo código compilado en el escritorio como en Silverlight sin cambios
  • Soporte para la utilización de webcams y micrófono
  • Deep Zoom ahora utiliza la aceleración por hardware
  • Multicast networking
  • Soporte para Google® Chrome
  • Soporte Multi-touch
  • Eventos de botón derecho del mouse
  • Acceso al Portapapeles
  • Se podrán utilizar en el escritorio como “trusted applications”, permitiendo acceder a documentos, aplicaciones como Outlook® (por ejemplo, para enviar mails), a dispositivos utilizando COM automation, etc.
  • Y muchas novedades más…. (para más detalles pueden visitar el sitio de Silverlight)

Para probarlo sólo necesitan tener la beta de Visual Studio® 2010 y el SDK de Silverlight® 4.

Cabe mencionar que en el día de hoy también salió el nuevo release del Silverlight® Toolkit, el cual ahora también soporta la beta de Silverlight 4.

Esta noticia seguramente va a alegrar a varios de los desarrolladores que ya utilizamos Silverlight® en nuestras aplicaciones.

Enlaces

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

Cómo crear una clase dinámicamente y ejecutarla

Sábado, 14 Nov, 2009 @ 19:13 | Por Gustavo Cantero (The Wolf) | .NET Framework

Revisando los newsgroups de Microsoft encontré una persona que preguntaba cómo hacer para “ejecutar un string”, o sea, escribir una fórmula matemática en una cadena de texto y luego obtener el resultado de la misma. En ese momento recordé que hace unos años tuve que hacer esto mismo utilizando .NET Framework 1.1, fue entonces cuando me decidí a buscar aquel código y escribir este artículo.
Para hacer esto necesitamos crear una clase, compilarla en memoria, luego instanciarla y por último ejecutar el método que devuelva el resultado en cuestión.

La clase la debemos crear en un string incluyendo los using necesarios, el namespace a utilizar y el método a ejecutar, el cual va a resolver la fórmula. Al compilador debemos pasarle varios parámetros a través de la clase CompilerParameters, donde vamos a indicarle que debe generar el ensamblado en memoria, que no debe generar un ejecutable y que no incluya (o si, depende de la necesidad) la información para debug.

CompilerParameters Parametros = new CompilerParameters()
{
    GenerateInMemory = true,
    GenerateExecutable = false,
    IncludeDebugInformation = false
};

Luego debemos crear el compilador con el método estático CreateProvider de la clase CodeDomProvider, al cual debemos pasarle el lenguaje que queremos utilizar, en nuestro ejemplo “CSharp”. Hay que tener en cuenta que el nombre del lenguaje es “case sensitive”, por lo que hay que tener cuidado de escribirlo con las mayúsculas y minúsculas correspondientes.

CodeDomProvider objCompiler = CodeDomProvider.CreateProvider("CSharp");

Una vez creado el compilador debemos pasarle la clase y los parámetros creados anteriormente para que genere el ensamblado necesario en memoria:

CompilerResults objResultados = objCompiler.CompileAssemblyFromSource(objParametros, strClase);

Por último debemos crear una instancia de la clase y llamar al método que creamos y que va a calcular la fórmula:

object objClase = objResultados.CompiledAssembly.CreateInstance("MiNamespace.MiClase", false, BindingFlags.CreateInstance, null, null, null, null);
return objClase.GetType().InvokeMember("MiMetodo", BindingFlags.InvokeMethod, null, objClase, null);

El método CreateInstance posee varios parámetros para globalización, parámetros para pasarle al método, etc., pero para nuestro ejemplo, al no necesitarlos, los vamos a establecer en null.

Una vez explicado lo que necesitamos hacer, les paso el código del método a ejecutar para que resuelva fórmulas o cualquier línea de C# (por ejemplo, búsquedas de cadenas de texto, etc.) y devuelva el valor:

namespace WebApplication1
{
    using System.CodeDom.Compiler;
    using System.Reflection;

    public static class Formula
    {
        /// <summary>
        /// Resuelve el valor de una fórmula
        /// </summary>
        /// <param name="Formula">Fórmula a resolver</param>
        /// <returns>Resultado</returns>
        /// <example>double Resultado = Formula.Resolver("2 * 8 + 3");</example>
        public static object Resolver(string Formula)
        {
            //Parámetros del compilador
            CompilerParameters objParametros = new CompilerParameters()
            {
                GenerateInMemory = true,
                GenerateExecutable = false,
                IncludeDebugInformation = false
            };

            //Clase
            string strClase =
                "using System;" +
                "namespace Scientia {" +
                "public class Formula {" +
                    "public object Ejecutar() {" +
                        "return " + Formula +
                ";}}}";

            //Compilo todo y ejecuto el método
            CodeDomProvider objCompiler = CodeDomProvider.CreateProvider("CSharp");

            //En .NET 1.1 usaba esta linea:
            //ICodeCompiler ICC = (new CSharpCodeProvider()).CreateCompiler();

            CompilerResults objResultados = objCompiler.CompileAssemblyFromSource(objParametros, strClase);
            object objClase = objResultados.CompiledAssembly.CreateInstance("Scientia.Formula",
                false, BindingFlags.CreateInstance, null, null, null, null);
            return objClase.GetType().InvokeMember("Ejecutar", BindingFlags.InvokeMethod, null, objClase, null);
        }
    }
}

Como ejemplo podemos crear una página donde el usuario pueda ingresar una fórmula y al pulsar en un botón se muestre el resultado en la misma. La página debería quedar así:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Ejemplo de Scientia® Soluciones Informáticas</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Panel DefaultButton="btnCalcular" runat="server">
        <asp:TextBox ID="txtFormula" runat="server" />
        <asp:Button ID="btnCalcular" runat="server" Text="=" OnClick="btnCalcular_Click" />
        <asp:Label ID="lblResultado" runat="server" />
    </asp:Panel>
    </form>
</body>
</html>

Y en el code behind de ésta debería tener lo siguiente:

using System;

namespace WebApplication1
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected void btnCalcular_Click(object sender, EventArgs e)
        {
            lblResultado.Text = Formula.Resolver(txtFormula.Text).ToString();
        }
    }
}

Espero que este artículo les sea de utilidad y, como siempre, les dejo el proyecto de ejemplo para Visual Studio® 2008.Descargar proyecto de ejemplo

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

Versión final de Bing Maps Platform

Martes, 10 Nov, 2009 @ 12:28 | Por Gustavo Cantero (The Wolf) | GIS, Silverlight

bing
Para aquellos que, como nosotros, utilizan la versión CTP del control de Silverlight para Virtual Earth les comento que este control funcionará hasta el 31 de diciembre de 2009 inclusive. Para poder seguir utilizando los servicios de Bing Microsoft acaba de lanzar (ayer) la versión final de Bing Maps Platform 1.0, el cual incluye la versión final del control para usarse desde Silverlight.

Para conocer más de la nueva versión del control o para conocer cómo utilizar los servicios del control de Silverlight para bing pueden leer el Getting Started Using the Silverlight Map Control.

También pueden ver el video del anuncio de esta nueva versión en Microsoft Bing Maps for Enterprise.

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

Autenticación por formularios en ASP.NET

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

Prólogo e inicios

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

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

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

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

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

<authentication mode="Forms">

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

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


Autenticación por formularios

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

Estructura del proyecto

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Autenticación

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

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

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

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

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

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

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

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

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

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

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

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

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

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

y

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

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

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

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.5/10 (2 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: 0.0/10 (0 votos 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: 5.0/10 (1 voto cast)

Utilizar certificados digitales desde .NET

Viernes, 21 Ago, 2009 @ 09:20 | Por Gustavo Cantero (The Wolf) | .NET, 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: 6.7/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: 8.0/10 (1 voto 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, IIS, Seguridad

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

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

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

Certificados

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

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

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

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

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

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

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

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

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

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

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

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

Propiedades IIS

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

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

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

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

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

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

Comunicaciones seguras


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

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

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

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

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

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

Artículos relacionados

VN:F [1.7.3_972]
Rating: 9.8/10 (11 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

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

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

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

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

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

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

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

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

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

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

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

string strIP = Page.Request.UserHostAddress;

o, para los usuarios de Visual Basic.NET:

Dim strIP as string = Page.Request.UserHostAddress

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

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

y, para los usuarios de Visual Basic.NET:

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

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

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

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

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

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

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

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

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

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

VN:F [1.7.3_972]
Rating: 9.3/10 (6 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.9/10 (8 votos cast)