|
Archivo de la categoría 'ASP.NET'
Lunes, 03 may, 2010 @ 14:28 | Por Dario Krapp | ASP.NET |
 |
Prólogo e inicios
En un artículo previo denominado Enlace de elementos en controles de lista en ASP.NET vimos las posibilidades que ASP.NET ofrecía para enlazar orígenes de datos a controles de lista y realizamos enlaces vía código y vía los elementos SqlDataSource y ObjectDataSource sin codificar ni una línea en los dos últimos casos.
En este artículo, daremos un próximo paso y no solo mostraremos información de un origen de datos en un control ASP.NET, sino que también veremos la forma de transferir los cambios realizados por el usuario sobre dichos datos nuevamente hacia el origen de datos. Para realizar estas tareas la funcionalidad proporcionada por los controles de lista no nos será suficiente, por lo que aprovecharemos la oportunidad para conocer uno de los controles más populares de ASP.NET, el control GridView el cual renderizará, como no debería sorprendernos, una grilla con datos.
Debido a que el control GridView es extremadamente flexible y ofrece una considerable variedad de posibilidades, como no queremos que el artículo se extienda demasiado, nos ocuparemos ver todos estos temas mencionados realizando el enlace vía código, ya que no hay tantos ejemplos (o al menos eso me parece), como los que pueden encontrarse contra enlaces a los objetos SqlDataSource y ObjectDataSource.
Para comenzar, dado que el artículo trata sobre enlace, diremos que en nuestro escenario contaremos con:
- Un origen de datos (datos a enlazar).
- Un control ASP.NET (GridView) destinado a enlazar y mostrar
en la interfaz de usuario a los elementos del origen de datos.
Nuestro interés será en esta etapa simplemente enlazar el origen de datos al control GridView.
Para poder realizar esta tarea, el control GridView ofrecerá una propiedad denominada DataSource en la cual deberá establecerse el origen de datos y todo quedará casi listo, un detalle a considerar es que podrán enlazarse elementos que implementen algunas de las siguientes interfaces: ICollection, IEnumerable o IListSource.
Seguramente una duda razonable será como se renderizará el control al efectuarse el enlace. En el caso de enlazar un objeto que implemente ICollection generará una fila por cada elemento de la colección y una columna por cada propiedad pública que el elemento posea.
A continuación y a modo de ejemplo enlazaremos una lista de elementos personalizados a un control GridView, utilizaremos como origen de datos un elemento del tipo List
<asp:GridView runat="server" ID="GV1" />
public class MiElem
{
public string Valor1 { set; get; }
public int Valor2 { set; get; }
public DateTime Valor3 { set; get; }
public bool Valor4 { set; get; }
public string Valor5 { set; get; }
public string Valor6 { set; get; }
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
BindGrid(GV1);
}
private void BindGrid(GridView Grd)
{
List<MiElem> Lst = new List<MiElem>();
Lst.Add(new MiElem()
{
Valor1 = "V1",
Valor2 = 1,
Valor3 = DateTime.Now.AddDays(1),
Valor4 = true,
Valor5 = "imagen1.gif",
Valor6 = @"~\Imagenes\imagen1.gif"
});
Lst.Add(new MiElem()
{
Valor1 = "V2",
Valor2 = 2,
Valor3 = DateTime.Now.AddDays(2),
Valor4 = true,
Valor5 = "imagen1.gif",
Valor6 = @"~\Imagenes\imagen1.gif"
});
Lst.Add(new MiElem()
{
Valor1 = "V3",
Valor2 = 3,
Valor3 = DateTime.Now.AddDays(3),
Valor4 = true,
Valor5 = "imagen1.gif",
Valor6 = @"~\Imagenes\imagen1.gif"
});
Grd.DataSource = Lst;
Grd.DataBind();
}
Como puede verse:

El control GridView ha renderizando una grilla y ha generando, como anticipamos, una fila por cada elemento enlazado y una columna por cada propiedad pública del mismo.
Un detalle a mencionar es que el método DataBind() será quien realizará el traspaso de datos desde el origen de datos hacia el control, si el mismo es obviado los datos no serán transferidos y la grilla quedará vacía. Por otra parte cualquier modificación que se realice sobre la lista “Lst” posterior a la invocación al método DataBind() no será transferida al control y no será reflejada en el mismo.
Volviendo al ejemplo, cabe mencionar que lo que ha hecho el control es utilizar reflection para conocer las propiedades públicas de los elementos que componen la lista Lst (en realidad del primer elemento de la lista) y ha autogenerado una columna para cada una de ellas, además ha enlazado a cada elemento en una fila (ubicando el valor de cada propiedad en la columna correspondiente).
Luego de la impresión inicial, es claro que este comportamiento no nos será demasiado útil en la mayoría de los casos, al menos eso me parece, ya que posiblemente si deseamos enlazar un grupo de elementos, en muchos casos se deseará visualizar solo algunas de sus propiedades (públicas).
En un artículo previo vimos que los controles de lista poseían la propiedad DataTextField para indicar que propiedad (o campos en el caso de enlazar el control contra un DataSet, un DataTable o un DataReader) mostraría el control. Pero el caso del GridView, que como ya hemos visto, permitirá enlazar varias propiedades o campos en forma simultánea, el esquema será más complejo y más aún si consideramos otros factores como por ejemplo, el orden de las columnas. Es entonces esperable que exista una manera de definir cómo será la visualización de las propiedades a mostrar en las columnas de la grilla que considere todos estos factores.
VN:F [1.7.3_972] Rating: 8.0/10 (3 votos cast)
No tiene comentarios »
Miércoles, 24 mar, 2010 @ 23:43 | Por Dario Krapp | ASP.NET |
 |
