Un Momento de Reflexión (Reflection) en la Vida Diaria
Esta vez un problema que se me presento me obligó a reflexionar, reflexionar y reflexionar…, el caso es el siguiente:
Tengo una clase que representa a una entidad, mas específicamente hablando la clase es la que se genera utilizando la plantilla NetTiers para CodeSmith, que se corresponde uno a uno con las tablas de la base de datos y que expone mediante sus propiedades las columnas de la tabla correspondiente, lo que necesito es cargar automáticamente las propiedades de la clase a sus correspondientes controles (TextBox) de un WinForm.
Necesito crear un método general que funcione con cualquier tipo de entidad, y como no utilizaré DataBinding, lo que se me ocurrió es utilizar la propiedad Tag que tienen todos los controles para poner ahí el nombre de la propiedad de la clase entidad que le corresponde.
El siguiente paso es recorrer las propiedades del objeto entidad e ir copiando los valores en el componente que le corresponde, pero ¿cómo recorrer las propiedades de una clase?; esto fue lo que me llevo a la Reflexión.
Reflexión.- “La reflexión es uno de los pilares de .NET. Esta característica permite almacenar y obtener información en tiempo de ejecución sobre casi cualquier objeto o tipo presente en un módulo. Es gracias a esto que es posible implementar técnicas fundamentales como la recolección de basura o la serialización en distintos formatos. Y aunque es cierto que la mayoría de los entornos de programación modernos proporcionan algún tipo de RTTI (runtime type information, el pariente pobre de la reflexión), nunca antes se había visto un uso tan extenso y generalizado de este recurso como en .NET.”
La definición anterior la tomé prestada de la página de uno de mis maestros Ian Marteens, les recomiendo que consulten un artículo muy ilustrativo con ejemplo incluido sobre reflexión en el siguiente link: Calling Dr. Marteens: Reflexión.
Utilizando Reflexión podemos recorrer con facilidad las propiedades de una clase:
public void CargarDatosAControles(Object entidad, Type tipo,
Control contenedor)
{
foreach (PropertyInfo info in tipo.GetProperties())
{
Attribute atributo = Attribute.GetCustomAttribute(info,
typeof(DataObjectFieldAttribute));
if (atributo != null)
{
foreach (Control control in contenedor.Controls)
{
if (control.Tag != null &&
control.Tag.ToString() == info.Name)
{
control.Text = info.GetValue(entidad,
null).ToString();
}//if
}//foreach
}//if
}//foreach
}
El método que se presenta en el ejemplo, utiliza tres parámetros, el primero es una referencia a un objeto entidad, el segundo es el tipo de la clase entidad y el tercer parámetro es la referencia a un control que contiene a los controles (TextBox) donde queremos cargar los datos de la clase entidad, por ejemplo un Panel.
Para comenzar podemos obtener información de las propiedades de un tipo utilizando el método GetProperties() que nos devuelve una colección de objetos PropertyInfo que contiene la información de todas las propiedades de un tipo en este caso una clase entidad.
Un problema que se me presento es que cuando recorres las propiedades de un tipo, el método GetProperties() devuelve todos las propiedades, y resulta que la clase entidad con la que estoy trabajando aparte de las propiedades que se corresponden con las columnas de la tabla de base de datos que representa, tiene mas propiedades.
Entones tengo que discriminar cuales son las propiedades que corresponden a una columna de una tabla de base de datos y cuales no. Para hacer eso tengo que utilizar la información que proporcionan los atributos que se asocian a la declaración de las propiedades en el código fuente. Revisando el código de la clase entidad encuentro que las propiedades que me interesan tienen asociado a ellas un atributo llamado DataObjectFieldAttribute que indica que la propiedad es un campo de datos, la definición de la entidad luce así:
[DataObjectField(false, false, false, 300)]
public virtual System.String RazonSocial
…
Lo cual indica que la propiedad RazonSocial es un campo de datos, por los 4 parámetros del constructor del atributo podemos deducir que: no es clave primaria, no es un campo de identidad es decir autogenerado, no acepta valores nulos, y tiene una longitud de 300.
Ahora regresemos a nuestro método CargarDatosAControles, en el cual tenemos la siguiente instrucción:
Attribute atributo = Attribute.GetCustomAttribute(info,
typeof(DataObjectFieldAttribute));
El código anterior obtiene un atributo de tipo DataObjectFieldAttribute a partir del objeto PropertyInfo de una propiedad.
Luego verificamos que el atributo obtenido sea diferente de null lo cual indica que la propiedad posee ese atributo y por tanto es un campo de datos, solo así procedemos a recorrer los controles del contendedor y buscar a que componente (TextBox) le corresponde la propiedad que estamos tratando, obtenemos el valor de la propiedad a través del método GetValue(entidad, null) de la clase PropertyInfo, para lo cual necesitamos como primer parámetro una instancia de la clase entidad y como segundo parámetro le pasamos null porque no estamos tratando con propiedades indexadas.
La manera de llamar al método seria la siguiente:
CargarDatosAControles(cliente, typeof(Cliente), panelControl1);
De manera similar se pude implementar el otro método que hace la operación inversa CargarDatosAClases.
Cómo Buscar Controles de ASP .Net con JavaScript Dentro de ContentPages
Ayer como a medio día, uno de mis viejos amigos, (uhmm esa ultima frase no me gusto mucho, me recuerda que estoy cada vez más viejo) me visitó y me comentó que tenia un problema cuando trabajaba con Master Pages y que no lograba que funcione una llamada a un calendario utilizando JavaScript.
Como yo no soy de escapar a estos desafíos, me dispuse a revisar el código y le dije a mi amigo, esto lo soluciono en un par de minutos, luego de media hora de hacer todas las pruebas posibles, tuve que decirle (no tengo la más mínima idea de cómo solucionar esto) que esto necesita ser revisado con paciencia y que lo solucionaría cuando tenga más tiempo.
El problema
Ahora que ya se fue mi amigo, comienzo a depurar con más calma el programa y localizo la fuente del problema, cuando se ejecuta el código desde un content page, la función de JavaScript:
document.getElementById(‘txtFecha’).Value
Devuelve null, es decir no encuentra el control txtFecha que como supondrán es un control TextBox, lo extraño es que cuando se ejecuta el mismo código desde una página Aspx normal que no utiliza master pages, funciona a la perfección.
Al parecer cuando se realiza la combinación del master page con el content page ya no se utilizan los nombres (ID) que les colocamos a los controles de nuestro formulario, esto para evitar posibles conflictos de nombres entre los controles que están en el master y content page.
La solución
Después de buscar información en muchas páginas y de probar infructuosamente muchas posibles soluciones, llegue a la conclusión de que como la función no encontraba el nombre (ID), que le había asignado al control, tendría que buscarlo por otro nombre, y ese nombre es el que esta en la propiedad ClientID de los controles. El valor de ClientID es generado automáticamente concatenando el valor de la propiedad (ID) del control con el valor de la propiedad UniqueID del control padre.
Un pequeño ejemplo
Suponiendo que tenemos un Content Page que se ha creado a partir de un Master Page, y que colocamos dentro un TextBox de nombre: txtFecha y un Button, en la página de código tendremos el método que maneja el evento click del botón:
protected void Button1_Click(object sender, EventArgs e)
{
string codigo = “document.getElementById(‘”+
txtFecha.ClientID + “‘).value = ‘Hola’”;
ClientScript.RegisterStartupScript(GetType(), “prueba”, codigo);
}
Noten que como parámetro de la función document.getElementById utilizamos la propiedad ClientID del TextBox. El fragmento de código anterior solo coloca en la propiedad Text del control txtFecha el valor de “Hola”.
De esta manera solucionamos el problema de los nombres de los controles cuando trabajamos con JavaScript y Master Pages.
Capa de Datos Automágicamente Generada con NetTiers y CodeSmith
Como es costumbre en nosotros los desarrolladores, siempre estamos en busca de nuevas formas de escribir menos código, ¿será para mostrar lo listos que somos?, o simplemente porque somos muy flojos.
Sea como sea, la primera librería que utilicé para agilizar la escritura de la capa de datos de mis aplicaciones fue el Data Access Application Blocks, que ahora forma parte de la Enterprise Library, es cierto que esta librería ayuda a la hora de crear la conexión con la base de datos y ejecutar los procedimientos almacenados, pero aún hay mucho código que escribir.
Entonces decidí buscar una nueva alternativa, algo más completo, mi búsqueda por la web me llevó a un nuevo concepto para mi, ORM (Object Relational Mapping), mapeo del modelo orientado a objetos en que programamos al modelo relacional de base de datos, este mapeo es necesario porque como sabemos existe una brecha entre estos dos modelos y hasta que no se desarrollen completamente las bases de datos orientadas a objetos, tendremos que estar saltando esta brecha cada vez que tengamos que persistir un objeto en una base de datos relacional (Sql Server, Oracle, MySql, etc.)
Para lidiar con el problema de estar escribiendo cada vez el código necesario para persistir nuestros objetos, surgen numerosas librerías que nos ayudan a automatizar esta tarea, a continuación enumero algunas:
eXpress Persistent Objects for .NET.- de Developer Express, es uno de los primeros ORM que revisé, es bastante simple de utilizar, a grandes rasgos lo que tenemos que hacer es crear nuestra clase, por ejemplo en C# con sus respectivos campos, luego para que esta clase se pueda persistir tenemos que hacer que herede de la clase XPObject, adicionalmente si queremos hacer asociaciones y cambiar los nombres con que se guardaran las tablas o campos en la base de datos tendremos que utilizar atributos, después de crear nuestras clases, si la base de datos aun no existe se creará automáticamente y podremos persistir nuestras clases en ella. Esta librería soporta la mayoría de las bases de datos conocidas, así que cambiar de gestor de base de datos no representa ningún problema, una desventaja es que esta librería es de pago.
NHibernate for .NET.- Es la versión para .Net del conocido Hibernate para Java, aunque no esta tan desarrollado como su hermano mayor de Java, es una buena alternativa; para persistir las clases no necesita que nuestra clase herede de una clase base como en el caso anterior, lo que necesitamos en este caso es un archivo en formato xml donde indicaremos el mapeo de los campos de la clase con la base de datos, soporta lenguaje de consultas orientado a objetos, además de la mayoría de bases de datos conocidas, y para cambiar de gestor de base de datos en nuestro proyecto solo basta cambiar una línea en un archivo de configuración, en la versión actual todavía no soporta procedimientos almacenados razón por la cual no lo utilizo en mis proyectos, en la siguiente versión ya traerá soporte para procedimientos almacenados. Esta librería es open source, así que pueden utilizarla sin costo alguno.
Cooperator Framework.- Es un framework relativamente nuevo, no he tenido oportunidad de probarlo pero promete bastante, esta hecho por un grupo de desarrolladores argentinos, así que no es mala idea echarle un vistazo.
.netTiers.- .netTiers en realidad es una plantilla para CodeSmith, y CodeSmith es un generador de código que funciona basado en plantillas, uno escribe una plantilla en un lenguaje que tiene sintaxis similar al ASP .Net y luego puede general código para cualquier lenguaje basado en el texto de la plantilla.
Entonces .netTiers es una enorme plantilla que funciona en CodeSmith, se conecta con la base de datos que le indiquemos y comienza a generar todos los procedimientos almacenados que necesitaremos es decir (Insert, Update, Delete y consultas) para cada una de las tablas de nuestra base de datos, luego genera todas las clases correspondientes a las tablas que tenemos en la base de datos, así como los métodos que se corresponden con los procedimientos almacenados, y lo coloca todo en diferentes proyectos que conformarán la capa de datos. Obviamente habrá alguna funcionalidad específica de nuestra aplicación que no se puede generar automáticamente , para lo cual tendremos que escribir el procedimiento almacenado nosotros mismos con una nomenclatura especial, para estos procedimientos también se generaran los respectivos métodos en las clases correspondientes.
De esta manera no tendremos que preocuparnos por hacer consultas Sql, únicamente tendremos que llamar a los métodos necesarios con los parámetros requeridos.
La ventaja de esto es que el código se genera una sola vez, por lo que se gana en velocidad de ejecución, puesto que todo es compilado antes de su distribución.
La desventaja es que por el momento solo funciona con Sql Server, además que la plantilla es de uso libre pero el generador CodeSmith es de pago.
Yo utilizo el .netTiers en mis proyectos porque lo considero una buena opción está en medio camino entre hacer todo a mano y utilizar un ORM que genera todo en tiempo de ejecución como el NHibernate.
Paginación Alfabética de GridView
Hace un rato mi amigo Christian Ruiz, me envió un ejemplo de cómo paginar un GridView alfabéticamente (ya era hora que haga algo él solo), revisé el ejemplo y me pareció de lo más interesante.
Lo primero que ha hecho es interceptar el evento RowDataBound que ocurre cada vez que una fila se enlaza a los datos. Dependiendo del tipo de fila, en este caso si se trata del pie del GridView, inserta dentro controles LinkButton para cada letra del alfabeto desde la “A” hasta la “Z”, y le pasa como argumento del LinkButton la letra que muestra el control.
Luego intercepta el evento RowCommand que ocurre cada vez que se presiona un botón dentro del GridView, recupera el valor del botón que se presionó (es decir sobre que letra se presionó) mediante las propiedades e.CommandName y e.CommandArgument del parámetro que nos proporciona el evento, una vez que sabemos que letra del pie del GridView se pulsó, solo queda hacer una consulta a la base de datos con ese parámetro y enlazarlo con el GridView, el ejemplo queda así:
El ejemplo utiliza la tabla Customers de la base de datos Northwind, esta hecho en Visual Basic .Net (no pasa nada con Visual Basic, C# es la voz)
Descargar Ejemplo: Ejemplo GridView paginado alfabéticamente
Favor de darle clic derecho y guardar destino como…, una vez descargado cambiar la extensión a .zip, y descomprimirlo (todo esto porque no me dejan subir archivos zip).
¿Cómo Simular un TabControl para WebForms con MultiView y View?
Otra Consulta de mi amigo Christian (ya se está haciendo costumbre): ¿Hay un componente TabControl para Web?, la respuesta corta NO.
Una mejor respuesta, no, pero se puede hacer, existen muchas formas de hacerlo, pero yo opté por la mas fácil y rápida de todas, para esto tenemos que utilizar dos nuevos controles de ASP .Net 2.0, MultiView y View, estos dos componentes trabajan justos para lograr la funcionalidad que necesitamos.
View.- Sirve como contenedor de otros elementos, es decir actúa como un panel, donde podemos colocar todos los controles que necesitemos.
MultiView.- Como su nombre lo indica es un contenedor que sirve para poner dentro de él uno o más controles View, adicionalmente se encarga de que en un momento dado se visualice sólo uno de los controles View que contiene, para indicarle cual de los controles View queremos visualizar tenemos que utilizar la propiedad ActiveViewIndex.
Ya se habrán dado cuenta que el control MultiView tiene funciones similares al TabControl, en realidad lo único que le hace falta es la parte superior donde se puede seleccionar las páginas, no tiene esta representación gráfica, y que vamos a hacer al respecto, simplemente agregarlo nosotros, la manera más fácil es utilizar un control Menu, lo configuramos para que tenga orientación horizontal ponemos los ítems que queremos y listo. Solo falta interceptar el evento MenuItemClick y ponerle el siguiente código:
void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
MultiView1.ActiveViewIndex = Int32.Parse(e.Item.Value);
}
Para hacer esto, es necesario poner en la propiedad value de cada item el índice del control View que le corresponde.

