Monday, July 12, 2004

Consuming XML Web Services from Javascript

Si bien es cierto ASP.NET nos brinda un mayor control sobre los elementos que colocamos sobre nuestras paginas, gracias al soporte que se brinda a traves ViewState y el modelo de objetos que propone el Framework para este tipo de aplicaciones.

Sin embargo, algunas veces el constante PostBack, no es muy bienvenido -que digamos- para muchos usuarios y esto trae como consecuencia darles a nuestra aplicaciones un comportamiento diferente, para ciertas interfaces de usuario que ameritan una implementacion hibrida.

La posibilidad de realizar operaciones sobre el servidor web, sin necesidad de postear la pagina, mostrar informacion a la usuario en funcion a las acciones que realize este sobre la interfaz que le otorgamos, tambien es factible de realizar en el mundo de ASP.NET.

Algunos le dice Callbacks, tecnicamente hablando es darle a nuestros scripts por el lado del usuario, una forma de comunicarse con el web server para realizar operaciones y obtener resultados.

Para ello existen muchas formas: Web Services Behaviors, Remote Scripting, Artificios usando Frames ocultos, etc.

En esta oportunidad la intencion es mostrarles, la forma de conseguirlo usando XML Web Services y el XMLHTTP, un componente del Microsoft XML Core Services.


Archivo .ASPX

<%@ Page language="c#" Codebehind="ParentChild.aspx.cs" AutoEventWireup="false" Inherits="DemoWebAppCSharp.Services.ParentChild" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>ParentChild</title>
<script language="javascript" src="../Resource/FactoryDom.js"></script>
<script language="javascript">
var _server = 'http://localhost';
var _app = '/DemoWebAppCSharp';
var _asmx = '/Services/General.asmx'

// Este metodo esta enganchado al evento onload del body.
function Load()
{
ParentRecordSet();
}

// Este metodo esta enganchado al evento onchange del DropDownList
// _child, en su representacion HTML como SELECT.
function Show( object )
{
ChildRecordSet( object.value );
}

// Este metodo esta enganchado al evento onclick del Button _submit
// y permite salvar la informacion de los DropDownList.
function Capture()
{
document.all( '_parent_selected' ).value = document.all( '_parent' ).value;
document.all( '_child_selected' ).value = document.all( '_child' ).value;
}

// Transforma la informacion de los documentos XML a un vocabulario
// comun a traves de una transformacion XSLT
function ToOptions( xmlstring, xsltfile )
{
factory = new FactoryDom();

xmldom = factory.CreateXml( xmlstring );
xsltdom = factory.CreateXslt( xsltfile );

output = factory.Transform( xmldom, xsltdom );
if( output == null )
{
alert( 'Error al ejectuar Transform' );
return null;
}

return output;
}

// Obtiene la informacion de los itemes padres a traves del
// XML Web Services
function ParentRecordSet()
{
url = new UrlTarget( _server + _app + _asmx + '/ParentRecordSet' );

ws = new WebService( url );
xmlstring = ws.Execute();
xsltfile = '../Resource/ParentItem.xslt';

var output = ToOptions( xmlstring, xsltfile );

childNodes = output.documentElement.childNodes;

_parent = document.all( '_parent' );
AddChoose( _parent );

while( ( node = childNodes.nextNode() ) != null )
AddOption( _parent, node );
}

// Obtiene la informacion de los itemes hijos a traves del
// XML Web Services
function ChildRecordSet( Parent )
{
url = new UrlTarget( _server + _app + _asmx + '/ChildRecordSet' );
url.Append( 'Parent', Parent );

ws = new WebService( url );
xmlstring = ws.Execute();
xsltfile = '../Resource/ChildItem.xslt';

var output = ToOptions( xmlstring, xsltfile );

childNodes = output.documentElement.childNodes

_child = document.all( '_child' );
RemoveAll( _child );
AddChoose( _child );

while( ( node = childNodes.nextNode() ) != null )
AddOption( _child, node );
}