Estoy seguro que será difícil encontrar en la actualidad algún programador que no se haya visto en la necesidad enviar información a la interfaz de usuario, y eventualmente manejar las modificaciones que un usuario haya realizado en la misma. La intención de este artículo es dar un primer paso a los conceptos de enlace en ASP.NET (una tecnología que nos permitirá resolver este tipo de problemas) para un escenario muy especifico pero frecuente que es cuando se requiere mostrar en un control ASP.NET un grupo de elementos como una lista. ASP.NET ofrecerá para este tipo de casos un grupo de controles (que llamaremos controles de lista para identificarlos de una forma sencilla) que nos permitirán realizar esta tarea en forma simple y clara.
En la década de los noventa, con ASP, lo que se acostumbraba hacer era identificar un control HTML adecuado para el grupo de datos que se deseaba mostrar y escribir los tags HTML intercalados junto con los datos para crear un fragmento de código HTML con la funcionalidad deseada, esta forma de trabajo, si bien funcionaba, daba como resultado un código poco claro y difícil de mantener.
ASP.NET y el enlace a datos permitirá obtener un resultado similar con un código de rápido desarrollo y de bajo costo de mantenimiento.
Antes que nada creo que lo más importante es definir quienes serán los actores que intervendrán en un escenario de enlace de datos y sin importar cuánto avancemos o donde comencemos, nos encontraremos con dos actores principales, el primero se estará compuesto por el grupo de datos con los que deseamos trabajar (al cual, para utilizar la nomenclatura habitual, llamaremos “Origen de datos” o “Data Source”) y el segundo por el elemento que permitirá que los mismos sean mostrados en la interfaz de usuario, y eventualmente sean modificados desde la misma, aunque esta opción quedará fuera del alcance de este artículo.
En el caso de ASP.NET ese elemento será un control y los controles que se ajustarán a las necesidades propuestas en este articulo serán los siguientes:
• BulletedList
• CheckBoxList
• DropDownList
• ListBox
• RadioButtonList
En WebControls y el control HtmlSelect en HtmlControls.
Aunque cada uno de ellos renderizará la lista de elementos a mostrar de una forma diferente, el enlace se efectuará de la misma forma para todos, por lo que en nuestros ejemplos optaremos por utilizar un control DropDownList.
Para comenzar, seremos sumamente triviales y supondremos que nuestro origen de datos es un grupo de elementos simples, por ejemplo, un grupo de cadenas de caracteres compuesta por los valores “I1”, “I2” e “I3” y nuestra intención será mostrar dichos valores en un control DropDownList.
El enlace
En nuestra primera suposición ya hemos conocido a ambos actores (el origen de datos compuesto por la cadena de caracteres “I1”, “I2” e “I3” y el control ASP.NET DropDownList), Deberemos finalmente realizar el enlace entre ambos.
Esta tarea será muy sencilla ya que los controles de lista contarán con la siguiente propiedad:
• DataSource
La propiedad DataSource representará al grupo de elementos (u origen de datos) que se enlazará al control, claro está que el grupo de elementos a enlazar no podrá ser tan arbitrario como deseemos ya que de alguna forma ASP.NET deberá saber de qué manera acceder a los elementos que componen dicho grupo, por lo que éste deberá implementar alguna de las siguientes interfaces: IEnumerable, ICollection o IListSource.
Sabemos ahora que para nuestro ejemplo, lo que llamábamos “grupo” deberá implementar alguna de las interfaces mencionadas.
Creo que de todas las comentadas, la interfaz ICollection nos será la más familiar y, para quien no la recuerde, sin usar mucho la imaginación, seguramente le será un sinónimo de Colecciones (o Collections). En base a esto podríamos reformular el problema para intentar enlazar una colección compuesta por las cadenas “I1”, “I2” e “I3” con un control DropDownList.
Los pasos para esta tarea constarán de crear una colección, de crear el control ASP.NET y establecer su propiedad DataSource, veamos un ejemplo concreto:
<asp:DropDownList runat="server" ID="DDL1"/>
List<string> Lst = new List<string>();
Lst.Add("I1");
Lst.Add("I2");
Lst.Add("I3");
DDL1.DataSource = Lst;
Si ejecutamos este código veremos que el enlace no funciona, esto se debe a que aún nos falta un detalle más, si modificamos el código de la siguiente forma:
List<string> Lst = new List<string>();
Lst.Add("I1");
Lst.Add("I2");
Lst.Add("I3");
DDL1.DataSource = Lst;
DDL1.DataBind();
Veremos que todo funciona según lo esperado, el método DataBind es el que ejecutará el pasaje de datos desde el origen de datos (Lst) hacia el control (DDL1). Es más si realizamos la siguiente prueba:
List<string> Lst = new List<string>();
Lst.Add("I1");
Lst.Add("I2");
Lst.Add("I3");
DDL1.DataSource = Lst;
DDL1.DataBind();
Lst.Add("I5");
Lst.Add("I6");
Notaremos que los valores “I5” e “I6” no serán transferidos al control.
En este primer ejemplo hemos realizado el enlace que nos habíamos propuesto y con muy poco esfuerzo.
Los controles de lista contarán también con la propiedad DataTextFormatString que permitirá establecer el formato de los datos a mostrar, para continuar con el ejemplo que ya habíamos comenzado, reemplazaremos la lista de cadenas de caracteres por una lista de fechas, el ejemplo es tan simple como antes, una opción es utilizar las siguientes líneas de código:
List<DateTime> Lst = new List<DateTime>();
Lst.Add(DateTime.Now);
Lst.Add(DateTime.Now.AddHours(1));
Lst.Add(DateTime.Now.AddHours(2));
DDL1.DataSource = Lst;
DDL1.DataBind();
donde al ejecutarlo veremos que en el DropDownList DDL1 las fechas se desplegarán como esperábamos, aunque para darle un poco de emoción al ejemplo y poner en practica lo que comentamos hace apenas unos parrafos, podríamos desear mostrar solamente la fecha pero no la hora en el formato dia/año/mes. De más está decir que una opción es modificar el código para realizar este ajuste programáticamente, pero no es la idea ya que la propiedad DataTextFormatString nos permitirá ahorrarnos las líneas de código.
A continuación se muestra el ejemplo:
<asp:DropDownList runat="server" ID="DDL1" DataTextFormatString="{0:dd/yyyy/MM}" />
Me parece que una pregunta que podríamos formularnos en este punto es ¿por qué el control está mostrando los valores adecuadamente?, si prestamos atención veremos que en ambos casos, tanto como cuando utilizamos una lista de cadenas de caracteres o una lista de fechas el control DropDownList ha podido mostrar el valor esperado sin que nosotros hayamos tenido que hacer ningún tipo de especificación, podríamos preguntarnos ¿qué pasará si utilizamos una lista de otro tipo de elementos?, por ejemplo un tipo de datos creado por nosotros mismos, como el siguiente:
public class MiElem
{
public string Valor1 { set; get; }
public int Valor2 { set; get; }
}
Si modificamos nuevamente el código previo de la siguiente forma
List<MiElem> Lst = new List<MiElem>();
Lst.Add(new MiElem() { Valor1 = "V1", Valor2 = 1 });
Lst.Add(new MiElem() { Valor1 = "V2", Valor2 = 2 });
Lst.Add(new MiElem() { Valor1 = "V3", Valor2 = 3 });
DDL1.DataSource = Lst;
DDL1.DataBind();
Notaremos la magia ya no funciona y que el DropDownList en este caso está mostrando por cada elemento un valor que no se encuentra en ninguna de sus propiedades. Lo que está haciendo el control es mostrar el valor de la representación de cadena de caracteres del elemento, o sea, simplemente está mostrando el resultado del método ToString() de cada elemento que carga. Si sobreescribimos el método ToString() de la clase MiElem nos convenceremos rápidamente de este hecho.
Ahora queda claro porque con cadenas de caracteres y fechas todo funcionaba sin problemas pero también está claro que con otros tipos de datos nos podríamos encontrar con complicaciones, ya que no siempre podremos ajustar el método ToString() a nuestra conveniencia. En este punto toma importancia la propiedad DataTextField de los controles de lista. DataTextField indicará el nombre de la propiedad o el campo (veremos más adelante a que nos referimos con campo) que el control mostrará. Si modificamos el código de la siguiente forma:
List<MiElem> Lst = new List<MiElem>();
Lst.Add(new MiElem() { Valor1 = "V1", Valor2 = 1 });
Lst.Add(new MiElem() { Valor1 = "V2", Valor2 = 2 });
Lst.Add(new MiElem() { Valor1 = "V3", Valor2 = 3 });
DDL1.DataSource = Lst;
DDL1.DataTextField = "Valor1";
DDL1.DataBind();
Notaremos que el control DDL1 mostrará el valor de la propiedad Valor1 de cada instantancia de MiElem.
De forma similar, la propiedad DataValueField indicará el nombre de la propiedad o el campo que el control utilizará como valor para cada ítem mostrado, ya que es frecuente el hecho de tener la necesidad de mostrar un dato, pero utilizar otro dato del elemento para realizar alguna tarea.
Si eventualmente se desea utilizar la misma propiedad para mostrarla en control tanto como para utilizar su valor, puede omitirse el uso de DataValueField, que es un detalle que permite mantener el código más simple.
Aunque en nuestro ejemplo hemos utilizado un control DropDownList enlazado a un objeto List podríamos haber utilizado cualquier otro control de lista (como mencionamos previamente), tanto como cualquier otra clase que implementase la interfaz ICollection.
Si volvemos a la propiedad DataSource, recordaremos que también pueden enlazarse objetos que implementan la interfaz IListSource, y podríamos preguntarnos ¿por qué?, la interfaz IListSource quizás no nos sea tan familiar como ICollection, pero es todo un indicio comentar que tanto la clase DataTable, como DataSet la implementan. Sabiendo esto quizás podríamos preguntarnos:
¿será entonces posible enlazar un DataTable o un DataSet a uno de estos controles?
La respuesta, como no podría ser de otra manera, es que si es posible, en tal caso en las propiedades DataTextField y DataValueField se deberá establecer el nombre de los campos a enlazar del DataTable, de esta forma el esquema de funcionamiento se mantendrá inalterable y el enlace podrá utilizarse para clases que implementen la interfaz ICollection tanto como IListSource. Por otra parte la interfaz IEnumerable (que era la tercera interfaz soportada por la propiedad DataSource) permitirá el enlace a objetos como por ejemplo un SqlDataReader donde los datos se van obteniendo de a uno a la vez. A continuación se muestra un ejemplo de enlace contra un DataSet, un DataTable y un SqlDataReader:
<asp:DropDownList
runat="server"
ID="DDL1"
DataTextField="Descripcion">
</asp:DropDownList>
<asp:DropDownList
runat="server"
ID="DDL2"
DataTextField="Descripcion">
</asp:DropDownList>
<asp:DropDownList
runat="server"
ID="DDL3"
DataTextField="Descripcion">
</asp:DropDownList>
private void DataReaderBinding()
{
string cnstr = ...;
SqlConnection SCon = new SqlConnection(cnstr);
SqlCommand SCom = new SqlCommand("SELECT top 11 * FROM Datos1", SCon);
SCom.Connection.Open();
SqlDataReader SRead = SCom.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
DDL1.DataSource = SRead;
DDL1.DataBind();
}
private void DataSetAndDataTableBinding()
{
string cnstr = ...;
SqlConnection SCon = new SqlConnection(cnstr);
SqlCommand SCom = new SqlCommand("SELECT top 11 * FROM Datos1", SCon);
SqlDataAdapter SDa = new SqlDataAdapter(SCom);
DataSet DSet = new DataSet();
SDa.Fill(DSet, "Datos1");
DDL2.DataSource = DSet.Tables[0] ;
DDL3.DataSource = DSet;
DDL3.DataMember = "Datos1";
DataBind();
}
En el ejemplo puede observarse el uso de la propiedad DataMember para el caso del enlace contra un objeto DataSet, la propiedad DataMember deberá utilizarse cuando sea necesario indicar el nombre del DataTable que se enlazará al control. También en el ejemplo puede observarse el uso de un método existente en la página denominado DataBind() que ejecutará todos los bindeos necesarios automáticamente ahorrandonos un poco de código.
VN:F [1.7.3_972] Rating: 9.0/10 (5 votos cast)
No tiene comentarios »
Hace unos meses Darío escribió un artículo donde explica cómo utilizar los servicios de autenticación y roles que posee ASP.NET en nuestras páginas (http://www.programandoamedianoche.com/2009/10/autenticacion-por-formularios-en-asp-net), tanto desde el servidor como utilizando AJAX (http://www.programandoamedianoche.com/2009/10/autenticacion-por-formularios-en-asp-net/3). Para complementarlo se me ocurrió escribir un artículo que explique cómo utilizar este servicio de autenticación desde Silverlight.
Para comenzar vamos a crear un proyecto Silverlight en nuestro Visual Studio, junto con su proyecto web de prueba (el cual va a exponer los servicios de autenticación).
Para utilizar estos servicios desde Silverlight (o desde cualquier aplicación externa) necesitamos publicarlos a través de WCF (Windows Communication Foundation). Para esto primero necesitamos configurar el servicio en el web.config, en la sección “Services”, apuntando a la clase “System.Web.ApplicationServices.AuthenticationService”. Cabe mencionar que necesitamos establecer la compatibilidad del servicio con ASP.NET, ya que sólo es soportado con http.
<system.serviceModel>
<services>
<service name="System.Web.ApplicationServices.AuthenticationService"
behaviorConfiguration="AuthenticationServiceTypeBehaviors">
<endpoint contract="System.Web.ApplicationServices.AuthenticationService"
binding="basicHttpBinding" bindingConfiguration="http"
bindingNamespace="http://asp.net/ApplicationServices/v200"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<!--En producción es aconsejable utilizar https-->
<binding name="http">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="AuthenticationServiceTypeBehaviors">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
Luego necesitamos habilitar los servicios web de autenticación a través de las siguientes líneas:
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true" requireSSL="false"/>
</webServices>
</scripting>
</system.web.extensions>
Y el último cambio que necesitamos hacer al web.config es para establecer la autenticación en “Forms”, utilizando:
<authentication mode="Forms" />
Bien, ahora que ya tenemos configurada nuestra aplicación debemos crear el archivo que exponga el servicio de autenticación. Para esto simplemente tenemos que agregar un archivo de tipo texto (“Text File”), para que el Visual Studio no le cree el code behind, y llamarlo, por ejempo, Authentication.svc. En este archivo vamos a agregar solamente una línea que apunte a la clase de ASP.NET que implementa el servicio de autenticación:
<%@ ServiceHost Service="System.Web.ApplicationServices.AuthenticationService" %>
Ahora ya tenemos una aplicación web que utiliza los servicios de autenticación de ASP.NET y los expone a través de servicios web, por lo tanto ahora, en el proyecto Silverlight, vamos a agregar la referencia al servicio creado en el paso anterior. Para esto pulsamos el botón derecho del mouse sobre el proyecto y elegimos la opción “Add Service Reference”, luego en el diálogo que se muestra pulsamos en “Discover” para que busque los servicios publicados en nuestra solución, elegimos “Authentication.svc” de la lista, cambiamos el namespace donde queremos que cree las clases (en nuestro ejemplo lo dejamos como está: “ServiceReference1”), y pulsamos “OK”. Esto nos creará las clases proxy para que nuestra aplicación Silverlight se conecte y utilice los servicios de autenticación expuestos por nuestra aplicación web.

La clase cliente que creamos, llamada en nuestro ejemplo “ServiceReference1.AuthenticationServiceClient”, tiene tres métodos importantes: LoginAsync, LogoutAsync e IsLoggedInAsync. Nótese que todos los métodos de los servicios web de Silverlight utilizan “Async” como sufijo debido a que siempre son asincrónicos, o sea, cuando los llamamos no se detiene la ejecución de la aplicación hasta obtener el valor solicitado, sino que se ejecutan de forma paralela y luego se llama a un evento determinado al finalizar la consulta, en nuestro caso, LoginCompleted, LogoutCompleted y IsLoggedInCompleted respectivamente.
Para nuestro ejemplo vamos a crear una grilla en la página principal de nuestra aplicación donde agregaremos tres botones para realizar el login, logout y consultar si el usuario está logeado:
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
<TextBlock Text="Usuario:" />
<TextBox x:Name="txtUser" />
<TextBlock Text="Contraseña:" />
<PasswordBox x:Name="txtPassword" />
</StackPanel>
<Button x:Name="btnLogin" Content="Login" Grid.Row="1" Click="btnLogin_Click" />
<Button x:Name="btnLogout" Content="Logout" Grid.Row="2" Click="btnLogout_Click" />
<Button x:Name="btnIsLogedIn" Content="IsLogedIn" Grid.Row="3" Click="btnIsLogedIn_Click" />
<TextBlock x:Name="lblLogin" Grid.Column="1" Grid.Row="1" />
<TextBlock x:Name="lblLogout" Grid.Column="1" Grid.Row="2" />
<TextBlock x:Name="lblIsLogedIn" Grid.Column="1" Grid.Row="3" />
<TextBlock Text="Hora último resultado:" Grid.Row="4" />
<TextBlock x:Name="lblTime" Grid.Column="1" Grid.Row="4" />
</Grid>
Y por último vamos a agregar el código que maneja los eventos de los botones del ejemplo:
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
lblLogin.Text = string.Empty;
ServiceReference1.AuthenticationServiceClient objWSAuth = new ServiceReference1.AuthenticationServiceClient();
objWSAuth.LoginCompleted += new EventHandler<ServiceReference1.LoginCompletedEventArgs>(objWSAuth_LoginCompleted);
objWSAuth.LoginAsync(txtUser.Text, txtPassword.Password, string.Empty, true);
}
void objWSAuth_LoginCompleted(object sender, ServiceReference1.LoginCompletedEventArgs e)
{
lblLogin.Text = e.Result.ToString();
lblTime.Text = DateTime.Now.ToLongTimeString();
}
private void btnLogout_Click(object sender, RoutedEventArgs e)
{
lblLogout.Text = string.Empty;
ServiceReference1.AuthenticationServiceClient objWSAuth = new ServiceReference1.AuthenticationServiceClient();
objWSAuth.LogoutCompleted += new EventHandler<AsyncCompletedEventArgs>(objWSAuth_LogoutCompleted);
objWSAuth.LogoutAsync();
}
void objWSAuth_LogoutCompleted(object sender, AsyncCompletedEventArgs e)
{
lblLogout.Text = "Listo";
lblTime.Text = DateTime.Now.ToLongTimeString();
}
private void btnIsLogedIn_Click(object sender, RoutedEventArgs e)
{
lblIsLogedIn.Text = string.Empty;
ServiceReference1.AuthenticationServiceClient objWSAuth = new ServiceReference1.AuthenticationServiceClient();
objWSAuth.IsLoggedInCompleted += new EventHandler<ServiceReference1.IsLoggedInCompletedEventArgs>(objWSAuth_IsLoggedInCompleted);
objWSAuth.IsLoggedInAsync();
}
void objWSAuth_IsLoggedInCompleted(object sender, ServiceReference1.IsLoggedInCompletedEventArgs e)
{
lblIsLogedIn.Text = e.Result.ToString();
lblTime.Text = DateTime.Now.ToLongTimeString();
}
Para probar este ejemplo tenemos que tener configurada nuestra aplicación para que utilice un proveedor de membership, por ejemplo, SqlMembershipProvider, el cual se explica en el artículo de Dario (http://www.programandoamedianoche.com/2009/10/autenticacion-por-formularios-en-asp-net/2). Luego, a través de la herramienta de configuración de ASP.NET (ASP.NET Configuration) a la que se puede acceder a través del menú “Project” del Visual Studio, podemos crear usuarios de prueba ingresando en “Security” y luego “Create User”.
Como siempre les dejo el proyecto para que lo prueben.
¡Suerte!
VN:F [1.7.3_972] Rating: 9.2/10 (5 votos cast)
No tiene comentarios »
Una 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: 5.0/10 (1 voto cast)
No tiene comentarios »
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:

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.

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:

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: 9.8/10 (12 votos cast)
5 Comentarios »
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.

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”.

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.

Dependiendo de la versión de IIS y del sistema operativo también vamos a poder vincular los certificados del cliente a usuarios del dominio y otras opciones más avanzadas, las cuales exceden el propósito de este artículo.
Ahora, al acceder al sitio vamos a ver que nos muestra que tenemos un certificado en el cual no confiamos, esto es porque el certificado raíz no está en el repositorio de confianza del Internet Explorer. Para seguir podemos pulsar sobre “Continuar con este sitio”, y luego de esto el navegador va a abrir una ventana para que elijamos un certificado de cliente, y en esa ventana vamos a ver el último certificado que creamos.
Como último paso vamos a leer el certificado y a mostrar la información básica desde una página de ASP.NET con el siguiente código agregado en el evento Load de la página:
HttpClientCertificate objCert = Request.ClientCertificate;
if (objCert.IsPresent)
{
if (objCert.IsValid)
{
System.Security.Cryptography.X509Certificates.X509Certificate2 objCert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(objCert.Certificate
Response.Write("Persona = " + objCert.Subject + "<br/>");
Response.Write("Emisor = " + objCert.Issuer + "<br/>");
Response.Write("Válido desde = " + objCert.ValidFrom + "<br/>");
Response.Write("Válido hasta = " + objCert.ValidUntil + "<br/>");
Response.Write("¿Es válido ? = " + objCert.IsValid + "<br/>");
Response.Write("Tamaño de la clave = " + objCert.SecretKeySize + "<br/>");
Response.Write("Nombre del certificado del servidor = " + objCert.ServerSubject + "<br/>");
Response.Write("Emisor del certificado del servidor = " + objCert.ServerIssuer + "<br/>");
Response.Write("Número de serie = " + objCert.SerialNumber + " - " + int.Parse(objCert2.SerialNumber, NumberStyles.HexNumber) + "<br/>");
Response.Write("Hash = " + objCert.CertEncoding + "<br/>");
}
else
Response.Write("El certificado no es válido");
}
else
Response.Write("No se ha encontrado un certificado");
Por último les dejo un archivo batch para crear los tres certificados en un paso. En el próximo artículo mostraré algunos usos más avanzados de los certificados desde .NET.
Artículos relacionados
VN:F [1.7.3_972] Rating: 9.8/10 (13 votos cast)
2 Comentarios »
Domingo, 24 may, 2009 @ 15:24 | Por Gustavo Cantero (The Wolf) | ASP.NET, GIS, Redes   |
 |
Muchos habrán notado que al ingresar en algunos sitios web éstos modifican su contenido, incluyendo idioma, publicidad, textos, etc., dependiendo del país desde el que estamos ingresando, por ejemplo, al ingresar en www.linksys.com desde Argentina éste nos redirecciona a www-ar.linksys.com, o al ingresar a www.google.com nos reenvía a www.google.com.ar. Para poder hacer esto la aplicación web busca la IP con la que estamos ingresando en una base de datos donde previamente se cargaron rangos de IP con su respectivo país.
Hasta acá todo parece muy obvio, el problema surge cuando nos preguntamos: ¿Dónde conseguimos estas bases de datos? La respuesta es sencilla: en internet.
En internet hay varios sitios que ofrecen, gratuitamente o como servicio pago, archivos con rangos de IP con su respectivo país. Muchos de estos sitios ofrecen además web services para poder consultar contra su base de datos la ubicación correspondiente a una dirección IP sin necesidad de tener los datos alojados en nuestro sitio. En este artículo voy a mostrar cómo obtener la ubicación consultando los datos en una base de datos local.
Uno de los sitios desde donde se pueden bajar estos datos es WebHosting.Info (www.webhosting.info), un sitio dedicado a dar servicios y estadísticas sobre ISP, WHOIS, Reverse IP Lookup, etc. Los datos que estos sitios ofrecen casi siempre están en archivos CSV, donde el país está codificado en ISO 3166 y la dirección IP está como un entero sin signo de 4 bytes llamado “número IP”.
Para obtener la lista de códigos de países según la ISO 3166 pueden ver en el sitio mismo de ISO, http://www.iso.org/iso/country_codes, o en Wikipedia, http://es.wikipedia.org/wiki/ISO_3166-1.
Para convertir la dirección IP en un número IP (un entero de 4 bytes) es una simple fórmula matemática donde lo que tenemos que hacer es un “shift left” de cada número para “posicionarlo” en el octeto correspondiente o, en español, moverlo hasta el byte correspondiente. Como cada byte representa 256 números (de 0 a 255), lo que debemos hacer es multiplicar cada octeto de la dirección por 256 elevado a la posición de ese byte. Supongamos que nuestra dirección a convertir es A.B.C.D, entonces lo que debemos hacer es:
(A * 256^3) + (B * 256^2) + (C * 256^1) + (D * 256^0)
Si reemplazamos las constantes por su resultado nos quedaría lo siguiente:
(A * 16777216) + (B * 65536) + (C * 256) + D
Ahora supongamos que la IP de mi cliente es la 190.2.8.88, entonces deberíamos hacer:
(190 * 16777216) + (2 * 65536) + (8 * 256) + 88 =
3187671040 + 131072 + 2048 + 88 = 3187804248
Cabe mencionar que para obtener la dirección IP del usuario que se está conectando a nuestro sitio se debería leer el encabezado de la petición http. Para los desarrolladores de .NET esto se puede hacer simplemente utilizando la propiedad UserHostAddress del objeto Request del Page, por ejemplo, en C# sería:
string strIP = Page.Request.UserHostAddress;
o, para los usuarios de Visual Basic.NET:
Dim strIP as string = Page.Request.UserHostAddress
Obviamente la IP está en un string, con lo cual debería dividir cada octeto de la dirección, convertirlo a un tipo de dato numérico y hacer la fórmula. A continuación les dejo un ejemplo en C#:
//Convierto la IP a un entero de 32 bits
string[] strBytes = Page.Request.UserHostAddress.Split('.');
uint intNumeroIP=
(uint.Parse(strBytes[0]) * 16777216) +
(uint.Parse(strBytes[1]) * 65536) +
(uint.Parse(strBytes[2]) * 256) +
uint.Parse(strBytes[3]);
y, para los usuarios de Visual Basic.NET:
'Convierto la IP a un entero de 32 bits
Dim strBytes As String() = Page.Request.UserHostAddress.Split('.')
Dim intNumeroIP As UInteger = _
(UInteger.Parse(strBytes(0)) * 16777216) + _
(UInteger.Parse(strBytes(1)) * 65536) + _
(UInteger.Parse(strBytes(2)) * 256) + _
UInteger.Parse(strBytes(3))
Nótese que en el ejemplo utilicé un entero sin signo, esto es porque el número IP no lleva signo. También cabe notar que, aunque cada octeto se puede representar con un byte, yo lo convierto a un entero sin signo para evitar la conversión del tipo de dato al almacenarlo en la variable final.
Ahora con este número en mente lo podemos buscar en el archivo con la información de la localización de las IPs. Obviamente lo mejor es guardar previamente la información de este archivo en una base de datos. Como mínimo deberíamos guardar la dirección IP y el país. Muchos de estos archivos de IP nos traen una “dirección desde” y una “dirección hasta” por cada línea del archivos, pero con almacenar la “dirección desde” ya nos alcanza.
Para los usuarios de motores de bases de datos como Oracle o MySql no tendrán problema con guardar los números IP en campos enteros de 4 bytes sin signo, pero para los usuarios de SQL Server se nos complica, ya que este motor no permite utilizar este tipo de datos. Para solucionar este inconveniente podemos hacer dos cosas: guardar el número en un campo del tipo entero de 8 bytes o a cada número IP restarle 2147483648.
Ahora sólo nos quedaría buscar la IP del usuario en la base, con un script sencillo como el siguiente para SQL Server:
SELECT TOP 1 Pais
FROM ListaIPs
WHERE IP >= 3187804248
ORDER BY IP DESC
Si en SQL Server elegimos utilizar un entero de 4 bytes (int) y restarle la mitad para no utilizar un entero de 8 bytes (bigint) habría que utilizar el valor 1040320600 en lugar de 3187804248.
Si quisiéramos hacer esta misma consulta pero utilizando MySql como motor de base de datos deberíamos utilizar un script como el siguiente:
SELECT Pais
FROM ListaIPs
WHERE IP >= 3187804248
ORDER BY IP DESC
LIMIT 1
Bien, hasta acá ya sabemos cómo obtener el país correspondiente a una dirección IP pero, como el título bien dice, también podemos obtener la ciudad correspondiente a esta dirección. Para esto la solución vuelve a ser sencilla, solamente tenemos que buscar en internet donde se puede conseguir una lista de IPs correspondiente a cada ciudad. Un lugar donde se puede obtener esta lista es en IP Location Tools (http://iplocationtools.com), donde se puede conseguir la lista de país por IP y ciudad por IP, en formato CSV y en SQL. La única diferencia con el proceso anterior es que en la base de datos, además o en lugar de guardar el país, se debería guardar la ciudad.
Espero que este artículo les haya sido de utilidad y, como siempre, los animo a escribir sus consultas, sugerencias y comentarios.
VN:F [1.7.3_972] Rating: 7.6/10 (11 votos cast)
4 Comentarios »
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.
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.

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”.

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.

Los nuevos tutoriales, junto con los que ya existían, se pueden encontrar en la siguiente dirección: http://www.asp.net/learn/ajax/
VN:F [1.7.3_972] Rating: 7.8/10 (12 votos cast)
No tiene comentarios »
Miércoles, 06 may, 2009 @ 00:48 | Por Gustavo Cantero (The Wolf) | ASP.NET, WebParts |
 |
Hace un tiempo desarrollamos un tablero de control para un cliente, el cual nos pedía que éste fuera personalizable por el usuario, tanto en contenido y estética, como en la posición de los controles a visualizar, motivo por el cual nos decidimos a construirlo con WebParts. La primera versión tuvo buena aceptación y gustó mucho, por lo cual en la segunda versión nos pidieron que hubieran más componentes y que éstos tuvieran muchas más opciones de configuración. Hasta acá todo iba bien, pero nos dimos cuenta que al agregar muchos WebParts al catálogo éste se hacía incómodo de utilizar (el catálogo tiene más de 200 componentes), y al agregarle muchas opciones de edición a éstos componentes era incómoda la edición, ya que esta se hacía muy extensa y el usuario debía “scrollear” mucho la página. Para resolver estos inconvenientes hicimos dos nuevos controles que utilizamos en ese desarrollo: un catálogo de componentes que utiliza un árbol (del cual hablaré en otro artículo), y una zona de edición donde cada EditorPart es “contraíble” para ocupar menos espacio en la página. Sobre éste último hablaremos en este artículo.
La idea será hace una zona de edición que se asemeje a la imagen a continuación:

Para modificar la forma en que se van a ver los editores de los componentes hay que crear una nueva “zona de edición”, la cual debe heredar de EditorZone y llamaremos CollapsibleEditorZone. En esta clase cambiaremos el método que devuelve el “chrome” a utilizar en esta zona, el cual modificaremos para que le agregue la opción de contraer y expandir como se ve en la imagen anterior. A nuestro “chrome” lo vamos a llamar EditorPartChrome, por lo tanto, el código de la clase CollapsibleEditorZone debería quedar como se muestra en el código que se encuentra a continuación:
using System.Web.UI.WebControls.WebParts;
/// <summary>
/// Nuevo editor de webparts
/// </summary>
public class CollapsibleEditorZone : EditorZone
{
/// <summary>
/// Escribe el control
/// </summary>
/// <param name="writer">Objeto sobre el cual escribir</param>
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
writer.Write("<span class="CollapsibleEditorZone">");
base.Render(writer);
writer.Write("</span>");
}
/// <summary>
/// Obtiene una referencia a un nuevo objeto <see cref="CollapsibleEditorChrome"/>
/// que se utiliza para representar los editores
/// </summary>
/// <returns>Un objeto <see cref="CollapsibleEditorChrome"/></returns>
protected override EditorPartChrome CreateEditorPartChrome()
{
return new CollapsibleEditorChrome(this);
}
}
Ahora que tenemos la nueva zona tenemos que crearle el “chrome” del que hablamos antes. Esta nueva clase debe heredar de EditorPartChrome y debe tener el constructor (para que utilice el de la clase base) y dos métodos: uno para escribir el contenido de cada editor a utilizar y otro para dibujar el título de cada uno de éstos, en donde van a estar los botones para contraer y expandir el contenido. Estos botones van a ejecutar un JavaScript que modifica el estilo “display” de un DIV que envuelve el editor, para evitar hacer postbacks innecesarios.
Pero no todo es tan sencillo como parece, supongamos que el usuario expande algunos editores, modifica algunos parámetros del componente y luego pulsa sobre “aplicar” para ver los cambios, la aplicación va a hacer un postback y aplicará los cambios sobre las propiedades de los WebParts, pero al volver a dibujar los editores éstos se verán contraídos nuevamente. Para solucionar esto agregamos un INPUT HIDDEN por cada editor donde guardamos del lado del cliente el último estado del editor, así cuando hacemos un postback simplemente tenemos que verificar el valor de este campo para saber si estaba expandido o contraído.
A continuación muestro el código de esta clase:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
/// <summary>
/// PartChrome de editores para hacerlos contraibles
/// </summary>
public class CollapsibleEditorChrome : EditorPartChrome
{
/// <summary>
/// Constructor del chrome
/// </summary>
/// <param name="Zone">Zona</param>
public CollapsibleEditorChrome(CollapsibleEditorZone Zone)
: base(Zone)
{
}
/// <summary>
/// Representa un control <see cref="EditorPart"/> completo con todas sus secciones.
/// </summary>
/// <param name="Writer">Objeto <see cref="HtmlTextWriter"/> que recibe el contenido de editorPart.</param>
/// <param name="EditorPart">Control que se está procesando en la actualidad.</param>
public override void RenderEditorPart(HtmlTextWriter Writer, EditorPart EditorPart)
{
if (EditorPart == null)
throw new ArgumentNullException("EditorPart");
PartChromeType enuPartChromeType = Zone.GetEffectiveChromeType(EditorPart);
Style objPartChromeStyle = CreateEditorPartChromeStyle(EditorPart, enuPartChromeType);
objPartChromeStyle.AddAttributesToRender(Writer, Zone);
Writer.RenderBeginTag(HtmlTextWriterTag.Fieldset);
if (enuPartChromeType == PartChromeType.TitleAndBorder || enuPartChromeType == PartChromeType.TitleOnly)
RenderTitle(Writer, EditorPart);
Style objPartStyle = Zone.PartStyle;
objPartStyle.AddAttributesToRender(Writer, Zone);
Writer.AddAttribute(HtmlTextWriterAttribute.Id, EditorPart.ClientID + "_div");
if (EditorPart.Page.Request.Form.Get(EditorPart.ClientID + "_state") == "1")
Writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "inline");
else
Writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none");
Writer.RenderBeginTag(HtmlTextWriterTag.Div);
RenderPartContents(Writer, EditorPart);
Writer.RenderEndTag(); // div
Writer.RenderEndTag();// fieldset
}
/// <summary>
/// Representa el título de un control <see cref="EditorPart"/>.
/// </summary>
/// <param name="Writer">Objeto <see cref="HtmlTextWriter"/> que recibe el contenido de editorPart.</param>
/// <param name="EditorPart">Control que se está procesando en la actualidad.</param>
private void RenderTitle(HtmlTextWriter Writer, EditorPart EditorPart)
{
string strDisplayTitle = EditorPart.DisplayTitle;
if (!String.IsNullOrEmpty(strDisplayTitle))
{
Style objTitleTextStyle;
TableItemStyle objPartTitleStyle = this.Zone.PartTitleStyle;
Style objStyle = new Style();
objStyle.CopyFrom(objPartTitleStyle);
objTitleTextStyle = objStyle;
objTitleTextStyle.AddAttributesToRender(Writer, Zone);
string strDescription = EditorPart.Description;
if (!String.IsNullOrEmpty(strDescription))
Writer.AddAttribute(HtmlTextWriterAttribute.Title, strDescription);
string strAccessKey = EditorPart.AccessKey;
if (!String.IsNullOrEmpty(strAccessKey))
Writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, strAccessKey);
Writer.RenderBeginTag(HtmlTextWriterTag.Legend);
if (EditorPart.Page.Request.Form.Get(EditorPart.ClientID + "_state") == "1")
Writer.AddAttribute(HtmlTextWriterAttribute.Src, EditorPart.ResolveClientUrl("~/Images/Minus.gif"));
else
Writer.AddAttribute(HtmlTextWriterAttribute.Src, EditorPart.ResolveClientUrl("~/Images /Plus.gif"));
Writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "collapseExpandEditor('" + EditorPart.ClientID + "');");
Writer.AddAttribute(HtmlTextWriterAttribute.Width, "9px");
Writer.AddAttribute(HtmlTextWriterAttribute.Height, "9px");
Writer.AddAttribute(HtmlTextWriterAttribute.Align, "middle");
Writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "hand");
Writer.AddAttribute(HtmlTextWriterAttribute.Id, EditorPart.ClientID + "_img");
Writer.RenderBeginTag(HtmlTextWriterTag.Img);
Writer.RenderEndTag(); // img
Writer.AddAttribute(HtmlTextWriterAttribute.Name, EditorPart.ClientID + "_state");
Writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden");
Writer.AddAttribute(HtmlTextWriterAttribute.Value, EditorPart.Page.Request.Form.Get(EditorPart.ClientID + "_state"));
Writer.RenderBeginTag(HtmlTextWriterTag.Input);
Writer.RenderEndTag(); // input
Writer.WriteFullBeginTag("B");
Writer.Write(' ');
Writer.Write(strDisplayTitle);
Writer.WriteEndTag("B");
Writer.RenderEndTag(); // legend
}
}
}
Cabe mencionar que para mostrar los botones de “más” y “menos” de cada editor la aplicación web debe tener las imágenes “plus.gif” y “minus.gif” dentro de la carpeta “images”.
Ahora faltaría mostrar el JavaScript que utiliza estas clases del lado del cliente para ocultar y mostrar los editores:
function collapseExpandEditor(id) {
var editor = $get(id + '_div');
var img = $get(id + '_img');
var input = $get(id + '_state');
if (editor.style.display == 'none') {
editor.style.display = 'inline';
img.src = 'Images/Minus.gif';
input.value = '1';
} else {
editor.style.display = 'none';
img.src = 'Images/Plus.gif';
input.value = '';
}
}
En este ejemplo nosotros utilizamos el ScriptManager de ASP.NET, por eso se ve que usamos el método $get(), pero si prefieren no usarlo simplemente lo tienen que cambiar por document.getElementById().
Por si no se dieron cuenta, en la clase CollapsibleEditorZone creamos un SPAN donde metemos todos los editores, el cual tiene un estilo llamado igual que la clase. Este estilo lo utilizamos para agregarle el borde a cada uno de los editores:
.CollapsibleEditorZone FIELDSET
{
border: 1px solid #777777;
padding: 2px 2px 2px 2px;
}
.CollapsibleEditorZone FIELDSET FIELDSET
{
border: 1px solid #aaaaaa;
margin-bottom: 6px;
}
Lo último que quedaría hacer es agregar esta clase en alguna página, la cual debería quedar algo como lo que se muestra a continuación:
<wp:CollapsibleEditorZone ID="cezEdit" runat="server" VerbButtonType="Button"
HeaderCloseVerb-Visible="false" Width="100%">
<ApplyVerb Text="Aplicar" />
<OKVerb Text="Aceptar" />
<CancelVerb Text="Cancelar" />
<ZoneTemplate>
<asp:PropertyGridEditorPart runat="server" />
</ZoneTemplate>
</cwp:CollapsibleEditorZone>
Nótese que el prefijo “wp” debe apuntar al namespace donde se encuentra la clase CollapsibleEditorZone.
Espero que este control les sea de utilidad y como siempre los invito a dejar sus opiniones y comentarios.
VN:F [1.7.3_972] Rating: 8.0/10 (1 voto cast)
No tiene comentarios »
Jueves, 26 feb, 2009 @ 13:26 | Por Gustavo Cantero (The Wolf) | ASP.NET |
 |