Esta solución es bastante simple, si quieren hacer algo más elaborado pueden utilizar imágenes para los tabs, el siguiente artículo contiene un ejemplo que utiliza imágenes: A Simple ASP.NET Tab Control Using the MultiView control.
¿Como Actualizar un GridView con Enlace a Datos en Tiempo de Ejecución?
Ayer martes por la mañana, uno de mis amigos me pidió ayuda con la grilla para web de Visual Studio 2005 (GridView), me dijo que no puede realizar actualizaciones de datos haciendo el enlace en tiempo de ejecución, es decir hacer todo por código, sin utilizar los componentes en tiempo de diseño con lo que se puede hacer la actualización de datos en pocos minutos.
Le dije (que no sabes hacer una cosa tan básica) no hay problema, en 20 minutos te mando un ejemplo completo; antes había trabajado de esta manera con el Framework 1.1 y el componente DataGrid sin ningún problema, pero grande fue mi sorpresa al ver que con el GridView las cosas se complicaban enormemente y algo que parecía tan básico no lo era tanto al menos para mi, luego de revisar muchas páginas con ejemplos, casi reventar el monitor con la cabeza y 3 horas de trabajo, al fin logré hacer andar el bendito ejemplo, a continuación algunas pautas para no enloquecer en el intento:
1. No te olvides que estas trabajando en un ambiente Web.- Debo confesarlo esto ya me había pasado antes, Visual Studio te da tantas facilidades para diseñar tus formularios de manera visual, que uno pierde la noción que esta desarrollando una aplicación web, y el error que me estaba volviendo loco esta relacionado simple y llanamente con el código que puse en el evento Load del formulario; como todos saben, el evento Load se dispara cada vez que la pagina se carga, incluido los PostBack necesario para realizar cada acción (por ejemplo pulsar un botón), para resolver el problema simplemente usamos la propiedad IsPostBack de la clase Page, que nos indica si es la primera ves que se carga la pagina o no:
if (Page.IsPostBack)
{
//Se ejecuta todas las veces que se carga la pagina exepto la
//primera vez
}
else
{
// Se ejecuta solo la primera vez que se carga la pagina
{
2. Convertir las columnas en Plantillas.- Para facilitar el trabajo de recuperar los datos del registro que se esta editando, es necesario cambiar los tipos de columnas de BoundField a TemplateField, de esta manera podremos recuperar los datos que se están ingresando en cada uno de controles de la plantilla, utilizando el método FindControl.
3. Implementar los eventos del GridView que necesitemos.- Esta parte es muy importante, puesto que en estos eventos tendremos que poner toda la lógica para realizar la actualización de datos, los eventos más importantes que tenemos que interceptar son: RowEditing, RowUpdating, RowCancelingEdit y RowDeleting.
//método auxiliar para facilitar el trabajo de enlace a datos
void EnlazarDatos()
{
//sdsEmpelado es un ObjectDataSource previamente configurado
GridView1.DataSource = sdsEmpleados;
GridView1.DataBind();
}
void GridView1_RowEditing(object sender, GridViewEditEventArgs e)
{
//estableciendo la propiedad EditIndex hacemos que esa fila se
//ponga en edición
GridView1.EditIndex = e.NewEditIndex;
EnlazarDatos();
}
void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
//recuperamos la fila que estamos editando
GridViewRow fila = GridView1.Rows[e.RowIndex];
//inicializamos los parametros para actualizar
sdsEmpleados.UpdateParameters["EmployeeID"].DefaultValue =
(fila.FindControl(“Label1″) as Label).Text;
sdsEmpleados.UpdateParameters["LastName"].DefaultValue =
(fila.FindControl(“TextBox2″) as TextBox).Text;
sdsEmpleados.UpdateParameters["FirstName"].DefaultValue =
(fila.FindControl(“TextBox3″) as TextBox).Text;
//Ejecutamos la actualización
sdsEmpleados.Update();
//para salir del estado de edición
GridView1.EditIndex = -1;
EnlazarDatos();
}
void GridView1_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
//para salir del estado de edición
GridView1.EditIndex = -1;
EnlazarDatos();
}
void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
//recuperamos la fila que queremos eliminar
GridViewRow fila = GridView1.Rows[e.RowIndex];
//Inicializamos los parametros para eliminar
sdsEmpleados.DeleteParameters["EmployeeID"].DefaultValue =
(fila.FindControl(“Label1″) as Label).Text;
//Ejecutamos la eliminación
sdsEmpleados.Delete();
//para que se actualice el grid
EnlazarDatos();
}
Atención, el código que se muestra es solo un ejemplo, por simplicidad está sin las validaciones necesarias (por ejemplo comprobar que no sean nulos algunos objetos).
Si necesitan revisar un ejemplo totalmente funcional pueden descargar el ejemplo que hice para mi amigo, funciona con la base de datos Northwind con los tres primeros campos de la tabla Employees, utiliza la clase SqlDataSource, también se puede hacer con la clase SqlDataAdapter, pero como estamos trabajando con el .Net Framework 2.0, decidí utilizar la nueva clase SqlDataSource, espero que les sirva de algo.
Descargar Ejemplo: Ejemplo Actualización GridView
Favor de darle clic derecho y guardar destino como…, una vez descargado cambiar la extensión a .zip, y descomprimirlo (todo esto porque no me dejan subir archivos zip).
Dejar un comentario
Comentarios (8)
Comentarios (11)