// Inserta una opcion por default en los DropDownList
function AddChoose( select )
{
var option = document.createElement( 'option' );
option.value = '-1';
option.text = '-- Elija una opcion --';

select.options.add( option );
}

// Insert una opcion a parti de los datos del node
function AddOption( select, node )
{
var option = document.createElement( 'option' );
option.value = node.selectSingleNode( '@value' ).text;
option.text = node.text;

select.options.add( option );
}

// Elimina todos los options de un DropDownList
// especialmente usado para el _child
function RemoveAll( select )
{
while( select.options.length > 0 )
select.options.remove( 0 );
}

// UrlTarget (Objecto)
// Permite realizar la construccion de una peticion POST hacia un destino
function UrlTarget( target )
{
this._empty = true;
this._target = target;
this._post = '';

this.Append = UrlTarget_Append;
}

// Metodo
function UrlTarget_Append( name, value )
{
if( this._empty )
this._empty = false;
else
this._post += '&';

this._post += name + '=' + value;
}

// WebService (Objeto)
// Encapsula el pedido a un XML Web Service a traves de peticiones POST
function WebService( Url )
{
this._url = Url;
this.Execute = WebService_Execute;
}

function WebService_Execute()
{
factory = new FactoryDom();
http = factory.CreateHttp();
http.Open( 'POST', this._url._target, false );
http.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' )
http.Send( this._url._post );

if( http.status != 200 )
{
message = '';
message += 'Hubo un error al ejecutar el WebService\n';
message += 'url: ' + this._url._target + '\n';
message += 'post: ' + this._url._post + '\n';
message += 'status: ' + http.status;

alert( message );
if( http.status == 500 )
_error.innerHTML = http.responseText;
return null
}

return http.responseText;
}
</script>
</HEAD>
<body onload="Load();">
<form id="ParentChild" method="post" runat="server">
<table>
<tr>
<td><asp:dropdownlist id="_parent" runat="server" Width="168px"></asp:dropdownlist></td>
</tr>
<tr>
<td><asp:dropdownlist id="_child" runat="server" Width="168px"></asp:dropdownlist></td>
</tr>
<tr>
<td id="_error"></td>
</tr>
</table>
<asp:Button id="_submit" runat="server" Text="Submit"></asp:Button>

<!-- Estos elementos no seran vistos en la interfaz del usuario -->
<div style="display: none">
<asp:TextBox id="_parent_selected" runat="server"></asp:TextBox>
<asp:TextBox id="_child_selected" runat="server"></asp:TextBox>
</div>
</form>
</body>
</HTML>

--- End File ---


Lo curioso en este archivo es el uso de dos objetos javascript, importantes para nuestro objetivo, pues ambos nos permiten encapsular cierta funcionalidad y no repetir codigo innecesariamente.

El primero de ellos es UrlTarget, este objeto permite definir cual sera nuestra destino para realizar una peticion y almacena la informacion a enviar a dicho destino, en el formato POST que todos conocemos.

El segundo es WebService, este objeto estable la forma de consumir un XML Web Service que existen en algun lugar de la red. La forma de consumir dicho servicio web es a traves del protocolo POST. Sin embargo, esto no excluye que no podamos hacer peticiones GET o SOAP. Para lo primero, GET, es mucho mas secillo, pues basta con pasar la informacion a traves del url pero, con las limitaciones conocidas de hacerlo por esa forma. Para lo segundo, SOAP, solo habra que construir el documento XML que se ajusta a la forma de solicitar informacion al servicio web y enviarlo como parte de la peticion.

Cuando testean sus XML Web Services a traves del VS.NET, veran los detalles en que esta permitido la forma en que podran consumirlo. Revisan dichas formas y listo.