Ayer, luego de escribir el artículo “Usar variables de sesión en servicios web de WCF”, recordé otro lugar donde, por defecto, no se pueden utilizar variables de sesión, pero que muchas veces son necesarias: los HttpHandlers. Estas clases representan un “handler” que puede accederse a través del navegador pero no necesariamente devuelven una página en HTML o XHTML. Un ejemplo clásico de una implementación de estas clases es para generar imágenes dinámicamente o buscarla en la base de datos y enviársela al browser, para realizar algún proceso y reenviar al usuario a otra URL o para generar XML para algún componente del lado del cliente, por ejemplo, de Silverlight o Flash.
El esqueleto de la clase es bastante sencillo, es algo como esto:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Download : IHttpHandler {
public void ProcessRequest(HttpContext Context) {
...
}
public bool IsReusable {
get { return false; }
}
}
Si dentro del método ProcessRequest queremos acceder al objeto System.Web.HttpContext.Current.Session nos va a devolver “null”. Para que este objeto no sea nulo y nos devuelva la colección de variables de sesión simplemente hay que hacer que nuestra clase, además de heredar de la interfaz IHttpHandler, herede de la interfaz IRequiresSessionState, quedando el esqueleto algo como esto:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Download : IHttpHandler, IRequiresSessionState {
public void ProcessRequest(HttpContext Context) {
...
}
public bool IsReusable {
get { return false; }
}
}
Con esta pequeña modificación en la herencia de la clase ya podremos acceder al objeto Session sin necesidad de implementar ningún método ni propiedad nueva, ya que el motor de ASP.NET utiliza esta interfaz como una marca para saber si tiene que cargar o no las variables de sesión.
VN:F [1.7.3_972] Rating: 7.3/10 (3 votos cast)
No tiene comentarios »
Miércoles, 25 feb, 2009 @ 20:47 | Por Gustavo Cantero (The Wolf) | ASP.NET, WCF  |
 |
