Enlace via código de grillas en 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)

Enlace de elementos en controles de lista en ASP.NET

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)

Utilizar Autenticación de ASP.NET desde Silverlight

Martes, 23 mar, 2010 @ 21:38 | Por Gustavo Cantero (The Wolf) | ASP.NET, Seguridad, Silverlight, WCF

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.

Agregar servicio web de Autenticación

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

Crear usuario

Descargar proyecto de ejemplo para utilizar Autenticación de ASP.NET desde SilverlightComo siempre les dejo el proyecto para que lo prueben.
¡Suerte!

VN:F [1.7.3_972]
Rating: 9.2/10 (5 votos cast)

Nueva versión del ASP.NET AJAX Control Toolkit

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

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

HTMLEditor

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

ajax-htmleditor

ComboBox

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

ColorPicker

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

Nuevos tutoriales

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

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

Editores de WebParts contraibles

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

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

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

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

using System.Web.UI.WebControls.WebParts;

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

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

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

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

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

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

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

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

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

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

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

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

        RenderPartContents(Writer, EditorPart);

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

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

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

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

            objTitleTextStyle.AddAttributesToRender(Writer, Zone);

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

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

            Writer.RenderBeginTag(HtmlTextWriterTag.Legend);

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

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

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

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

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

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

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

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

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

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

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

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

Usar variables de sesión en HttpHandlers

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

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

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

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

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

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

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

Usar variables de sesión en servicios web de WCF

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

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

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

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

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

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

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

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

Eliminar archivos temporales de ASP.NET

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

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

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

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

VN:F [1.7.3_972]
Rating: 8.9/10 (9 votos cast)

“Alert” personalizado

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

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

window.alert = Alert;

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

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

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

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

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

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

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

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

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

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

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS

VN:F [1.7.3_972]
Rating: 8.2/10 (9 votos cast)

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

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

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

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

y acá está el código JavaScript:

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

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

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

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

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

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

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS

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