Archivo .ASPX.CS

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DemoWebAppCSharp.Services
{
/// <summary>
/// Descripción breve de Ubigeo.
/// </summary>
public class ParentChild : System.Web.UI.Page
{
protected System.Web.UI.WebControls.DropDownList _parent;
protected System.Web.UI.WebControls.Button _submit;
protected System.Web.UI.WebControls.TextBox _parent_selected;
protected System.Web.UI.WebControls.TextBox _child_selected;
protected System.Web.UI.WebControls.DropDownList _child;

private void Page_Load(object sender, System.EventArgs e)
{
if( ! this.IsPostBack )
{
// Enganchando metodos a los eventos DHTML correpondientes
this._parent.Attributes.Add( "onchange", "Show(this);" );
this._submit.Attributes.Add( "onclick", "Capture();" );
}
}

#region Código generado por el Diseñador de Web Forms
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: llamada requerida por el Diseñador de Web Forms ASP.NET.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Método necesario para admitir el Diseñador. No se puede modificar
/// el contenido del método con el editor de código.
/// </summary>
private void InitializeComponent()
{
this._submit.Click += new System.EventHandler(this._submit_Click);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void _submit_Click(object sender, System.EventArgs e)
{
// Muestra la informacion que ha sido posteada
this.Response.Write( "Parent: " + this._parent_selected.Text + "<br/>Child: " + this._child_selected.Text );
}
}
}

--- End File ---


No hay mucho que explicar de este archivo. Solo, debido a que la informacion para cada DropDownList se ingresa por el lado del cliente a traves de las peticiones a los XML Web Services, en los siguientes postback no habra informacion trascendente en dichos controles. Por lo que se nos apoyamos en los TextBox para salvar la informacion del item seleccionado de los DropDownList por el usuario de manera que podamos enviarsela hacia el servidor por este medio.

La intencion del ejemplo, consite en presentar al usuario dos DropDownList dependiente uno del otro, para efectos practicos les llamaremos _parent y _child. Mas claro el agua.

Los itemes de _parent se solicitan al XML Web Services, al lanzarse el evento onload del documento HTML que presentamos al usuario. Al cual esta enganchado el metodo Load(), en el script por el lado del cliente.

Cada vez que el usuario seleccione un item de _parent, se ejecuta el metodo Show() por el lado del cliente aun, el cual solicitara los itemes de _child a otro WebMethod del XML Web Services, que estamos usando para este ejemplo.


Archivo .ASMX.CS

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;

using System.Xml.Serialization;

namespace DemoWebAppCSharp.Services
{
/// <summary>
/// Descripción breve de General.
/// </summary>
[WebService(Namespace="http://guydotnetxmlwebservices.com")]
public class General : System.Web.Services.WebService
{
public General()
{
//CODEGEN: llamada necesaria para el Diseñador de servicios Web ASP .NET
InitializeComponent();
}

#region Código generado por el Diseñador de componentes

//Requerido por el Diseñador de servicios Web
private IContainer components = null;

/// <summary>
/// Método necesario para admitir el Diseñador. No se puede modificar
/// el contenido del método con el editor de código.
/// </summary>
private void InitializeComponent()
{
}

/// <summary>
/// Limpiar los recursos que se estén utilizando.
/// </summary>
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}

#endregion

[WebMethod]
public ParentContainer ParentRecordSet()
{
ParentContainer container = new ParentContainer();
container.Fill();
return container;
}

[WebMethod]
public ChildContainer ChildRecordSet( Int32 Parent )
{
ChildContainer container = new ChildContainer();
container.Fill();
return container.FilterBy( Parent );
}
}

/// <summary>
/// Business Entity para salvar informacion del tipo Parent
/// </summary>
public class ParentItem
{
private Int32 _id;

public Int32 Id
{
get { return this._id; }
set { this._id = value; }
}

private String _name;

public String Name
{
get { return this._name; }
set { this._name = value; }
}

public ParentItem()
{}

public ParentItem( Int32 Id, String Name )
{
this._id = Id;
this._name = Name;
}
}

/// <summary>
/// Una colleccion del tipo ParentItem fuertemente tipeado
/// </summary>
public class ParentCollection : CollectionBase
{
public void Add( ParentItem Item )
{
this.List.Add(Item);
}

public ParentItem this[ Int32 index ]
{
get { return (ParentItem)List[ index ]; }
set { List[ index ] = value; }
}
}

/// <summary>
/// Un container para la coleccion ParentCollection
/// </summary>
[XmlRoot("ParentRoot")]
public class ParentContainer
{
private ParentCollection _collection;

[XmlArray("ParentCollection")]
public ParentCollection Collection
{
get { return this._collection; }
set { this._collection = value; }
}

public ParentContainer()
{
this._collection = new ParentCollection();
}

/// <summary>
/// Llena informacion inicial en la coleccion
/// </summary>
public void Fill()
{
this._collection.Add( new ParentItem( 1, "simplegeek" ) );
this._collection.Add( new ParentItem( 2, "spoutlet" ) );
this._collection.Add( new ParentItem( 3, "fabrik" ) );
}
}

/// <summary>
/// Business Entity para salvar informacion del tipo Child
/// </summary>
public class ChildItem
{
private Int32 _id;

public Int32 Id
{
get { return this._id; }
set { this._id = value; }
}

private Int32 _parent;

public Int32 Parent
{
get { return this._parent; }
set { this._parent = value; }
}

private String _name;

public String Name
{
get { return this._name; }
set { this._name = value; }
}

public ChildItem()
{
}

public ChildItem( Int32 Id, Int32 Parent, String Name )
{
this._id = Id;
this._parent = Parent;
this._name = Name;
}
}

/// <summary>
/// Una colleccion del tipo ChildItem fuertemente tipeado
/// </summary>
public class ChildCollection : CollectionBase
{
public void Add( ChildItem Item )
{
this.List.Add(Item);
}

public ChildItem this[ Int32 index ]
{
get { return (ChildItem)List[ index ]; }
set { List[ index ] = value; }
}
}

/// <summary>
/// Un container para la coleccion ChildCollection
/// </summary>
[XmlRoot("ChildRoot")]
public class ChildContainer
{
private ChildCollection _collection;

[XmlArray("ChildCollection")]
public ChildCollection Collection
{
get { return this._collection; }
set { this._collection = value; }
}

public ChildContainer()
{
this._collection = new ChildCollection();
}

/// <summary>
/// Llena informacion inicial a la coleccion
/// </summary>
public void Fill()
{
this._collection.Add( new ChildItem( 1, 1, "longhorn" ) );
this._collection.Add( new ChildItem( 2, 1, "avalon" ) );
this._collection.Add( new ChildItem( 3, 2, "soa" ) );
this._collection.Add( new ChildItem( 4, 2, "ws" ) );
this._collection.Add( new ChildItem( 5, 2, "indigo" ) );
this._collection.Add( new ChildItem( 6, 3, "nodes" ) );
this._collection.Add( new ChildItem( 7, 3, "network" ) );
this._collection.Add( new ChildItem( 8, 3, "agents" ) );
}

/// <summary>
/// Filtra la collection inicial a traves de un criterio
/// </summary>
/// <param name="Parent">El codigo del padre</param>
/// <returns></returns>
public ChildContainer FilterBy( Int32 Parent )
{
ChildContainer child = new ChildContainer();

foreach( ChildItem Item in this._collection )
{
if( Item.Parent == Parent )
child._collection.Add( Item );
}

return child;
}
}
}