Muchas veces necesitamos guardar información sensible por cada sesión de usuario de una aplicación web, y muchas de esas veces decidimos guardarlas en variables de sesión, ya que son seguras y rápidas, aunque consumen recursos del servidor, pero con un uso medido son una buena solución. El problema surge cuando estas variables deben ser utilizadas por servicios web de WCF (Windows Communication Foundation), y que al intentar acceder a ellas a través del objeto System.Web.HttpContext.Current.Session nos devuelve la clásica excepción Object reference not set to an instance of an object. Esto es debido a que la propiedad Current del objeto HttpContext es nula. La causa de este comportamiento es debido a que en el WCF se desacopló los servicios y sus operaciones de ASP.NET, ya que el transporte de éstos no tiene que ser necesariamente http.
Para poder acceder al contexto web desde un servicio de WCF hay que utilizar el atributo AspNetCompatibilityRequirements para especificar el modo de compatibilidad que va a tener con ASP.NET. Por ejemplo, si tenemos un servicio llamado “ServiceTest”, y queremos que tenga acceso a las variables de sesión, el mismo debería quedar como esto:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TestService : ITestService {
//operaciones del servicio
}
Pero esto no es todo, todavía hay que escribir un poco más. Hasta ahora marcamos a nuestro servicio como que “puede” utilizar la compatibilidad con ASP.NET, pero si en este punto consultamos la propiedad Current del HttpContext veremos que sigue siendo nula, y peor aún si en lugar de poner “Allowed” en el atributo de compatibilidad pusimos “Required”: se nos va a generar una excepción. Esto es causado porque aún falta configurar la aplicación para utilizar la compatibilidad con ASP.NET. Para que los servicios web puedan utilizar esta característica hay que modificar el web.config agregando (o modificando) la siguiente línea dentro de <configuration><system.serviceModel>:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
Una vez hecho esto ya deberíamos poder acceder a nuestras variables de sesión con un código parecido al siguiente:
object sessionVar = System.Web.HttpContext.Current.Session["MiVariable"];
VN:F [1.7.3_972] Rating: 8.5/10 (4 votos cast)
1 Comment »
Jueves, 18 sep, 2008 @ 12:10 | Por Gustavo Cantero (The Wolf) | .NET, ASP.NET, IIS   |
 |
