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"></asp:dropdownlist>
List<string> Lst = new List</string><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><string> Lst = new List</string><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><string> Lst = new List</string><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><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}"></asp:dropdownlist>
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><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><mielem> Lst = new List</mielem><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.
Menos código
Me parece, luego de lo visto que este es el mejor momento para comentar la existencia de dos controles muy interesantes en ASP.NET, los controles SqlDataSource y ObjectDataSource. Ambos controles nos permitirán definir un origen de datos sin tener la necesidad de escribir ni una sola línea de código.
SqlDataSource
Comenzaremos por ver el control SqlDataSource y como éste encaja en el esquema que hemos visto hasta ahora.
SqlDataSource nos permitirá representar un origen de datos contra un proveedor ADO.NET los proveedores que vienen en ASP.NET listos para usar son SqlClient, OracleClient, OleDb y Odbc.
Si observamos las propiedades de SqlDataSource veremos que una de ellas es ConnectionString. En la misma deberemos establecer la cadena de conexión al servidor SQL.
Hay un detalle que no debemos pasar por alto, si hacemos memoria recordaremos que cada proveedor se encuentra implementado en clases específicas, por ejemplo, para el proveedor contra SQL Server tendremos un SQLCommand, un SQLConnection, etc dentro de namespace SqlClient, mientras para por ejemplo para el caso de ODBC tendremos un OdbcCommand y un OdbcConnection dentro del namespace Odbc.
Por este motivo al control SqlDataSource, que puede funcionar con cualquiera de ellos, deberá establecérsele cual de los mismos deberá utilizar, para tal fin el mismo contará con la propiedad ProviderName las opciones que vienen en ASP.NET son (System.Data.SqlClient, System.Data.OracleClient, System.Data.OleDb y System.Data.Odbc).
Como comentamos previamente, SqlDataSource nos permitirá establecer un origen de datos (contra un proveedor ADO.NET) en el diseñador sin escribir código, entonces no deberá sorprendernos que exista alguna manera de indicarle al control cuales serán los elementos de ese origen de datos que estamos creando, tampoco debería sorprendernos el hecho que dicha definición sea una sentencia SQL, la propiedad SelectCommand permitirá establecer el comando SQL de selección de los elementos que compondrán el origen de datos, a continuación se muestra un ejemplo concreto.
<asp:sqldatasource runat="server" ID="SqlDS2" ConnectionString="Data Source=(local);Initial Catalog… " SelectCommand="SELECT TOP 11 * FROM Datos1"></asp:sqldatasource>
En este ejemplo podemos ver la definición de la cadena de conexión y el comando de selección de datos, la propiedad ProviderName no se ha establecido ya que su valor por defecto es System.Data.SqlClient y en nuestro caso estamos utilizando un servidor de base de datos SQL Server 2008 Enterprise.
Cabe mencionar que el SQLDataSource cuenta también con una propiedad llamada SelectCommandType la cual puede establecerse como StoredProcedure para utilizar procedimientos almacenados y no la sentencia SQL como se muestra en el ejemplo, donde al no haberse establecido ningún valor para dicha propiedad, la misma ha tomado el valor por defecto que es Text.
Hasta este punto hemos podido crear un origen de datos sin escribir nada de código, la próxima pregunta imagino que será ¿de qué manera podrá enlazarse este origen de datos contra algunos de los controles de lista?, en este punto entrará en juego la propiedad DataSourceID de los controles de lista, donde deberá simplemente establecerse el nombre del control que suministrará el origen de datos y el enlace quedará listo, a continuación se muestra un ejemplo completo:
<asp:dropdownlist runat="server" ID="DDL2" DataSourceID="SqlDS2" DataTextField="Descripcion"> </asp:dropdownlist> <asp:sqldatasource runat="server" ID="SqlDS2" ConnectionString="Data Source=(local);Initial Catalog… " SelectCommand="SELECT TOP 11 * FROM Datos1"></asp:sqldatasource>
De esta forma nos hemos evitado todo el código que habíamos tenido que escribir en los ejemplos previos.
No hay dudas que si vemos esta definición, podrían abordarnos una cuantas inquietudes, sobre todo si le préstamos atención a la propiedad SelectCommand y si a la vez pensamos en la vida real donde las consultas pueden estar restringidas por diversas condiciones las cuales pueden a la vez encontrarse definidas de diversas formas. Por ejemplo podríamos desear obtener información en base a un valor existente en una cookie, en un valor pasado en el QueryString, etc. Este detalle no ha sido omitido y será posible leer parámetros desde:
• Query String
• Un valor de sesión
• Una cookie
• Un valor de perfil (Profile)
• Un valor del formulario (Form)
• Otro control
• Utilizar un parámetro fijo
Para tal fin dispondremos de los elementos:
• QueryStringParameter
• SessionParameter
• CookieParameter
• ProfileParameter
• ControlParameter
• Parameter
En el siguiente ejemplo, se utiliza un elemento ControlParameter para darle valor al parámetro @ID de la consulta perteneciente al SqlDataSource denominado SqlDS2 en base al valor de la propiedad text del control CtlParam (Comentado parece intrincado, pero en el ejemplo puede verse lo claro y simple que es realizar esta tarea).
<asp:dropdownlist runat="server" ID="DDL2" DataSourceID="SqlDS2" DataTextField="Descripcion"> </asp:dropdownlist> <asp:textbox runat="server" ID="CtlParam"> </asp:textbox> <asp:sqldatasource runat="server" ID="SqlDS2" ConnectionString="Data Source=(local);Initial Catalog… " SelectCommand="SELECT TOP 11 * FROM Datos1 WHERE ID>@ID"> <selectparameters> <asp:controlparameter Name="ID" Type="Int32" ControlID="CtlParam" PropertyName="text" DefaultValue="110"></asp:controlparameter> </selectparameters> </asp:sqldatasource>
Cada elemento “parámetro” contará con las propiedades necesarias para poder definir la captura de parámetros del origen deseado. Para quien no se sienta cómodo escribiendo tags, le será reconfortante saber que será posible establecer todas estas definiciones en modo Diseño. Seleccionando la opción «Configure Data Source …» en el control SqlDataSource deseado se desplegará un wizard.
Un detalle a mencionar es que por defecto el control SqlDataSource utilizará un DataSet para efectuar el enlace de datos, aunque será posible modificar dicho comportamiento estableciendo la propiedad DataSourceMode como “DataReader”.
ObjectDataSource
En la actualidad no puede negarse la tendencia a la utilización de componentes de acceso a datos, el control ObjectDataSource permitirá enlazar un control ASP.NET a una clase de acceso a datos, de forma similar a la que el control SqlDataSource permitía enlazar un control a un origen de datos SQL.
En este caso deberemos contar con una clase que efectuará las tareas de acceso a datos la cual será utilizada por el control ObjectDataSource y otra clase de objeto de datos que representará a la entidad a modelar. Un ejemplo permitirá entender estos conceptos en una forma más simple.
Para comenzar supondremos que estaremos modelando una entidad compuesta por un identificador y una descripción, en base a esto deberemos definir dos clases una clase de acceso a datos y una clase de objeto de datos, la clase de objeto de datos representará a la entidad que la clase de acceso a datos manejará. El primer paso será crear la clase de objeto de datos.
public class ObjectDataSourceDO { private int _ID; private string _Descripcion; public int ID { get { return _ID; } set { _ID = value; } } public string Descripcion { get { return _Descripcion; } set { _Descripcion = value; } } }
Es imprescindible que cada propiedad de la entidad sea modelada como una propiedad pública de la clase.
El próximo paso constará en definir la clase de acceso a datos.
public class ObjectDataSourceDB { public ObjectDataSourceDO[] GetWhereIdGreaterThan(int ID) { … } }
En este ejemplo solo se ha definido un metodo que devolverá un arreglo de objetos de datos ObjectDataSourceDO para aquellas entidades con un valor de ID mayor al pasado por parámetros, en este caso el código del metodo no se ha incluido, ya que no tiene repercusion en el ejemplo.
La clase de acceso a datos deberá cumplir con ciertas condiciones para que pueda ser utilizada sin problemas por un ObjectDataSource, deberá:
• Utilizar un solo método para cada operación (alta, baja, listado, etc)
• Utilizar un elemento que implemente la interfaz IEnumerable para devolver varios datos, cada dato a devolver además deberá ser un objeto de datos.
• La clase de acceso a datos no deberá mantener estados.
Estas restricciones se deben a que el elemento ObjectDataSource utilizará reflection para crear en tiempo de ejecución los objetos necesarios para acceder (a traves de al clase de acceso a datos) a los datos y obtener los resultados (en la clase de objetos de datos). Cada instancia será creada cuando sea necesaria y destruida luego de haberse utilizado.
En el siguiente ejemplo se utiliza el método GetWhereIdGreaterThan de la clase ObjectDataSourceDB para enlazar los resultados devueltos al control DDL1 y se utiliza un parámetro que tomará el valor de la sesión y se lo pasará al método GetWhereIdGreaterThan. Las opciones de parámetros mencionados para el SqlDataSource, como es de esperarse, se encontrarán también disponibles para el ObjectDataSource, al igual que la posibilidad de definirlo en modo diseño.
<asp:dropdownlist ID="DDL1" runat="server" DataSourceID="ObjDS1" DataTextField="ID"> </asp:dropdownlist> <asp:objectdatasource runat="server" ID="ObjDS1" TypeName=" ObjectDataSourceDB" SelectMethod="GetWhereIdGreaterThan"> <selectparameters> <asp:sessionparameter DefaultValue="100" DbType="Int32" Name="ID"></asp:sessionparameter> </selectparameters> </asp:objectdatasource>
En el ejemplo puede observarse que el parámetro ID solicitado por el metodo GetWhereIdGreaterThan es proporcionado en el ObjectDataSource ObjDS1, la propiedad Name del parametro SessionParameter deberá coincidir con el nombre del parametro esperado por el método GetWhereIdGreaterThan (sin importar mayusculas y minusculas) para que ASP.NET pueda realizar la invocación.
Si ejecutamos este codigo veremos que el DropDownList DDL1 es llenado por los valores proporcionados por el método GetWhereIdGreaterThan.
Con este último ejemplo doy por concluido este artículo donde si bien hemos visto las opciones que el enlace ofrece en ASP.NET, no hemos profundizado demasiado en cada una de ellas, en mi proximo articulo mi intención es retomar el tema desde este punto para adentrarnos en un control de enlace más intesante llamado GridView que nos permitirá explorar las opciones que ASP.NET nos ofrece para además de mostrar informacion en la interfaz de usuario, permitir la modificacion y transferencia de datos modificados hacia el origen de datos.
Espero que este articulo haya sido de utilidad para quienes nunca hayan utilizado enlace en ASP.NET y para quienes si ya lo hayan hecho, espero se hayan encontrado con algún detalle desconocido.