--- End File ---


Para este archivo tenemos que explicar algunas cosas.

Primero, podran percatarse el uso del System.Xml.Serialization, este namespace permite -obviamente- serializar la informacion que tengamos de las clases que implementemos. De por si, cuando intentamos devolver informacion a traves de un XML Web Services, cometemos el un error involutario al hacerlo como un DataSet, esta estructura es de uso generico y para efectos de implemtacion de nuestra aplicaciones en general dentro del mundo .NET, su uso solo deberia estar sesgado -en lo posible- a la capa de acceso a datos.

En las demas capas, necesitamos de estructuras de datos, fuertemente tipeadas -AKA Strongly Typed- para brindar una mayor escalabilidad en el producto que entregamos.

En nuestro ejemplo, usamos el CollectionBase, para dar soporte a las coleciones que necesitmos implementar. Su uso es recomendado para encapsular conjuntos de datos para cada uno de los Business Entities que requerimos. Su comportamiento es relativamente similar en cada diferente coleccion, es decir: Add, Remove, this, etc.

Cuando se libere la proxima version de VS.NET (Codename: Whidbey), esta soportara la version 2.0 de C#, la que traera entre algunas cosas interesantes, el uso de templates <T>, dicha estructura nos permitira reducir muchas lineas de codigo, y seran usadas muy puntualmente para este tipo de requerimiento, las colecciones.