Cuando accedemos por primera vez a una aplicacion web hecha con .NET el IIS compila las páginas y guarda los assemblies generados (junto con los de la carpeta bin) en una carpeta temporal de cache, la cual está en el mismo lugar donde se instala el framework. Rara vez sucede que el IIS no refresca esta cache, y cuando actualizamos nuestras páginas o assemblies, éstas no se ven reflejadas. Para asegurarnos que esta actualización suceda debemos limpiar esta cache, y para hacerlo debemos seguir los siguientes pasos:
- Detener el IIS, lo cual se puede hacer con el comando
"IISReset /stop"
- Eliminar el contenido de la carpeta “C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files”
- Iniciar nuevamente el IIS, lo cual se puede hacer con el comando
"IISReset"
Luego de esto, al ingresar en nuestra aplicación, el IIS vuelve a compilar el sitio y generar los archivos de cache.
VN:F [1.7.3_972] Rating: 8.9/10 (9 votos cast)
2 Comentarios »
Miércoles, 09 jul, 2008 @ 01:03 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET  |
 |
Desarrollando una aplicación web para un cliente hicimos que todas las ventanas de la misma se mostraran con un ModalPopUp de la librería AJAX Control Toolkit de Microsoft, utilizando dentro de ésta el mismo diseño que para el resto del sitio, logrando así una imagen homogénea en toda la aplicación. El primer inconveniente que tuvimos fue cuando nuestro cliente nos solicitó que TODOS los popup de la aplicación fueran iguales, incluidos los de los validadores, los cuales se muestran con un SummaryValidator en un “alert” de JavaScript. Reemplazar esto fue sencillo, ya que simplemente creamos una función llamada “Alert” (nótese que se diferencia de la función original de JavaScript en que la primer letra está en mayúscula) que escribía el mensaje que se le pasara a través de un parámetro dentro del ModalPopUp, lo mostraba, y lo colocaba sobre el resto de los posibles ModalPopUp que hubiesen cambiando el zIndex de los layers, de la misma manera que lo mostramos en el artículo “Deshabilitar controles de la página hasta que finalicen los UpdatePanels”. Luego simplemente reemplazamos la llamada al alert con el siguiente código:
window.alert = Alert;
El código JavaScript quedaba como se muestra a continuación:
function Alert(msg) {
$get('divAlertContent').innerText = msg;
popUpShowed = $find('Alert');
popUpShowed.show();
popUpShowed._backgroundElement.style.zIndex += 10;
popUpShowed._foregroundElement.style.zIndex += 10;
}
y en la página colocamos un código parecido al siguiente (al presentado aquí le quitamos el diseño para que quede más legible):
<asp:Button ID="btnAlert" runat="server" Style="display: none" />
<cc1:ModalPopupExtender ID="mpeAlert" runat="server" Enabled="True" PopupControlID="panAlert" DropShadow="true" TargetControlID="btnAlert" OkControlID="btnAcceptAlert" BehaviorID="Alert" />
<asp:Panel ID="panAlert" runat="server" HorizontalAlign="Center" Style="display: none" Width="370px" Height="100px">
<div id="divAlertContent"></div>
<br />
<asp:Button ID="btnAcceptAlert" runat="server" Text="OK" />
</asp:Panel>
Esto daba como resultado que todos los “alerts” de la aplicación se mostraran en una ventana del mismo diseño.
El siguiente pedido que nos hizo el cliente fue que, al igual que los alerts “tradicionales”, éstos pudieran cerrarse al pulsar la tecla ESC. Para lograr esto modificamos la función anterior para que al mostrar el ModalPopUp guardara una referencia al mismo en una variable global llamada popUpShowed. Luego agregamos una llamada a una función nuestra en el evento “pageLoad” del framework de AJAX, la cual chequea si el usuario pulsó la tecla ESC y, en caso de que popUpShowed no sea nula, cerramos el PopUp al que referencia.
A continuación muestro una página de ejemplo con todo el código para que mostrar la función “Alert” funcionando.
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>">
<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
<title>Página de ejemplo</title>
<script type="text/javascript">
var popUpShowed = null;
function pageLoad() {
document.body.onkeypress=keyPressHandler;
window.alert=Alert;
}
function showPopUp(popUp){
popUpShowed = $find(popUp);
popUpShowed.show();
}
function Alert(msg){
$get('divAlertContent').innerText = msg;
popUpShowed = $find('Alert');
popUpShowed.show();
popUpShowed._backgroundElement.style.zIndex += 10;
popUpShowed._foregroundElement.style.zIndex += 10;
}
function keyPressHandler(e){
if (popUpShowed != null){
var kC = (window.event) ? event.keyCode : e.keyCode;
var Esc = (window.event) ? 27 : e.DOM_VK_ESCAPE;
if(kC==Esc){
popUpShowed.hide();
popUpShowed = null;
}
}
}
</script>
<style>
.ModalBackground {
background-color: Gray;
filter: alpha(opacity=70);
opacity: 0.7;
}
.ModalPopup {
background-color: #eeeeee;
border-width: 1px;
border-style: solid;
border-color: Gray;
padding: 3px;
font-size: small;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<input type="button" value="Ejemplo" onclick="alert('Ejemplo desde javascript')" />
<asp:Button ID="btnAlert" runat="server" Style="display: none" />
<cc1:ModalPopupExtender ID="mpeAlert" runat="server" Enabled="True" PopupControlID="panAlert"
DropShadow="true" TargetControlID="btnAlert" OkControlID="btnAcceptAlert" BehaviorID="Alert"
BackgroundCssClass="ModalBackground" OnOkScript="popUpShowed = null" />
<asp:Panel ID="panAlert" runat="server" HorizontalAlign="Center" Style="display: none"
Width="370px" Height="100px" CssClass="ModalPopup">
<div id="divAlertContent">
</div>
<br />
<asp:Button ID="btnAcceptAlert" runat="server" Text="OK" />
</asp:Panel>
</form>
</body>
</html>
Por último les dejo un proyecto hecho con Visual Studio 2008 con un ejemplo del “Alert”

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS
VN:F [1.7.3_972] Rating: 8.2/10 (9 votos cast)
6 Comentarios »
Jueves, 19 jun, 2008 @ 00:30 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET  |
 |
Hace unos meses comenzamos a desarrollar para un cliente una aplicación web más o menos compleja, con .NET 3.5 y bastante uso de AJAX. En esta aplicación hay páginas que utilizan varios UpdatePanels, y algunos de estos realizan procesos complejos que pueden llegar a demorar varios segundos, es por esto que nuestro cliente nos pidió que, al iniciar estos procesos, se bloquee la página para que el usuario no pueda ejecutar ninguna otra acción en la misma hasta tanto no finalice el proceso pendiente. Obviamente no queríamos ejecutar un script “a mano” cada vez que se ejecutaba una acción y otro al finalizar la misma, así que buscamos la manera de automatizarlo un poco. Comenzamos buscando los eventos propios del motor de AJAX de Microsoft® y encontramos que podemos capturar los del RequestManager cuando inicia una petición al servidor y cuando la misma finaliza. Esto lo podemos hacer utilizando los métodos add_initializeRequest y add_endRequest de la instancia actual del PageRequestManager, la cual podemos obtener con el siguiente código: Sys.WebForms.PageRequestManager.getInstance().
En el primer evento mostrábamos un ModalPopUp (control que muestra una “ventana” en la página y deshabilita todos los demás controles de la misma) de la librería ASP.NET AJAX Control Toolkit de Microsoft®, el cual contenía un mensaje que decía “Aguarde un momento…”, y en el segundo evento lo cerrábamos. A continuación se muestra como quedaba el PopUp en la página:
<cc1:ModalPopupExtender ID="mpeLoading" runat="server" BehaviorID="idmpeLoading" PopupControlID="pnlLoading" TargetControlID="btnLoading" EnableViewState="false" DropShadow="true" BackgroundCssClass="ModalBackground" />
<asp:Panel ID="pnlLoading" runat="server" Width="300" Height="50" HorizontalAlign="Center" CssClass="ModalPopup" EnableViewState="false" Style="display: none">
<br />Aguarde un momento...</asp:Panel>
<asp:Button ID="btnLoading" runat="server" Style="display: none" />
y acá está el código JavaScript:
function initializeRequest(sender, args){
$find('idmpeLoading').show();
}
function endRequest(sender, args){
$find('idmpeLoading').hide();
}
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(initializeRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
Cabe aclarar que la variable idmpeLoading del código anterior contiene el identificador del ModalPopUp a mostrar.
Hasta este punto todo funcionaba bien, el problema surgió cuando utilizamos paneles modales con UpdatePanels dentro, en ese momento la ventana con el mensaje “Aguarde un momento…” se “dibujaba” debajo del panel que estaba utilizando el usuario, no cumpliendo con su finalidad, ya que de esta manera no se deshabilitaban todos los controles de la página.
Paso siguiente revisamos el código del ModalPopUp (el mismo se puede bajar desde el mismo link que mostré antes) y encontramos que a los paneles se les estable un valor fijo en el estilo zIndex para mostrarlos sobre los demás controles. Luego revisamos las propiedades del objeto de JavaScript que representa el panel y encontramos que posee dos objetos interesantes: _backgroundElement y _foregroundElement. Estos representan los DIVs del fondo y del contenido de PopUp respectivamente, entonces para hacer que estos objetos de HTML se posicionen sobre todos los demás simplemente tenemos que incrementarles la propiedad zIndex de su estilo. Entonces, nuestro código JavaScript queda como se muestra a continuación:
function initializeRequest(sender, args){
var mpeLoading = $find(idmpeLoading);
mpeLoading.show();
mpeLoading._backgroundElement.style.zIndex += 10;
mpeLoading._foregroundElement.style.zIndex += 10;
}
function endRequest(sender, args){
$find(idmpeLoading).hide();
}
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(initializeRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
Por último, para hacer que esta funcionalidad esté presente en todas las páginas de nuestra aplicación, lo agregamos en la página “master” del sitio, logrando que en cualquier página (que utilice esta master) posea la funcionalidad aquí descripta.
En la siguiente imagen muestro como queda una aplicación de ejemplo que cree con Visual Studio 2008 para este artículo:

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

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS
VN:F [1.7.3_972] Rating: 8.4/10 (8 votos cast)
6 Comentarios »
|
|