Volviendo a nuestro ejemplo en el codigo, se observa tambien, el uso de ciertos atributos como: XmlArray, XmlRoot, etc.

Estos atributos, se encuentran dentro del System.Xml.Serialization, y permiten customizar la forma en que se ha de serializar la estructura que vayamos a devolver a traves del XML Web Services.

Si intentasen, devolver un ArrayList a traves de un WebMethod, veran que el documento XML entregado a quien consuma dicho servicio web, tendra un vocabulario, similar a este.


<ArrayOfAnyType>
<anyType />
<anyType />
...
</ArrayListOfAnyType>


Obviamente, dicho vocabulario, no nos da mucha informacion -que digamos- debido a que se encuentra en terminos genericos.

El uso de Business Entities, Collections (a traves de CollectionBase) y Containers, permitiran entregar un documento XML mucho mas explicito en la informacion que contienen.


Archivo PARENT.XSLT

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ws="http://guydotnetxmlwebservices.com">

<xsl:template match="/">
<select>
<xsl:apply-templates select="/ws:ParentRoot/ws:ParentCollection/ws:ParentItem" />
</select>
</xsl:template>

<xsl:template match="ws:ParentItem">
<option>
<xsl:attribute name="value">
<xsl:value-of select="ws:Id" />
</xsl:attribute>

<xsl:value-of select="ws:Name" />
</option>
</xsl:template>
</xsl:stylesheet>

--- End File ---



Archivo Child.XSLT

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ws="http://guydotnetxmlwebservices.com">

<xsl:template match="/">
<select>
<xsl:apply-templates select="/ws:ChildRoot/ws:ChildCollection/ws:ChildItem" />
</select>
</xsl:template>

<xsl:template match="ws:ChildItem">
<option>
<xsl:attribute name="value">
<xsl:value-of select="ws:Id" />
</xsl:attribute>

<xsl:value-of select="ws:Name" />
</option>
</xsl:template>
</xsl:stylesheet>

--- End File ---


Estos dos ultimos archivos que aqui les alcanzo, permite tranformar la el documento XML que se obtiene a traves del XML Web Services, para consefuir un vocabulario que nos permita una mayor facilidad para construir los OPTIONS de cada DropDownList, por el lado del cliente.

Es importante recalcar, el uso de este namespaces dentro de los documentos XSLT:

xmlns:ws="http://guydotnetxmlwebservices.com"

Esta definicion, simplemente es necesaria, si la obviasemos, no podremos conseguir los resultados deseados al realizar las transformaciones.

Hay que entender que el documento XML viene firmado por un namespace, desde su origen, en la definicion misma del XML Web Services. Dicho namespace debe ser tomado encuenta, cada vez que usemos dicho documento, dentro de nuestra aplicacion.

Call a .NET Webservice using XMLHTTP, XMLDOM and VBScript
http://www.eggheadcafe.com/articles/20001006.asp

Web Methods Make it Easy to Publish Your App's Interface over the Internet
http://msdn.microsoft.com/msdnmag/issues/02/03/WebMethods/default.aspx

Tuesday, July 06, 2004

Soccer Refactory and Breakpoint

Y Grecia se alzo con el triunfo de la Eurocopa 2004. Un equipo que termino jugando fiel al estilo que propuso Otto Rehhagel, el dictador-democratico del futbol.

http://fifaworldcup.yahoo.com/06/es/040330/1/9tf.html

Tal vez ahora al final del torneo, uno haga memoria y revisando los encuentros que sostuvo el equipo Griego, se hara esta pregunta:

¿Sera que Franceses, Chekos, Portugueses y los demas jugaron tan apaticos sus respectivos encuentros, por que hubo un equipo al frente que los llevaba hacia ese ritmo? ¿Sera esa una consecuencia del plateamiento estrategico del nuevo rey griego?

Otto se llamó el primer monarca. Otto I de Baviera, entonces de 16 años, fue nombrado Rey de Grecia el 7 de mayo de 1832. Ahora, un nuevo Otto es elevado por los griegos al Olimpo: Otto Rehagel, el entrenador alemán del seleccionado griego, reciente campeon del torneo de naciones mas importante de Europa.

Pero regresemos a lo nuestro, a lo oriundo, a lo autoctono.

Hoy se inicia, en nuestra tierra, nuestro Peru, una nueva version de la Copa America.

No llegaron los Ronaldo, Ronaldinho, Roberto Carlos, y demas. Pero Brasil es Brasil. Y con KaKa seguramente aspiren a mas que jugar solo las finales de este torneo.

Sin embargo, por lo experimientado en los ultimos tiempos, en los que algunos equipos que no contaban con una historia futbolistica importante, empiezen a construirla con triunfos resonantes. Asi entonces, dificil dar un pronostico.

Quien podria negar que Venezuela, la que aun no ha conseguido algo trascendente -futbolisticamente hablando- por esta parte del mundo. Ahora lo logre.

Sino, preguntemosle a Grecia (EuroCopa 2004), Porto (Copa de Campeones 2004), Once Caldas (Copa Libertadores 2004) y Cienciano (Copa Sudamericana 2003)

http://www.go2peru.com/copa_america/copaamerica.htm

De todos modos, nos daremos un tiempo -alguno de estos dias- para dejar la garganta en la tribuna, alentando a los muchachos. Que por cierto, ni se lo merecen. Pues debieron ganarle a Venezuela, por las eliminatorias. Pero, que le vamos hacer.

Estamos convencidos, de que el Futbol -es solo Futbol- cuando se juega con el corazon.

Thursday, July 01, 2004

Dynamic WebForms in ASP.NET

Muchas veces, segun los requerimientos del cliente, se da la necesidad de poder construir -en ciertos casos- formularios webs y grillas de datos de manera dinamica, a partir de cierta logica para definir la estrucura que se ha de presentar a traves de la interfaz del usuario.

Les muestro un ejemplo que puede darles las primeras pautas de como poder llevar a cabo estas tareas.

Archivo .ASPX

<form id="SimpleDynamic" method="post" runat="server">
<asp:PlaceHolder id="_holder" runat="server"></asp:PlaceHolder><br/>
<asp:Button id="_submit" runat="server" Text="Submit"></asp:Button><br/>
<asp:Label id="_post" runat="server"></asp:Label><br/>
<asp:Label id="_message" runat="server"></asp:Label><br/>
</form>

--- End File ---


Notese el uso del control PlaceHolder, que permitira alojar en el diferentes tipos de controles en tiempo de ejecucion, para su posterior presentacion e interaccion.


Archivo .CS

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DemoWebAppCSharp.Custom
{
/// <summary>
/// Descripción breve de Dynamic.
/// </summary>
public class SimpleDynamic : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Button _submit;
protected System.Web.UI.WebControls.Label _message;
protected System.Web.UI.WebControls.Label _post;
protected System.Web.UI.WebControls.PlaceHolder _holder;

private void Page_Load(object sender, System.EventArgs e)
{
if( ! this.IsPostBack )
{
// Llenando el DropDownList de datos
this.FillList();
}
}

private void DefineForm()
{
TextBox Name = new TextBox();
Name.ID = "_name";

DropDownList List = new DropDownList();
List.ID = "_list";
List.AutoPostBack = true;

// Enganchando una funcionalidad al evento del DropDownList
List.SelectedIndexChanged += new EventHandler( this.List_SelectedIndexChanged );

// Colocando los controles en el PlaceHolder
this._holder.Controls.Add( Name );
this._holder.Controls.Add( List );
}

private void FillList()
{
// Solicitando el DropDownList al PlaceHolder
DropDownList List = (DropDownList)this._holder.FindControl( "_list" );

List.Items.Add( "-- Elija una opción --" );
List.Items.Add( "Simple Geek");
List.Items.Add( "Spoutlet" );
List.Items.Add( "Frabriq" );
}

// Funcion que se enganchara al evento correspondiente del DropDownList
private void List_SelectedIndexChanged( Object sender, EventArgs e )
{
DropDownList List = (DropDownList)sender;
this._post.Text = "Item selected: " + List.SelectedValue;
}

#region Código generado por el Diseñador de Web Forms
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Método necesario para admitir el Diseñador. No se puede modificar
/// el contenido del método con el editor de código.
/// </summary>
private void InitializeComponent()
{
this._submit.Click += new System.EventHandler(this.Submit_Click);
this.Load += new System.EventHandler(this.Page_Load);

this.DefineForm();
}
#endregion

// Posteando la informacion
private void Submit_Click(object sender, System.EventArgs e)
{
TextBox Name = (TextBox)this._holder.FindControl( "_name" );
DropDownList List = (DropDownList)this._holder.FindControl( "_list" );

this._message.Text = Name.Text + " : " + List.SelectedValue;
}
}
}

--- End File ---

Explicando, se contruira un TextBox y un DropDownList que esta seteado como AutoPostBack. De esta manera, cada vez que el usuario seleccione un item en la lista la pagina se posteara inmediatamente.

Revisando el metodo DefineForm. En este punto se muestra algo interesante, que es el enganche de un metodo hacia el evento de un control que se crea on-fly, es decir en tiempo de ejecucion.

Este metodo -el DefineForm- es llamado desde el InitializeComponent, que permite ejecutar operaciones sobre el metodo sobre-escrito OnInit. Que por cierto, es el lugar adecuado para poder colocar elementos dinamicos en la pagina.

Vale decir, que por el hecho de ser llamado desde dicho evento -el Init- la construccion dinamica de los elementos se realiza, por cada peticion de la pagina. Pues este evento siempre es solicitado por cada PostBack, al igual que el evento Load.

Es trabajo del modelo de objetos de ASP.NET, mantener el estado de los controles en la pagina por cada posterior PostBack.

Se remarca tambien, que los itemes adheridos al DropDownList, solo se realiza una sola vez, y esta operacion se lleva a cabo dentro del evento Load, validando el IsPostBack.

Se quiere dar a entender de esta manera, que la estructura del formulario es independiente de los datos que se plasmaran en dicha estructura. El evento Init es el lugar correcto para construir estructuras dinamicas y el Load para llenar datos en esas estructuras.

El metodo List_SelectedIndexChanged, solo hace un casting del objeto sender. Pues es justamente en esta variable que se encuentra una referencia al control que provoco el posting de la pagina, es decir el DropDownList.

Finalmente, el metodo Submit_Click, solicita al PlaceHolder, los controles que se adherieron al el, para presentar los valores que el usuario haya ingresado o seleccionado.

En conclusion, la intencion es poder construir cualquier estructura en el webform, a partir de una metadata, que brinde toda la informacion necesaria para la presentacion de dicha estructura y los datos que han de colocarse en el.

Les recomiendo descargar una interesante herramienta que he constuido, y que justamente parte de lo aqui mostrado es el 'core' de dicho producto.

http://groups.msn.com/mugperu/general.msnw?action=get_message&mview=1&ID_Message=11466

En dicho lugar, podran encontrar mayor informacion de como instalar el software y como trabajar con el.

Comparto tambien estos lugares de donde podran encontran mayor informacion sobre los eventos asociados a una pagina, el orden en el que son llamados y las actividades que se realizan en cada uno de ellos.

The ASP.NET Page Object Model
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/aspnet-pageobjectmodel.asp

The ASP.NET Page Life Cycle
http://www.15seconds.com/issue/020102.htm

Al igual que esto, la construccion de un DataGrid dinamico en funcion a las columnas y al contenido en cada DataGridItem, sean estos datos u otros controles, tambien es posible realizarlo, de esta forma.

Javier Luna
-- Software Architect and blogger too ;)
-- Movil: (51-1) 9-731-7187

"Indigo: Ports, Messages, Channels and more..."