Anda di halaman 1dari 1031

Tabla de contenido

INTRODUCCIN ........................................................................................................... 5 1. 2. 3. CREACIN DE UNA CAPA DE ACCESO A DATOS ..................................................................7 CREACIN DE UNA CAPA LGICA DE NEGOCIOS ..............................................................45 PAGINAS MAESTRAS Y NAVEGACIN DEL SITIO ................................................................64

INFORMES BSICOS.................................................................................................... 89 4. 5. 6. VISUALIZACIN DE DATOS CON EL OBJECTDATASOURCE .............................................90 DECLARACIN DE PARAMETROS ..........................................................................................107 DEFINICIN MEDIANTE PROGRAMACIN DE LOS VALORES DE LOS PARAMETROS

DEL OBJECTDATASOURCE...............................................................................................................117 MAESTRO / DETALLE ............................................................................................... 126 7. 8. 9. 10. FILTRADO MAESTRO/DETALLE CON UN DROPDOWNLIST ............................................127 FILTRADO MAESTRO/DETALLE CON DOS DROPDOWNLISTS .......................................138 FILTRADO MAESTRO/DETALLE A TRAVS DE DOS PGINAS ........................................154 FILTRADO MAESTRO/DETALLE USANDO UN GRIDVIEW MAESTRO

SELECCIONABLE CON DETALLES DETAILVIEW ..........................................................................170 FORMATO PERSONALIZADO ..................................................................................... 181 11. 12. 13. 14. 15. FORMATO PERSONALIZADO BASADO EN LOS DATOS................................................182 USO DE PLANTILLAS DE CAMPO EN EL CONTROL GRIDVIEW ...................................202 USANDO PLANTILLAS DE CAMPO EN EL CONTROL DETAILSVIEW ..........................222 USO DE LAS PLANTILLAS EN LOS FORMVIEWS ..............................................................235 MOSTRAR INFORMACIN RESUMIDA EN EL PIE DE PGINA DEL GRIDVIEW .........242

EDICIN, INSERCIN Y ELIMINACIN DE DATOS....................................................... 257 16. UN RESUMEN DE LA EDICIN, INSERCIN Y ELIMINACIN DE DATOS .................258

17.

EXAMINANDO LOS EVENTOS ASOCIADOS CON LA INSERCIN, ACTUALIZACIN

Y ELIMINACIN. .................................................................................................................................296 18. 19. MANEJO DE EXCEPCIONES EN UNA PGINA ASP.NET A NIVEL DE DAL Y BLL ......320 AGREGAR CONTROLES DE VALIDACION PARA LAS INTERFACES DE EDICION E

INSERCIN ...........................................................................................................................................336 20. 21. 22. 23. PERSONALIZACION DE LA INTERFAZ DE MODIFICACION DE DATOS .....................356 APLICACIN DE LA CONCURRENCIA OPTIMISTA .........................................................375 ADICION DE CONFIRMACION DE ELIMINACION DEL LADO DEL CLIENTE .............414 LIMITACIN DE LA FUNCION DE MODIFICACION DE DATOS BASADOS EN EL

USUARIO...............................................................................................................................................424 PAGINACION Y ORDENAMIENTO DE DATOS.............................................................. 444 24. 25. 26. 27. PAGINACIN Y ORDENAMIENTO DE DATOS DEL INFORME ......................................445 PAGINACIN EFICIENTE A TRAVS DE LARGAS CANTIDADES DE DATOS ............465 ORDENAMIENTO PERSONALIZADO DE DATOS PAGINADOS .....................................492 CREAR UNA INTERFAZ DE USUARIO DE ORDENAMIENTO PERSONALIZADO........505

ACCIONES DE BOTONES PERSONALIZADAS ............................................................... 519 28. AGREGAR Y RESPONDER A LOS BOTONES DEL GRIDVIEW .........................................520

MOSTRANDO DATOS CON EL DATALIST Y EL REPEATER ........................................... 544 29. 30. MOSTRANDO DATOS CON LOS CONTROLES DATALIST Y REPEATER ....................545 DAR FORMATO AL DATALIST Y REPEATER BASADOS EN LOS DATOS ...................568

31. MOSTRAR VARIOS REGISTROS POR FILA CON EL CONTROL DATALIST.....................581 32. CONTROLES DE DATOS WEB ANIDADOS ............................................................................588 ESCENARIOS DE FILTRADO CON EL DATALIST Y REPEATER ....................................... 601 33. FILTRADO MAESTRO/ DETALLE CON UN DROPDOWNLIST ...........................................602 34. FILTRADO MAESTRO/DETALLE A TRAVES DE DOS PGINAS ........................................615

35. FILTRADO MAESTRO/DETALLE USANDO UNA LISTA CON VIETAS DE LOS REGISTROS MAESTROS Y UN DATALIST DE DETALLES ...........................................................629 EDICION Y ELIMINACION DE DATOS CON EL DATALIST ............................................. 650 36. RESUMEN DE EDICIN Y ELIMINACIN DE DATOS EN EL DATALIST ..........................651 37. REALIZACION DE ACTUALIZACIONES POR LOTES ............................................................674 38. MANEJO DE EXCEPCIONES A NIVEL DE DAL Y BLL ...........................................................686 39. AGREGAR CONTROLES DE VALIDACIN A LA INTERFAZ DE EDICIN DEL DATALIST ................................................................................................................................................................695 40. PERSONALIZAR LA INTERFAZ DE EDICION DEL DATALIST .............................................707 PAGINACION Y ORDENACION CON EL DATALIST Y REPEATER ................................... 722 41. PAGINAR UN REPORTE DE DATOS CON UN CONTROL DATALIST Y REPEATER .......723 42. ORDENAR DATOS EN UN CONTROL DATALIST Y REPEATER.........................................743 PERSONALIZAR ACCIONES DE BOTON CON EL DATALIST Y REPEATER ....................... 775 43. PERSONALIZAR ACCIONES DE BOTON CON EL DATALIST Y REPEATER .....................776 TRABAJAR CON ARCHIVOS BINARIOS........................................................................ 787 51. CARGA DE ARCHIVOS ...............................................................................................................788 52. MOSTRAR DATOS BINARIOS EN LOS CONTROLES DE DATOS WEB .............................808 53. INCLUIR LA OPCIN DE CARGA DE ARCHIVO CUANDO AGREGAMOS UN NUEVO REGISTRO.............................................................................................................................................824 54. ACTUALIZACIN Y ELIMINACIN DE DATOS BINARIOS EXISTENTES .........................845 ALMACENAMIENTO DE DATOS EN CACHE ................................................................ 873 55. ALMACENAMIENTO DE DATOS EN CACHE CON EL OBJECTDATASOURCE ................874 56. ALMACENAMIENTO DE DATOS EN CACHE EN LA ARQUITECTURA .............................876 57. ALMACENAMIENTO DE DATOS EN CACHE AL INICIO DE LA APLICACIN ................877 58. UTILIZACIN DE SQL PARA DEPENDENCIAS DE CACHE .................................................878 MANEJO DE MAPAS DEL SITIO DE BASE DE DATOS .................................................... 879

59. CONSTRUCCIN DE UN PROVEEDOR PERSONALIZADO DE MAPA DE SITIO QUE MANEJE LA BASE DE DATOS ...........................................................................................................880 TRABAJAR CON LOTES DE DATOS ............................................................................ 881 60. ENVOLVER MODIFICACIONES DE BASE DE DATOS EN UNA TRANSACIN ................882 61. ACTUALIZACIN DE LOTES ....................................................................................................883 62. ELIMINACIN POR LOTES ........................................................................................................884 63. INSERCIN POR LOTES .............................................................................................................885 ESCENARIOS AVANZADOS DE ACCESO A DATOS ...................................................... 886 64. CREACIN DE PROCEDIMIENTOS ALMACENADOS PARA LOS TABLEADAPTERS DEL DATASET TIPIADO .............................................................................................................................887 65. UTILIZAR PROCEDIMIENTOS ALMACENADOS EXISTENTES PARA LOS TABLEADAPTERS DEL DATASET TIPIADO ...................................................................................919 66. ACTUALIZAR EL TABLEADAPTER PARA UTILIZAR JOINs .................................................938 67. AGREGAR COLUMNAS ADICIONALES AL DATATABLE .....................................................956 68. TRABAJAR CON COLUMNAS CALCULADAS ........................................................................971 69. CONFIGURACION DE LA CONEXION DE LA CAPA DE ACCESO A DATOS Y DEFINICIONES A NIVEL DE COMANDO ........................................................................................990 70. PROTECCIN DE LAS CADENAS DE CONEXIN Y OTRA INFORMACIN DE CONFIGURACIN ............................................................................................................................ 1002 71. DEPURACIN DE PROCEDIMIENTOS ALMACENADOS ................................................... 1017 72. CREACIN DE PROCEDIMIENTOS ALMACENADOS Y FUNCIONES DEFINIDAS POR EL USUARIO CON MANAGED CODE ................................................................................................ 1019

INTRODUCCIN

1. CREACIN DE UNA CAPA DE ACCESO A DATOS


En este tutorial empezaremos desde el principio y crearemos la capa de acceso a datos (DAL), utilizando un DataSet con tipo para acceder a la informacin de una base de datos. Introduccin Como desarrolladores web, nuestro trabajo gira en torno al trabajo con datos. Creamos bases de datos para almacenar datos, los cdigos para modificarlos y recuperarlos y las pginas web para agrupar y recopilar los mismos. Este es el primer tutorial de una larga serie de tutoriales que explorar las tcnicas para implementar estos enfoques comunes en Asp.Net 2.0. Empezaremos con la creacin de una arquitectura de software compuesta por una capa de acceso a datos (DAL) que usa DataSet con tipos, una capa lgica de negocios (BLL) que har cumplir las reglas de negocio personalizadas y una capa de presentacin compuesta por pginas Asp.Net que comparten una pgina de diseo comn. Una vez que este trabajo de campo de segundo plano ha sido establecido, pasaremos a la presentacin de informes que ensean como mostrar, resumir, recopilar y validar los datos desde una aplicacin web. Estos tutoriales estn orientados a ser concisos y proporcionar instrucciones paso a paso con un montn de capturas de pantalla que lo guiarn por el proceso visual. En este tutorial empezaremos desde el principio y crearemos la capa de acceso a datos (DAL), seguido de la creacin de la capa lgica de negocios (BLL) en el segundo tutorial y trabajaremos en el diseo y navegacin de la pgina en el tercero. Los tutoriales despus del tercero se basan en los fundamentos establecidos en los tres primeros. Tenemos mucho que cubrir en el primer tutorial, as que prendamos el Visual Studio y empecemos! Paso 1: Crear un proyecto Web y la conexin a la base de datos Antes de crear nuestra capa de acceso a datos (DAL), primero necesitamos crear un sitio web y configurar nuestra base de datos. Comenzamos creando un nuevo archivo de sitio web basado en Asp.Net. Para realizar esto, vaya al men Archivo y seleccione Nuevo sitio web, lo cual mostrar el cuadro de dialogo de Nuevo sitio web. Escoja la plantilla Sitio Web Asp.Net, establezca la lista desplegable del cuadro de Localizacin en File System, seleccione una carpeta para colocar el sitio web y establezca el lenguaje en Visual Basic.

Figura 1. Crear un nuevo archivo de sitio web basado en el sistema. Esto crear un nuevo sitio web con una pgina Asp.Net Default.aspx, una carpeta App_Data y un archivo Web.config. Con el sitio web creado, el siguiente paso es agregar una referencia a la base de datos en el explorador de servidores de Visual Studio. Agregando una base de datos al explorador de servidores podemos agregar sus tablas, procedimientos almacenados, vistas, entre otros; todo desde Visual Studio. Tambin puede ver los datos de las tablas o crear sus propias consultas a mano o grficamente por medio del Generador de Consultas. Por otra parte, cuando construimos el DataSet con tipo para la DAL necesitamos un punto desde Visual Studio hacia la base de datos desde la cual el DataSet con tipo sera construido. Aunque en este punto, podemos proporcionar la informacin de esta conexin, Visual Studio poblar automticamente la lista desplegable de las bases de datos registradas en el explorador de servidores. Los pasos para agregar la base de datos Northwind al explorador de servidores, depende de si usted desea usar la base de datos de SQL Server 2005 Express Edition en la carpeta App_Data o si tiene una configuracin del servidor de base de datos Microsoft SQL Server 2000 o 2005 que desee usar en su lugar. Usar una base de datos en la carpeta App_Data

Si no tiene un servidor de base de datos SQL Server 2000 o 2005 al cual conectarse, o simplemente desea evitar agregar la base de datos a un servidor de base de datos, puede utilizar la versin SQL Server 2005 Express Edition de la base de datos Northwind que est localizado en el sitio web de descarga de la carpeta App_Data (NORTHWND.MDF). Una base de datos situada en la carpeta App_Data se agrega automticamente al explorador de servidores. Asumiendo que tiene SQL Server 2005 Express Edition instalado en su computador, debera ver en el explorador de soluciones un nodo llamado Northwind.mdf que puede expandirse y explorar sus tablas, vistas, procedimientos almacenados y as sucesivamente. (Ver figura 2). La carpeta App_Data tambin puede contener archivos Microsoft Access .mdb, que al igual que sus homlogos de SQL Server, se agregaran automticamente al Explorador de Servidores. Conexin a la base de datos desde un servidor SQL Server 2000 o 2005 Alternativamente se puede conectar una base de datos Northwind instalada en un servidor de base de datos. Si el servidor de base de datos no tiene instalada la base de datos Northwind, primero debemos agregarla al servidor de base de datos. Una vez que haya instalado la base de datos, vaya al explorador de servidores en Visual Studio, haga clic en el nodo conexiones de datos y elija Agregar conexin. Si no ve el explorador de servidores, vaya a Ver/Explorador de Servidores o presione Ctrl+Alt+S. Esto abrir el cuadro de dialogo Agregar Conexin, en el que se puede especificar el servidor al cual desea conectarse, la informacin de autenticacin y el nombre de la base de datos. Una vez haya configurado correctamente la informacin de la conexin de la base de datos y haga clic en el botn Aceptar, la base de datos se agrega como un nodo debajo del nodo Conexiones de datos. Puede expandir el nodo de la base de datos para explorar sus tablas, vistas, procedimientos almacenados y as sucesivamente. Paso 2: Creacin de la capa de acceso a datos Cuando trabajamos con datos una opcin es integrar la lgica especfica de datos directamente dentro de la capa de presentacin (en una pgina web, las pginas Asp.Net representan la capa de presentacin). Estas pueden tomar la forma de escritura

de cdigo Asp.Net en la parte de cdigo de la pgina Asp.Net o utilizando un control SqlDataSource en la porcin de marcado. En cualquier caso este enfoque une la lgica de acceso a datos con la capa de presentacin. Sin embargo lo que se recomienda es separar la lgica de acceso a datos de la capa de presentacin. Esta capa independiente se conoce capa de acceso a datos, para abreviar DAL y normalmente es implementada como un proyecto separado de biblioteca de clases. Los beneficios de esta arquitectura estn bien documentados y este es el enfoque que tendr esta serie de tutoriales.

Figura 2. Agregar una conexin de la base de datos Northwind a su servidor de base de datos. Todo el cdigo que es especifico del origen de datos subyacentes, as como la creacin de una conexin a la base de datos, la emisin de comandos SELECT, INSERT, UPDATE y DELETE, deben ser ubicados en la DAL. La capa de presentacin no debe contener ninguna referencia a dicho cdigo de acceso a datos, sino que debe realizar llamadas a la DAL para cualquiera de todos los datos solicitados. Las capas de acceso a datos normalmente contienen mtodos para acceder a la base de datos subyacente. Las tablas de la base de datos Northwind, por ejemplo Products y Categories registran los productos para la venta y la categora a la que pertenecen. En nuestra DAL tendremos mtodos como: GetCategories ( ), devuelve la informacin de todas las categoras. GetProducts ( ), devuelve la informacin de todos los productos.

GetProductsByCategoryId ( ), devuelve todos los productos que pertenecen a una categora GetProductByProductId (), devuelve la informacin acerca de un producto

Cuando estos mtodos son invocados, se conectan a la base de datos, ejecutan la consulta apropiada y devuelven los resultados. Lo importante es cmo devuelven los resultados? Estos mtodos solo pueden devolver un DataSet o un DataReader poblado con la consulta de la base de datos, pero lo ideal es que estos resultados sean devueltos usando objetos fuertemente tipiados. Un objeto fuertemente tipiado es aquel cuyo esquema esta rgidamente definido en tiempo de compilacin, mientras que por el contrario un objeto dbilmente tipiado es aquel cuyo esquema no se conoce sino en tiempo de ejecucin. El DataSet y el DataReader (por defecto) son objetos dbilmente tipiados, ya que su esquema est definido por las columnas devueltas por la consulta de base de datos utilizada para poblarlos. Para acceder a una columna en particular de un DataTable dbilmente tipiado se utiliza sintaxis como: DataTable.Rows (index) (ColumnName). En los DataTable dbilmente tipiados de este ejemplo est expuesto el hecho que para acceder al nombre de una columna necesitamos usar una cadena o un ndice ordinal. Por el contrario un DataTable fuertemente tipiado tendr cada una de sus columnas implementadas como propiedades, lo que resulta en un cdigo parecido a este: DataTable.Rows (ndex).ColumnName. Para devolver objetos fuertemente tipiados, los desarrolladores pueden crear sus objetos de negocio personalizados o usar DataSets con tipos. Un objeto de negocio es implementado por el desarrollador como una clase cuyas propiedades normalmente reflejan las columnas de la tabla de la base de datos subyacente que el objeto de negocio representa. Un DataSet con tipo es una clase generada por usted para Visual Studio basado en un esquema de base de datos y cuyos miembros son fuertemente tipiados de acuerdo a este esquema. El DataSet con Tipo en si mismo consiste de clases que comprenden las clases DataSet, DataTable y DataRow de ADO.Net. Adems de los DataTables fuertemente tipiados, los DataSet tipiados ahora tambin incluyen TableAdapters, los cuales son clases con mtodos para poblar los DataTables del DataSet y propagar las modificaciones en los DataTables de regreso a la base de datos.

Nota: Para obtener ms informacin de las ventajas y desventajas de usar DataSets tipiados versus los objetos de negocio personalizados, consulte Diseo de componentes de niveles de datos y anlisis de los datos a travs de los niveles. Usaremos los DataSet fuertemente tipiados para la arquitectura de estos tutoriales. La figura 3 muestra el diagrama de flujo entre las diferentes capas de una aplicacin que utiliza DataSets tipiados.

Fig. 3 Todo el cdigo de acceso a datos es relegado a la DAL Creacin de un DataSet tipiado y un TableAdapter Para empezar a crear nuestra DAL, empezamos agregando un DataSet tipiado a nuestro proyecto. Para realizar esto hacemos clic derecho en el nodo del proyecto en el explorador de soluciones y seleccionamos agregar nuevo elemento. Seleccionamos la opcin DataSet de la lista de plantillas y la llamamos Northwind.xsd.

Fig. 4 Seleccionamos agregar un nuevo DataSet a su proyecto Despus de hacer clic en agregar y cuando se nos solicite agregar el DataSet a la carpeta App_Code, seleccionamos S. Entonces se mostrara el diseador para el DataSet

y se iniciar el asistente de configuracin del TableAdapter, permitindole agregar el primer TableAdapter a su DataSet tipiado. Un DataSet con tipo sirve como una coleccin de datos fuertemente tipiados, este est compuesto de instancias DataTable fuertemente tipiadas, cada una de las cuales est compuesta de instancias DataRow fuertemente tipiadas. Comenzaremos creando un DataTable fuertemente tipiado para cada una de las tablas de la base de datos subyacente que necesitamos para trabajar en esta serie de tutoriales. Comenzaremos creando un DataTable para la tabla Products. Tenga en cuenta que los DataTable fuertemente tipiados no incluyen ninguna informacin sobre como acceder a los datos de la tabla de la base de datos subyacente. Con el fin de recuperar los datos para poblar el DataTable, usaremos una clase TableAdapter la cual funciona como nuestra capa de acceso a datos. Para nuestra DataTable Products, el TableAdapter contendr los mtodos GetProducts (), GetProductsByCategoryID (categoryID) y otros que se invocaran desde la capa de presentacin. El papel de los DataTables es el de servir como objetos para pasar datos entre las capas. El asistente de configuracin del TableAdapter comienza pidindole que seleccione la base de datos con la cual trabajar. La lista desplegable muestra todas las bases de datos en el explorador de servidores. Si no ha agregado la base de datos Northwind al explorador de servidores, en este momento puede hacerlo, haciendo clic en el botn Nueva Conexin. Despus de seleccionar la base de datos y hacer clic en Siguiente, se nos preguntara si deseamos guardar la cadena de conexin en el archivo Web.config. Al guardar la cadena de conexin evitaremos tenerla codificada en las clases TableAdapter, lo cual simplifica las cosas si por alguna razn la informacin de la cadena de conexin cambia en el futuro. Si usted selecciona guardar la cadena de conexin en el archivo de configuracin, esta es guardada en la seccin <connectionStrings>, que opcionalmente puede ser encriptada para mayor seguridad o modificaciones posteriores por medio de las nuevas propiedades de la pgina Asp.Net en la herramienta de administracin de interfaz grafica de usuario IIS, que es ms ideal para administradores.

Luego necesitamos definir el esquema para el primer DataTable fuertemente tipiado y proporcionar el primer mtodo a nuestro TableAdapter que usara cuando poblemos el DataSet fuertemente tipiado. Estos dos pasos se completan de forma simultnea por medio de la creacin de una consulta que devuelve las columnas desde la tabla que deseamos reflejar en nuestro DataTable. Al finalizar el asistente le daremos un nombre al mtodo de esta consulta. Una vez que realicemos esto, el mtodo ser invocado desde nuestra capa de presentacin. El mtodo ejecutara la consulta definida y poblara un DataTable fuertemente tipiado.

Figura 5. Eleccin de la base de datos Northwind de la lista desplegable

Figura 6. Guardar la cadena de conexin en Web.Config Para empezar a definir la consulta, primero debemos indicar como queremos que el TableAdapter ejecute la consulta. Podemos usar una sentencia ad-hoc, crear un nuevo procedimiento almacenado o utilizar un procedimiento almacenado ya existente. Para estos tutoriales utilizaremos las instrucciones SQL. Consulte el articulo de Bryan Noyes Construir una capa de acceso a datos con el diseador del DataSet de Visual Studio 2005 para un ejemplo del uso de procedimientos almacenados.

Figura 7. Consultar los datos usando una sentencia SQL ad-hoc En este punto podemos escribir la consulta SQL a mano. Cuando creamos el primer mtodo en el TableAdapter normalmente queremos tener la consulta que devuelva las columnas que necesitan ser expresadas en el DataTable correspondiente. Para realizar esto, creamos una consulta que devuelve todas las columnas y las filas de la tabla Products:

Figura 8. Ingrese el cdigo de consulta SQL en el cuadro de texto Tambin puede utilizar el generador de consultas y construir grficamente la consulta como se muestra en la Figura 9.

Figura 9. Crear la consulta de forma grfica, por medio del Generador de Consultas

Despus de crear la consulta, pero antes de movernos a la siguiente pantalla, damos clic en el botn Opciones avanzadas. En el proyecto del sitio web Generar sentencias Insert, Update, Delete es la nica opcin avanzada seleccionada por defecto, si ejecutamos el asistente desde una biblioteca de clases o desde un proyecto de Windows, la opcin Usar concurrencia optimista tambin estara seleccionada. Por ahora deje la opcin Usar concurrencia optimista sin marcar. Examinaremos la concurrencia optimista en futuros tutoriales.

Figura 10. Seleccione solo la opcin Generar sentencias Insert, Update y Delete Despus de verificar las opciones avanzadas, damos clic en siguiente para pasar a la pantalla final. Aqu se nos pide seleccionar los mtodos para agregar al TableAdapter. Existen dos modelos para poblar los datos: Llenar un objeto DataTable, con este enfoque se crea un mtodo que tomar el DataTable como un parmetro y lo poblar basado en los resultados de la consulta. La clase DataAdapter de ADO.Net, por ejemplo, implementa este enfoque con su mtodo Fill (). Devolver un DataTable, con este enfoque el mtodo crea y llena el DataTable por usted y lo devuelve como el valor de retorno del mtodo. Usted puede tener un TableAdapter con uno o ambos enfoques aplicados. Tambin puede cambiar el nombre de los mtodos establecidos aqu, dejaremos ambas casillas de verificacin seleccionadas, aunque solo usaremos el ltimo enfoque a travs de todos estos tutoriales. Adems cambiaremos el nombre genrico GetData por GetProducts.

Si verificamos, la casilla final GenerateDBDirectMethods crea los mtodos Insert (), Update () y Delete () del TableAdapter. Si deja esta opcin sin seleccionar, todas las actualizaciones necesitan hacerse por medio del mtodo exclusivo Update () del TableAdapter, las cuales se hacen en un DataSet tipiado, un DataTable, un DataRow o un conjunto de DataRows. (Si usted no selecciona la opcin Generar sentencias Insert, Update y Delete en las propiedades avanzadas de la figura 9, esta casilla de verificacin no tendr ningn efecto). Dejaremos seleccionada esta casilla.

Figura 11. Cambiar el nombre del mtodo GetData por GetProducts Complete el asistente haciendo clic en Finalizar. Despus el asistente se cierra y volveremos al Diseador de DataSet el cual le muestra el DataTable que acabamos de crear. Usted puede ver la lista de columnas en el DataTable Products (ProductID, ProductName, etc.) as como los mtodos de la ProductsTableAdapter (Fill () y GetProducts ()).

Figura 12. Cambiar el nombre del mtodo GetData por GetProducts Hasta el momento tenemos y una un DataSet clase tipiado con un nico DataTable tipiada

(Northwind.Products)

DataAdapter

fuertemente

(NorthwindTableAdapters.ProductsTableAdapter) con un mtodo GetProducts (). Estos objetos pueden ser usados para acceder a una lista de todos los productos desde un cdigo as:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter () Dim products as Northwind.ProductsDataTable products= productsAdapter.GetProducts() For Each productRow As Northwind.ProductsRow In products Response.Write ("Product: " & productRow.ProductName & "<br />")

Este cdigo no requiere que escribamos ni un poco de cdigo de acceso a datos especfico. No tuvimos que crear ninguna instancia de clases ADO.Net, ni tuvimos que referirnos a ninguna cadena de conexin, consultas SQL o procedimientos almacenados. En su lugar, el TableAdapter gener el cdigo de acceso a datos de bajo nivel por nosotros. Cada objeto usado en este ejemplo tambin es fuertemente tipiado, permitindole a Visual Studio proporcionar Intellisense y la comprobacin en tiempo de compilacin. Lo mejor de todo es que los DataTable devueltos por el TableAdapter pueden ser enlazados a controles de datos web Asp.Net, como el GridView, DetailsView,

DropDownList, CheckBoxList y otros. El siguiente ejemplo muestra el enlace del DataTable devuelto por el mtodo GetProducts () a un GridView en tan solo tres lneas de cdigo en el controlador de eventos del evento Page_Load. AllProducts.aspx
<%@ Page Language="VB" AutoEventWireup="true" CodeFile="AllProducts.aspx.vb" Inherits="AllProducts" %> <! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>View All Products in a GridView</title> <link href="Styles.css" rel="stylesheet" type="text/css" /> </head> <body> <form id="form1" runat="server"> <div> <h1> All Products</h1> <p> <asp:GridView ID="GridView1" runat="server" CssClass="DataWebControlStyle"> <HeaderStyle CssClass="HeaderStyle" /> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> </asp:GridView> </p> </div> </form> </body> </html>

AllProducts.aspx.vb
Imports NorthwindTableAdapters Partial Class AllProducts Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load Dim productsAdapter As New ProductsTableAdapter

GridView1.DataSource = productsAdapter.GetProducts() GridView1.DataBind() End Sub End Class

Figura 13. La lista de productos es mostrada en un GridView Mientras este ejemplo requiere que escribamos tres lneas de cdigo en el controlador de eventos Page_Load de la pgina Asp.Net, en los futuros tutoriales examinaremos como usar el ObjectDataSource para la declaracin de la recuperacin de datos desde la DAL. Con el ObjectDataSource no tenemos que escribir ningn cdigo y tambin obtendremos soporte de paginacin y agrupacin. Paso 3: Adicin de mtodos parametrizados a nuestra capa de acceso a datos Hasta este momento nuestra clase ProductsTableAdapter tiene un solo mtodo, el cual devuelve todos los productos en la base de datos. Si bien la posibilidad de trabajar con todos los productos es til, hay ocasiones en que deseemos recuperar informacin de un producto especfico, o de todos los productos que pertenecen a una categora especifica. Para agregar esta funcionalidad a nuestra capa de acceso a datos podemos aadir mtodos parametrizados al TableAdapter. Aadiremos el mtodo GetProductsByCategoryID (categoryID). Para agregar un nuevo mtodo, regresamos al diseador de DataSet, hacemos clic en la seccin TableAdapter y seleccionamos Agregar Consulta.

Figura 14. Haga clic en el TableAdapter y elija Agregar Consulta Lo primero que aparece es si deseamos acceder a la base de datos usando una instruccin SQL ad-hoc o por medio de un nuevo procedimiento almacenado o uno ya existente. Optamos por seleccionar de nuevo la sentencia ad-hoc. Luego nos preguntan qu tipo de consulta deseamos utilizar. Ya que deseamos devolver todos los productos pertenecientes a una categora en particular, deseamos escribir una sentencia SELECT que devuelva filas. El siguiente paso es definir la consulta SQL usada para acceder a los datos. Como solo deseamos devolver aquellos productos que pertenecen a una categora especfica, usamos la misma declaracin de GetProducts () pero agregamos la siguiente clausula WHERE CategoryID=@CategoryID. El parmetro @CategoryID indica al asistente del TableAdapter que el mtodo que estamos creando requiere un parmetro de entrada del tipo correspondiente (es decir, un entero que acepta valores Null).

Figura 15. Seleccione crear una sentencia SELECT que devuelve filas

Figura 16. Escriba una consulta para devolver solo los productos que pertenezcan a una categora especfica. En el ltimo paso podemos elegir los enfoques de acceso a datos que usaremos, as como personalizar los nombres de los mtodos generados. Para el enfoque de relleno, cambiaremos el nombre a FillByCategoryID y para el enfoque de devolucin de un DataTable (los mtodos Get X) usaremos GetProductsByCategoryID.

Figura 17. Seleccione los nombres para los mtodos del TableAdapter Despus de completar el asistente, el diseador del DataSet incluye nuevos mtodos al TableAdapter.

Figura 18. Los productos pueden ser consultados por categora Tome un momento para agregar un mtodo GetProductByProductID (productID) usando la misma tcnica. Estas consultas parametrizadas pueden ser probadas directamente desde el diseador de DataSet. De clic derecho en el mtodo en el TableAdapter y seleccione Vista previa de datos. Luego ingrese los valores para usar por los parmetros y de clic en Vista Previa.

Figura 19. Los productos pertenecientes a la categora Bebidas son mostrados Con el mtodo GetProductByProductID (productID) en nuestra DAL, podemos crear una pgina Asp.Net que muestre solo los productos de una categora especfica. El siguiente ejemplo muestra todos los productos que estn en la categora Bebidas, que tiene un CategoryID de 1.

Figura 20. Los productos de la categora Bebidas son mostrados Paso 4: Actualizar, insertar y eliminar datos

Hay dos enfoques de uso general para insertar, actualizar y eliminar datos. El primer enfoque el cual llamaremos enfoque directo de base de datos, implica la creacin de mtodos que luego son invocados, generando un comando INSERT, DELETE y UPDATE a la base de datos operando en un solo registro de la base de datos. Estos valores suelen pasarse en una serie de valores escalares (enteros, cadenas, bolanos, fechas, horas y as sucesivamente) correspondientes a los valores a insertar, actualizar o eliminar. Por ejemplo con este enfoque para la tabla Products, el mtodo delete tomara un parmetro entero, indicando el ProductID del registro que se desea eliminar, mientras que el mtodo Insert tomara una cadena para el ProductName, un decimal para el UnitPrice, un nmero entero para UnitsOnStock y as sucesivamente.

Figura 21. Cada solicitud Insert, Update y Delete es enviada inmediatamente a la base de datos El otro enfoque, al cual nos referiremos como enfoque de actualizacin por lotes, es para actualizar un DataSet completo, un DataTable o una coleccin de DataRows en una llamada al mtodo. Con este enfoque un desarrollador elimina, inserta y actualiza los DataRows en un DataTable y luego pasa estos DataRows o DataTable a un mtodo de actualizacin. Luego este mtodo enumera los DataRows pasados y determina si ha sido o no modificado, agregado o borrado (por medio del valor de la propiedad RowState del DataRow) y emite la solicitud apropiada a la base de datos para cada registro.

Figura 22. Todos los cambios son sincronizados con la base de datos cuando el mtodo Update es invocado El TableAdapter por defecto siempre utiliza el enfoque de actualizacin por lotes, pero tambin soporta el enfoque directo de base de datos. Ya que seleccionamos la opcin Generar sentencias Update, Insert y Delete en las propiedades avanzadas cuando creamos nuestro TableAdapter, el TableAdapter Products contiene un mtodo Update (), el cual implementa el enfoque de actualizacin por lotes. En concreto el TableAdapter contiene un mtodo Update () que puede ser pasado al DataSet tipiado, un DataTable fuertemente tipiado o uno o ms DataRows. Si usted dejo seleccionada la casilla GenerateDBDirectMethods cuando creamos el primer TableAdapter, el enfoque directo de base de datos tambin ser implementado por medio de los mtodos Update (), Delete () y Insert (). Ambos enfoques de modificacin de datos utilizan las propiedades InsertCommand, UpdateCommand y DeleteCommand del TableAdapter para generar sus comandos Insert, Update y Delete a la base de datos. Podemos inspeccionar y modificar las propiedades InsertCommand, UpdateCommand y DeleteCommand haciendo clic en el TableAdapter en el diseador del DataSet y luego ir a la ventana propiedades. (Asegrese propiedades). de tener seleccionada el TableAdapter y que el objeto ProductsTableAdapter es el nico seleccionado en la lista desplegable de la ventana de

Figura 23. El TableAdapter tiene propiedades InsertCommand, UpdateCommand y DeleteCommand Para examinar o modificar cualquiera de estas propiedades de comando de la base de datos, haga clic en la sub-propiedad CommandText, lo cual abrir el Generador de Consultas. El siguiente cdigo muestra cmo utilizar el enfoque de actualizacin por lotes para doblar el precio de todos los productos que no estn descontinuados y que tienen 25 unidades de existencia o menos:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter () Dim products As Northwind.ProductsDataTable = productsAdapter.GetProducts () For Each product As Northwind.ProductsRow In products If Not product. Discontinued AndAlso product.UnitsInStock <= 25 Then product.UnitPrice *= 2 End if Next productsAdapter.Update (products)

Figura 24. Configurar las sentencias Insert, Update y Delete en el generador de Consultas El siguiente cdigo muestra cmo usar el enfoque directo de base de datos para que mediante programacin eliminemos un producto en particular, luego actualicemos uno y finalmente agreguemos un nuevo producto:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter () productsAdapter.Delete(3) productsAdapter.Update( _ "Chai", 1, 1, "10 boxes x 20 bags", 18.0, 39, 15, 10, false, 1) productsAdapter.Insert( _ "New Product", 1, 1, "12 tins per carton", 14.95, 15, 0, 10, false)

Creacin de mtodos Insert, Update y Delete personalizados Los mtodos Insert (), Update () y Delete (), creados por el enfoque directo de base de datos pueden ser un poco engorrosos, especialmente para tablas con muchas columnas. Observando el ejemplo anterior sin la ayuda de Intellisense no es claro que columnas de la tabla Products se unen a cada parmetro de entrada de los mtodos Insert () y Update (). Pueden haber ocasiones en que solo deseemos actualizar una o dos columnas, o deseemos un mtodo personalizado Insert () que tal vez devuelva el valor del campo IDENTITY (incremento automtico) del registro recin creado.

Para crear un mtodo personalizado, regrese al diseador de DataSet, haga clic en el TableAdapter y elija Agregar nueva consulta, regresando al asistente del TableAdapter. En la segunda pantalla podemos indicar el tipo de consulta que deseamos crear. Crearemos un mtodo que agregue un nuevo registro y luego devuelva el valor del ProductID del registro recin creado. Por lo tanto la opcin es crear una consulta INSERT. En la siguiente pantalla aparece el CommandText del InsertCommand. Aumentaremos esta consulta agregando al final de la consulta SELECT SCOPE IDENTITY (), el cual devolver el valor de la ultima identidad insertado en la columna IDENTITY en el mismo alcance. (Consulte la documentacin tcnica para obtener ms informacin acerca de SCOPE IDENTITY () y es probable que desee usarlo en lugar de @ @IDENTITY.). Asegrese de finalizar la instruccin INSERT con un punto y coma antes de agregar la sentencia SELECT.

Figura 25. Crear un mtodo para agregar una nueva fila a la tabla Products

Figura 26. Aumentar la consulta para devolver el valor SCOPE IDENTITY () Por ltimo nombre el nuevo mtodo InsertProduct.

Figura 27. Establezca el nombre del nuevo mtodo en InsertProduct Cuando regrese al diseador del DataSet vera que el TableAdapter Products contiene un nuevo mtodo InsertProduct. Si este nuevo mtodo no tiene un parmetro de entrada para cada columna de la tabla Products, lo ms probable es que haya olvidado

finalizar la declaracin INSERT con un punto y coma. Configure el mtodo InsertProduct y asegrese de que existe un punto y coma delimitando las sentencias INSERT y SELECT. Por defecto los mtodos Insert generan mtodos de No-consulta, lo que significa que devolver el nmero de filas afectadas. Sin embargo, queremos que el mtodo devuelva el valor regresado por la consulta y no el nmero de filas afectadas. Para realizar esto, modificamos la propiedad ExecuteMode del mtodo InsertProduct a Scalar.

Figura 28. Cambiar la propiedad ExecuteMode a Scalar El siguiente cdigo muestra el nuevo mtodo InsertProduct en accin:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter() Dim new_productID As Integer = Convert.ToInt32(productsAdapter.InsertProduct( _ "New Product", 1, 1, "12 tins per carton", 14.95, 10, 0, 10, false)) productsAdapter.Delete(new_productID)

Paso 5: Completar la capa de acceso a datos Tenga en cuenta que la clase ProductsTableAdapter devuelve los valores de categoryID y SupplierID de la tabla Products, pero no devuelve la columna CategoryName de la tabla Categories o la columna CompanyName de la tabla Suppliers, aunque es probable que deseemos mostrar estas columnas cuando mostremos la informacin de los productos. Podemos aumentar el mtodo inicial GetProducts () del TableAdapter para

incluir los valores de las columnas CategoryName y CompanyName los cuales actualizaran el DataTable fuertemente tipiado para incluir estas columnas tambin. Esto podra presentar un problema, ya que los mtodos para insertar, actualizar y eliminar datos estn basados en este mtodo inicial. Afortunadamente los mtodos automticamente generados para insertar, actualizar y eliminar no son afectados por las sub-consultas de la sentencia SELECT. Al tomar la precaucin de agregar nuestras consultas Categories y Suppliers como sub-consultas, en lugar de JOINs, evitaremos tener que elaborar nuevamente los mtodos de modificacin de datos. Haga clic en el mtodo GetProducts () del ProductsTableAdapter y elija Configurar. Luego ajuste la sentencia SELECT para que se vea de la siguiente forma:
SELECT ProductID, ProductName, SupplierID, CategoryID,QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT (SELECT FROM Products CategoryName CompanyName FROM FROM Suppliers WHERE Categories WHERE = Categories.CategoryID=Products.CategoryID) as CategoryName, Suppliers.SupplierID Products.SupplierID) as SupplierName

Figura 29. Actualizar la sentencia SELECT para el mtodo GetProducts () Despus de actualizar el mtodo GetProducts () para usar esta nueva consulta el DataTable incluir dos nuevas columnas: CategoryName y SupplierName.

Figura 30. El DataTable Products tiene dos nuevas columnas Tmese un momento para actualizar la sentencia SELECT en el mtodo

GetProductsByCategoryID (categoryID). Si actualiza el SELECT GetProducts () usando sentencias JOINs, el diseador del DataSet no ser capaz de generar automticamente los mtodos para insertar, actualizar y eliminar datos de la base de datos usando el enfoque directo de base de datos. En su lugar tendr que crearlos manualmente al igual que hicimos anteriormente con el mtodo InsertProduct en este tutorial. Adems de la forma manual, tendr que proporcionar los valores de las propiedades InsertCommand, UpdateCommand y DeleteCommand si desea utilizar el enfoque de actualizacin por lotes. Agregar los TableAdapters restantes Hasta el momento solo hemos visto el trabajo con un solo TableAdapter para una sola tabla de la base de datos. Sin embargo la base de datos Northwind contiene varias tablas relacionadas con las cuales tendremos que trabajar en nuestra aplicacin web. Un DataSet tipiado puede contener muchos DataTables relacionados. Por lo tanto para completar nuestra DAL necesitamos agregar DataTables para las otras tablas que usaremos en estos tutoriales. Para agregar un nuevo TableAdapter al DataSet tipiado, abra el diseador del DataSet, haga clic derecho en el diseador y seleccione Agregar/ TableAdapter. Esto creara un nuevo objeto DataTable y TableAdapter e iremos a travs del asistente que vimos anteriormente en este tutorial.

Tmese unos minutos para crear los TableAdapters y los mtodos usando las siguientes consultas. Tenga en cuenta que las consultas en el TableAdapter incluyen las sub-consultas para agregar la categora de cada producto y el nombre del proveedor. Adems si usted ya ha continuado, ya ha agregado la clase TableAdapter Products y los mtodos GetProducts () y GetProductsByCategoryID (categoryID).

Products TableAdapter

GetProducts:

SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,UnitPrice, UnistInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID=Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID=Products.SupplierID) as SupplierName FROM Products

GetProductsByCategoryID:

SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products WHERE CategoryID = @CategoryID

GetProductsBySupplierID:
ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice,

SELECT

UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryNameFROM Categories WHERE Categories.CategoryID =Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products WHERE SupplierID = @SupplierID

GetProductsByProductID:
ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice,

SELECT

UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID =Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products WHERE ProductID = @ProductID

Categories TableAdapter

GetCategories:

SELECT CategoryID, CategoryName, Description FROM Categories

GetCategoryByCategoryID:

SELECT CategoryID, CategoryName, Description FROM Categories WHERE CategoryID = @CategoryID

Suppliers TableAdapter

GetSuppliers:

SELECT SupplierID, CompanyName, Address, City, Country, Phone FROM Suppliers

GetSupplersByCountry:

SELECT SupplierID, CompanyName, Address, City, Country, Phone FROM Suppliers WHERE Country = @Country

GetSupplierBySupplierID:

SELECT SupplierID, CompanyName, Address,

City, Country, Phone FROM Suppliers WHERE SupplierID = @SupplierID

Employees TableAdapter
GetEmployees:

SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees

GetEmployesByManager:

SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees WHERE ReportsTo = @ManagerID

GetEmployeeByEmployeeID:

SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees WHERE EmployeeID = @EmployeeID

Figura 31. El DataSet despus que se han agregado los cuatro DataTables Agregar cdigo personalizado a la DAL Los TableAdapters y los DataTables agregados al DataSet tipiado son expresados como un archivo XML de definicin de Esquemas (Northwnd.xsd). Usted puede ver esta informacin de esquema haciendo clic derecho sobre Northwind.xsd en el explorador de soluciones y seleccionando Ver cdigo. Esta informacin de esquema se traduce en cdigo Visual Basic o C# en tiempo de diseo o cuando se compila en tiempo de ejecucin (si es necesario), momento en el cual usted puede caminar a travs de l con el depurador. Para ver el cdigo generado automticamente vaya a la vista de clases y llegue hasta las clases TableAdapter o DataSet tipiado. Si usted no ve la vista de clases en la pantalla, vaya a men, ver y seleccinelo all, o presionando Ctrl+Shift+C. Desde la vista de clases usted puede ver las propiedades, mtodos y eventos de las clases TableAdapters y DataSets tipiados. Para ver el cdigo de un mtodo especifico, haga doble clic sobre el nombre del mtodo en la Vista de Clases o haga clic derecho sobre l y seleccione Ir a definicin.

Aunque el cdigo generado automticamente puede ser un gran ahorro de tiempo, a menudo el cdigo es muy genrico y debe ser personalizado para satisfacer necesidades nicas de una aplicacin. El riesgo de extender un cdigo generado automticamente, es que la herramienta que genero el cdigo podra decidir que es hora de reconstruir y sobrescribir en las personalizaciones. Con el nuevo concepto de clase parcial de .Net 2.0, es fcil dividir una clase en varios archivos. Esto nos permite aadir nuestros propios mtodos, propiedades y eventos para las clases generadas automticamente sin tener que preocuparnos de que Visual Studio sobrescriba en nuestras personalizaciones.

Figura 32. El archivo XML de definicin de esquemas (XSD) para el DataSet tipiado Northwind Para demostrar como personalizar nuestra DAL, vamos a agregar nuestro mtodo GetProducts () a la clase SuppliersRow. La clase SuppliersRow representa un nico registro de la tabla Suppliers, cada proveedor puede tener de cero a muchos productos, por lo cual GetProducts () devolver todos los productos de un proveedor especifico. Para realizar esto, creamos un nuevo archivo de clase en la carpeta App_Code llamado SuppliersRow.vb y agregamos el siguiente cdigo:
Imports NorthwindTableAdapters Partial Public Class Northwind Partial Public Class SuppliersRow

Public Function GetProducts() As Northwind.ProductsDataTable Dim productsAdapter As New ProductsTableAdapter Return productsAdapter.GetProductsBySupplierID(Me.SupplierID) End Function End Class End Class

Esta

clase

parcial

indica

al

compilador

que

cuando

construya

la

clase

Northwind.SuppliersRow incluya el mtodo GetProducts() que acabamos de definir. Si usted construye su proyecto y luego vuelve a la vista de clases, puede encontrar que el mtodo GetProducts() aparece ahora como un mtodo de Northwind.SuppliersRow. El mtodo GetProducts() ahora puede ser usado para enumerar el conjunto de productos de un proveedor en particular, como se muestra en el siguiente cdigo:
Dim suppliersAdapter As New NorthwindTableAdapters.SuppliersTableAdapter() Dim suppliers As Northwind.SuppliersDataTable = suppliersAdapter.GetSuppliers() For Each supplier As Northwind.SuppliersRow In suppliers Response.Write("Supplier: " & supplier.CompanyName) Response.Write("<ul>") Dim products As Northwind.ProductsDataTable = supplier.GetProducts() For Each product As Northwind.ProductsRow In products Response.Write("<li>" & product.ProductName & "</li>") Next Response.Write("</ul><p> </p>") Next

Figura 33. Inspeccione el cdigo generado automticamente seleccionado Ir a definicin desde la vista de clases Estos datos tambin pueden ser mostrados en cualquiera de los controles de datos web de Asp.Net. La siguiente pgina muestra un control GridView con dos campos:

Un BoundField que muestra el nombre de cada proveedor, y Un TemplateField que contiene un control BulletedList que esta enlazado a los resultados devueltos por el mtodo GetProducts () para cada proveedor.

Figura 34. El mtodo GetProducts () ahora es parte de la clase Northwind.SuppliersRow Veremos ms adelante como mostrar informes maestro/detalles en futuros tutoriales. Por ahora este ejemplo sirve para ilustrar el uso de un mtodo personalizado agregado a la clase Northwind.SuppliersRow.

SuppliersAndProducts.aspx
<%@ Page Language="VB" CodeFile="SuppliersAndProducts.aspx.vb" AutoEventWireup="true" Inherits="SuppliersAndProducts" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> <link href="Styles.css" rel="stylesheet" type="text/css" /> </head> <body> <form id="form1" runat="server"> <div> <h1> Suppliers and Their Products</h1> <p> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CssClass="DataWebControlStyle"> <HeaderStyle CssClass="HeaderStyle" />

<AlternatingRowStyle CssClass="AlternatingRowStyle" /> <Columns> <asp:BoundField DataField="CompanyName" HeaderText="Supplier" /> <asp:TemplateField HeaderText="Products"> <ItemTemplate> <asp:BulletedList ID="BulletedList1" runat="server" DataSource="<%#CType(CType(Container.DataItem, System.Data.DataRowView).Row,Northwind.SuppliersRow) .GetProducts() %>" DataTextField="ProductName"> </asp:BulletedList> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> </p> </div> </form> </body> </html>

SuppliersAndProducts.aspx.vb
Imports NorthwindTableAdapters Partial Class SuppliersAndProducts Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load Dim suppliersAdapter As New SuppliersTableAdapter GridView1.DataSource = suppliersAdapter.GetSuppliers() GridView1.DataBind() End Sub End Class

Figura 35. El nombre de la compaa proveedora es listado en la columna izquierda y sus productos en la derecha RESUMEN Al construir una aplicacin web, la creacin de la DAL debe ser uno de los primeros pasos, que se producen antes de empezar la capa de presentacin. Con Visual Studio la creacin de una DAL basada en DataSets tipiados es una tarea que puede llevarse a cabo en 10 o 15 minutos sin escribir una sola lnea de cdigo. Los siguientes tutoriales se basaran en esta DAL. En el siguiente tutorial vamos a definir una serie de reglas de negocio y la forma de ponerlas en prctica en una capa separada de lgica de negocios.

2. CREACIN DE UNA CAPA LGICA DE NEGOCIOS


En esta seccin veremos cmo centralizar las reglas de negocios en una capa lgica de negocios BLL que sirve como intermediario para el intercambio de datos entre la capa de presentacin y la DAL. Introduccin La capa de acceso a datos DAL creada en el primer tutorial, separa la lgica de datos de la capa de presentacin. Sin embargo aunque la DAL separa limpiamente los detalles de acceso a datos de la capa de presentacin, no hace cumplir las reglas de negocio que se puedan aplicar. Por ejemplo para nuestra aplicacin deseamos deshabilitar los campos CategoryID y SupplierID de la tabla Products para que no sean modificados cuando el campo Discontinued se establezca en 1, o podramos desear forzar reglas de antigedad, prohibiendo situaciones en las cuales un empleado es supervisado por alguien que fue contratado despus de l. Otro escenario comn es tal vez autorizar solo a usuarios de un rol en particular para que puedan eliminar productos o cambiar el valor de UnitPrice. En este tutorial veremos cmo centralizar estas reglas de negocio en una capa lgica de negocios BLL que sirve como intermediario para el intercambio de datos desde la capa de presentacin a la DAL. En una aplicacin del mundo real, la BLL debe ser implementada como un proyecto de Biblioteca de clases separadas, sin embargo para estos tutoriales implementaremos la BLL como una serie de clases en nuestra carpeta App_Code con el fin de simplificar la estructura del proyecto. La figura 1 muestra las relaciones de la arquitectura entre la capa de presentacin, la BLL y la DAL.

Figura 1. La BLL separa la capa de presentacin de la capa de acceso a datos e impone las reglas de negocio En lugar de crear clases separadas para implementar nuestra lgica de negocios BLL, podramos colocar alternativamente esta lgica directamente en un DataSet tipiado con clases parciales. Para un ejemplo de creacin y ampliacin de un DataSet tipiado refirase de nuevo al tutorial anterior. Paso 1: Creacin de clases BLL Nuestra BLL estar compuesta de cuatro clases, una para cada TableAdapter en la DAL, cada una de estas clases tendr mtodos para recuperar, insertar, actualizar y eliminar desde el respectivo TableAdapter en la DAL, aplicando las reglas de negocio adecuadas. Para separar ms limpiamente la DAL y las clases BLL relacionadas, crearemos dos subcarpetas DAL y BLL en nuestra carpeta App_Code. Simplemente haga clic en la carpeta App_Code en el explorador de soluciones y seleccione Nueva Carpeta. Despus de crear estas dos subcarpetas, mueva el DataSet tipiado creado en el primer tutorial a la subcarpeta DAL.

A continuacin cree cuatro archivos de clase en la subcarpeta BLL. Para realizar esto, haga clic derecho en la subcarpeta BLL y seleccione agregar nuevo elemento y escoja plantilla de clase. Nombre las cuatro clases ProductsBLL, CategoriesBLL, SuppliersBLL y EmployeesBLL.

Figura 2. Anadir cuatro clases a la carpeta App_Code Luego agregamos mtodos a cada una de las clases para que sencillamente envuelvan los mtodos definidos para los TableAdapters en el primer tutorial. Por ahora estos mtodos llamaran directamente en la DAL, regresaremos despus para agregar la lgica de negocios necesaria. Nota: Si est utilizando Visual Studio Standard Edition o superior (es decir no est utilizando Visual Web Developer), si lo desea puede disear sus clases de forma visual utilizando el diseador de clases. Consultemos el blog diseador de clases para obtener ms informacin sobre esta nueva funcin de Visual Studio. Para la clase ProductsBLL tenemos que aadir un total de siete mtodos:

Get Products (), devuelve todos los productos. GetProductsByProductId (productID), devuelve el producto con el ProductID
especificado.

GetProductsByCategoryID (categoryID), devuelve todos los productos de una


categora especfica.

GetProductsBySupplierID (supplierID), devuelve todos los productos de un


proveedor especfico.

AddProduct () UpdateProduct () DeleteProduct (productID), elimina el producto especificado de la base de datos.

ProductsBLL.vb
Imports NorthwindTableAdapters <System.ComponentModel.DataObject()> _ Public Class ProductsBLL Private _productsAdapter As ProductsTableAdapter = Nothing Protected ReadOnly Property Adapter() As ProductsTableAdapter Get If _productsAdapter Is Nothing Then _productsAdapter = New ProductsTableAdapter() End If Return _productsAdapter End Get End Property <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, True)> _ Public Function GetProducts() As Northwind.ProductsDataTable Return Adapter.GetProducts() End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductByProductID(ByVal productID As Integer) _ As Northwind.ProductsDataTable Return Adapter.GetProductByProductID(productID) End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _

Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _ As Northwind.ProductsDataTable Return Adapter.GetProductsByCategoryID(categoryID) End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsBySupplierID(ByVal supplierID As Integer) _ As Northwind.ProductsDataTable Return Adapter.GetProductsBySupplierID(supplierID) End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Insert, True)> _ Public Function AddProduct( _ productName As String, supplierID As Nullable(Of Integer), _ categoryID As Nullable(Of Integer), quantityPerUnit As String, _ unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _ unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _ discontinued As Boolean) _ As Boolean Dim products As New Northwind.ProductsDataTable() Dim product As Northwind.ProductsRow = products.NewProductsRow() product.ProductName = productName If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull()

Else product.QuantityPerUnit = quantityPerUnit End If If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If If Not unitsOnOrder.HasValue Then product.SetUnitsOnOrderNull() Else product.UnitsOnOrder = unitsOnOrder.Value End If If Not reorderLevel.HasValue Then product.SetReorderLevelNull() Else product.ReorderLevel = reorderLevel.Value End If product.Discontinued = discontinued products.AddProductsRow(product) Dim rowsAffected As Integer = Adapter.Update(products) Return rowsAffected = 1 End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateProduct(_ productName As String, supplierID As Nullable(Of Integer), _

categoryID As Nullable(Of Integer), quantityPerUnit As String, _ unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _ unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _ discontinued As Boolean, productID As Integer) _ As Boolean Dim products As Northwind.ProductsDataTable = _ Adapter.GetProductByProductID(productID) If products.Count = 0 Then Return False End If Dim product as Northwind.ProductsRow = products(0) product.ProductName = productName If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull() Else product.QuantityPerUnit = quantityPerUnit End If If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If

If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If If Not unitsOnOrder.HasValue Then product.SetUnitsOnOrderNull() Else product.UnitsOnOrder = unitsOnOrder.Value End If If Not reorderLevel.HasValue Then product.SetReorderLevelNull() Else product.ReorderLevel = reorderLevel.Value End If product.Discontinued = discontinued Dim rowsAffected As Integer = Adapter.Update(product) Return rowsAffected = 1 End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteProduct(ByVal productID As Integer) As Boolean Dim rowsAffected As Integer = Adapter.Delete(productID) Return rowsAffected = 1 End Function End Class

Los

mtodos

que

solo

devuelven

datos

GetProducts,

GetProductByProductID,

GetProductsByCategoryID y GetProductsBySupplierID son bastante sencillos ya que solamente llaman a la DAL. Aunque en algunos escenarios podra haber reglas de negocio que necesitan ser implementadas a este nivel (como las reglas de actualizacin basadas en el usuario actual o el rol al cual pertenece el usuario) simplemente dejaremos estos mtodos como estn. Para estos mtodos, la BLL solo sirve como una aproximacin a travs de la cual la capa de presentacin tiene acceso a los datos subyacentes de la base de datos.

Los mtodos AddProduct y UpdateProduct toman como parmetros los valores para los diversos campos del producto y agregan un nuevo producto o actualizan uno existente, respectivamente. Como muchas de las columnas de la tabla Products pueden aceptar valores nulos (CategoryID, SupplierID y UnitPrice, por nombrar algunos) estos parmetros de entrada para AddProduct y UpdateProduct se asignan a dichas columnas que usan tipos anulables. Los tipos anulables son nuevos en .Net 2.0 y proporcionan una tcnica para saber si un tipo de valor podra ser Nothing. Consulte el blog de Vick Paul La verdad acerca de los tipos anulables y VB y a la documentacin tcnica para la estructura anulable para mayor informacin. Los otros tres mtodos devuelven un valor booleano indicando si una fila fue insertada, actualizada o eliminada ya que la operacin podra no resultar en una fila afectada. Por ejemplo si el desarrollador llama a DeleteProduct pasando un ProductID para un producto que no existe, la sentencia DELETE emitida a la base de datos no tendr ningn efecto y por lo tanto el mtodo DeleteProduct devolver False. Tenga en cuenta que al agregar un nuevo producto o actualizar uno existente tomamos los valores de los campos del nuevo o modificado producto como una lista de escalares en lugar de aceptar una instancia ProductsRow. Este enfoque fue seleccionado, debido a que la clase ProductsRows deriva de la clase DataRows de ADO.Net la cual no tiene por defecto un constructor sin parmetros. Por lo tanto para crear una nueva instancia ProductsRow, primero debemos crear una instancia ProductsDataTable y luego invocamos su mtodo newProductRow (lo cual hacemos en AddProduct). Esta deficiencia sobresale cuando procedemos a insertar y actualizar productos utilizando los ObjectDataSource. En resumen el ObjectDataSource tratar de crear una instancia de los parmetros de entrada. Si el mtodo BLL espera una instancia ProductsRow, el ObjectDataSource tratara de crear una, pero fallara debido a la falta de un constructor predeterminado sin parmetros. Para obtener mayor informacin refirase a los siguientes dos foros: Actualizacin de ObjectDataSource con DataSets tipiados fuertemente y Problemas con los ObjectDataSource y los DataSet fuertemente tipiados. Luego en AddProduct y UpdateProduct, el cdigo crea una instancia ProductsRow y la rellena con los valores que acabamos de pasar. Cuando asignamos valores a las DataColumn de los DataRows varias validaciones a nivel de campo pueden ocurrir. Por lo tanto poner manualmente los valores pasados de nuevo en un DataRow lo que ayuda

a garantizar la validez de los datos que se pasan al mtodo BLL. Lamentablemente las clases DataRow fuertemente tipiadas generadas por Visual Studio no utilizan los tipos anulables. Por el contrario, para indicar que un valor de un Object DataColumn en un DataRow corresponder a un valor de base de datos Null, debemos usar el mtodo Set ColumnName Null (). En UpdateProducts primero cargamos el producto a actualizar usando

GetProductsByproductID (productID). Si bien esto puede parecer un viaje innecesario a la base de datos, este viaje extra vale la pena explorarlo en futuros tutoriales que exploran la concurrencia optimista. La concurrencia optimista es una tcnica para asegurar que dos usuarios que estn trabajando simultneamente sobre los mismos datos no sobrescriban accidentalmente uno de los cambios del otro. Tomar el registro completo hace ms fcil crear mtodos de actualizacin que solo modifican un subconjunto de las columnas del DataRow. Cuando exploremos la clase SuppliersBLL veremos un ejemplo. Finalmente tenga en cuenta que la clase ProductsBLL tiene el DataObjectAttribute aplicado a esta (la sintaxis [System.ComponentModel.DataObject] antes de la sentencia de clase creada a la parte superior del archivo) y los mtodos tienen atributos DataObjectMethodAttribute. El atributo DataObject marca la clase como un objeto adecuado para el enlace a un control ObjectDataSource, mientras que el DataObjectMethodAttribute indica el propsito del mtodo. Como veremos en futuros tutoriales el ObjectDataSource de Asp.net 2.0 hace ms fcil declarar acceso a datos desde una clase. Para ayudar a filtrar la lista de las posibles clases para enlazar en el asistente del ObjectDataSource, por defecto solo las clases marcadas como DataObjects se muestran en la lista desplegable del asistente. La clase ProductsBLL funcionara igual de bien sin estos atributos, pero aadirlos hace que sea ms fcil trabajar con el asistente del ObjectDataSource. Agregar las otras clases Con la clase ProductsBLL completa, aun necesitamos agregar las clases para trabajar con las categoras, empleados y proveedores. Tome un momento para crear las siguientes clases y mtodos usando los conceptos en el ejemplo anterior: CategoriesBLL.cs GetCategories()

GetCategoryByCategoryID (categoryID) SuppliersBLL.cs GetSuppliers() GetSupplierBySupplierID (supplierID) GetSuppliersByCountry (country) UpdateSupplierAddress (supplierID, adress, city, country) EmployeesBLL.cs GetEmployees() GetEmployeesByEmployeeID (employeeID) GetEmployeesByManager (managerID) El mtodo que vale la pena destacar es el mtodo UpdateSupplierAddress de la clase SuppliersBLL. Este mtodo ofrece una interfaz para actualizar solamente la informacin de la direccin del proveedor. Internamente este mtodo lee el objeto SupplierRow para el SupplierID especificado (usando GetSupplierBySupplierID), establece las propiedades relacionadas con la direccin y luego llama al mtodo Update del SupplierDataTable. El mtodo UpdateSupplierAddress es el siguiente:
<System.ComponentModel.DataObjectMethodAttribute_ (System.ComponentModel.DataObjectMethodType.Update, True)>_ Public Function UpdateSupplierAddress(ByVal supplierID As Integer,_ ByVal address As String, ByVal city As String, ByVal country As String)_ As Boolean Dim suppliers As Northwind.SuppliersDataTable = _ Adapter.GetSupplierBySupplierID(supplierID) If suppliers.Count = 0 Then Return False Else Dim supplier As Northwind.SuppliersRow = suppliers(0) If address Is Nothing Then supplier.SetAddressNull() Else supplier.Address = address End If

If city Is Nothing Then supplier.SetCityNull() Else supplier.City = city End If If country Is Nothing Then supplier.SetCountryNull() Else supplier.Country = country End If Dim rowsAffected As Integer = Adapter.Update(supplier) Return rowsAffected = 1 End If End Function

Refirase a la descarga de este artculo para la implementacin completa de las clases BLL. Paso 2: Acceda a los DataSets tipiados a travs de las clases BLL En el primer tutorial vimos ejemplos de cmo trabajar directamente con los DataSets tipiados mediante programacin, pero con la adicin de las clases BLL, la capa de presentacin debe trabajar en su lugar con la BLL. En el ejemplo Allproducts.aspx del primer tutorial, el ProductsTableAdapter fue utilizado para enlazar la lista de productos a un GridView, como se muestra en el siguiente cdigo:
Dim productsAdapter As New ProductsTableAdapter() GridView1.DataSource = productsAdapter.GetProducts() GridView1.DataBind()

Para usar las nuevas clases BLL, todo lo que necesitamos cambiar es la primera lnea del cdigo remplazando simplemente el objeto ProductsTableAdapter con el objeto ProductsBLL:
Dim productLogic As New ProductsBLL() GridView1.DataSource = productLogic.GetProducts() GridView1.DataBind()

Las clases BLL tambin pueden ser accedidas mediante declaracin (al igual que los DataSet tipiados) por medio del ObjectDataSource. Discutiremos el ObjectDataSource con ms detalles en los futuros tutoriales.

Figura 3. La lista de productos es mostrada en un GridView Paso 3: Agregar validacin a nivel de campo a las clases DataRow La verificacin a nivel de campo son comprobaciones a los valores de las propiedades de los objetos de negocio al insertar a actualizar. Algunas reglas de validacin a nivel de campo para los productos incluyen: El campo ProductName debe ser de 40 o menos caracteres de longitud. El campo QuantityPerUnit debe ser de 20 o menos caracteres de longitud. Los campos ProductID, ProductName y Discontinued son obligatorios, pero todos los dems son opcionales. Los campos UnitPrice, UnitsInStock, UnitsOnOrder y ReorderLevel deben ser iguales o mayores a cero. Estas reglas pueden y deben ser expresadas a nivel de base de datos. El lmite de caracteres en los campos ProductName y QuantityPerUnit son capturados por los tipos de datos de estas columnas en la tabla Products (nvarchar(40) y nvarchar(20)

respectivamente).Si los campos son obligatorios o opcionales son expresados si la columna de la tabla de la base de datos permite NULLs. Existen cuatro restricciones de comprobacin para asegurar que solo valores iguales o mayores a cero sean ingresados en las columnas UnitPrice, UnitsInStock, UnitsOnOrder o ReorderLevel. Adems de forzar estas reglas en la base de datos tambin deben aplicarse a nivel de DataSet. De hecho la longitud del campo y si un valor es obligatorio u opcional ya estn capturados para el conjunto de DataColumns de cada DataTable. Para ver la verificacin a nivel de campo proporcionada automticamente, vaya al diseador de DataSet, seleccione un campo de uno de los DataTables y luego vaya a la ventana propiedades. Como se muestra en la figura 4, el DataColumn QuantityPerUnit en el ProductsDataTable tiene una longitud mxima de 20 caracteres y permite valores NULLs. Si se intenta establecer la propiedad QuantityPerUnit del ProductsDataRow a un valor de cadena de ms de 20 caracteres de longitud se emitir un ArgumentException.

Figura 4. El DataColumn proporciona validacin bsica a nivel de campo Desafortunadamente no podemos especificar los controles de lmites, como que el valor UnitPrice debe ser mayor o igual a cero por medio de la ventana propiedades. Con el fin de proporcionar este tipo de validacin a nivel de campo necesitamos crear un controlador de eventos al evento ColumnChanging del DataTable. Como se menciono en el tutorial anterior los objetos DataSet, DataTable y DataRow creados por el DataSet tipiado puede ser extendido empleando clases parciales. Con esta tcnica podemos

crear un controlador de eventos ColumnChanging para la clase ProductsDataTable. Comenzaremos creando una clase en la carpeta App_Code llamada ProductsDataTable.ColumnChanging.vb.

Figura 5. Agregar una nueva clase a la carpeta App_Code Luego cree un controlador de eventos para el evento ColumnChanging que asegure que los valores de las columnas UnitPrice, UnitsInStock, UnitsOnOrder y ReorderLevel (si no son NULLs) sean mayores o iguales a cero. Si cualquier columna esta fuera de este rango se emitir un ArgumentException.

ProductsDataTable.ColumnChanging.vb
Imports System.data Partial Public Class Northwind Partial Public Class ProductsDataTable Public Overrides Sub BeginInit() AddHandler Me.ColumnChanging, AddressOf ValidateColumn End Sub Sub ValidateColumn(sender As Object, e As DataColumnChangeEventArgs) If e.Column.Equals(Me.UnitPriceColumn) Then If Not Convert.IsDBNull(e.ProposedValue) AndAlso _ CType(e.ProposedValue, Decimal) < 0 Then Throw New ArgumentException( _ "UnitPrice cannot be less than zero", "UnitPrice")

End If ElseIf e.Column.Equals(Me.UnitsInStockColumn) OrElse _ e.Column.Equals(Me.UnitsOnOrderColumn) OrElse _ e.Column.Equals(Me.ReorderLevelColumn) Then If Not Convert.IsDBNull(e.ProposedValue) AndAlso _ CType(e.ProposedValue, Short) < 0 Then Throw New ArgumentException(String.Format( _ "{0} cannot be less than zero", e.Column.ColumnName), _ e.Column.ColumnName) End If End If End Sub End Class End Class

Paso 4: Agregar reglas de negocio personalizadas a las clases BLL Adems de la validacin a nivel de campo, pueden haber reglas de negocio personalizadas de alto nivel que involucran diferentes entidades o conceptos no expresivos a nivel de una sola columna, tales como: Si un producto es descontinuado, su UnitPrice no se puede actualizar El pas de residencia de un empleado debe ser el mismo pas de residencia de su jefe Un producto no puede ser descontinuado si es el nico producto proporcionado por un proveedor Las clases BLL deben tener verificaciones para garantizar el cumplimiento de las reglas de negocio de la aplicacin. Estos controles pueden ser agregados directamente a los mtodos que los aplicaran. Imagine que nuestras reglas de negocio determinan que un producto no puede ser descontinuado si es el nico producto suministrado por el proveedor. Es decir si un producto X es el nico producto que obtenemos del proveedor Y, no podemos marcar X como descontinuado, sin embargo si el proveedor Y nos suministra tres productos A, B y C entonces podramos marcar todos y cada uno de estos productos como descontinuado. Es una regla de negocio extraa, pero las reglas de negocio y el sentido comn no siempre estn ligados!

Para

establecer esta regla

de negocio, el mtodo UpdateProducts empezara

estableciendo si Discontinued se estableci en s, de ser as llamara al mtodo GetProductsBySupplierID para determinar el nmero de productos comprados a este proveedor de productos. Si este es el nico producto que se compra a este proveedor se emitir una ApplicationException.
<System.ComponentModel.DataObjectMethodAttribute_ (System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateProduct( _ productName As String, supplierID As Nullable(Of Integer), _ categoryID As Nullable(Of Integer), quantityPerUnit As String, _ unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _ unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _ discontinued As Boolean, productID As Integer) _ As Boolean Dim products As Northwind.ProductsDataTable = _ Adapter.GetProductByProductID(productID) If products.Count = 0 Then Return False End If Dim product As Northwind.ProductsRow = products(0) If discontinued Then Dim productsBySupplier As Northwind.ProductsDataTable = _ Adapter.GetProductsBySupplierID(product.SupplierID) If productsBySupplier.Count = 1 Then Throw New ApplicationException( _ "You cannot mark a product as discontinued if it is " & _ "the only product purchased from a supplier") End If End If product.ProductName = productName

If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull() Else product.QuantityPerUnit = quantityPerUnit End If If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If If Not unitsOnOrder.HasValue Then product.SetUnitsOnOrderNull() Else product.UnitsOnOrder = unitsOnOrder.Value End If If Not reorderLevel.HasValue Then product.SetReorderLevelNull() Else product.ReorderLevel = reorderLevel.Value End If product.Discontinued = discontinued Dim rowsAffected As Integer = Adapter.Update(product) Return rowsAffected = 1

End Function

Respuesta a los errores de validacin en la capa de presentacin Al llamar a la BLL desde la capa de presentacin podemos decidir si manejar las excepciones que puedan ocurrir o dejarlas a Asp.Net (Lo que aumentara el evento Error del HttpAplication). Para manejar una excepcin cuando se trabaja con la BLL mediante programacin, podemos usar los bloques TryCatch, como se muestra en el siguiente ejemplo:
Dim productLogic As New ProductsBLL() Try productLogic.UpdateProduct("Scotts Tea", 1, 1, Nothing, _ -14, 10, Nothing, Nothing, False, 1) Catch ae As ArgumentException Response.Write("There was a problem: " & ae.Message) End Try

En los futuros tutoriales, exploraremos el controlador de excepciones generado desde la BLL cuando se emplea un control de datos web para insertar, eliminar o actualizar datos pueden ser manipuladas directamente en un controlador de eventos en lugar de tener que envolver el cdigo en bloques Try Catch. RESUMEN Una aplicacin bien diseada se hace a mano en distintas capas, cada una de ellas encierra un papel muy especial. En el primer tutorial de esta serie de artculos hemos creado una capa de acceso a datos usando DataSets tipiados, en este tutorial construiremos una capa lgica de negocios como una serie de clases en la carpeta App_Code de nuestra aplicacin que llame a nuestra DAL. La BLL implementala lgica a nivel de campo y a nivel de negocios de nuestra aplicacin. Adems de la opcin de crear una BLL separada como lo hicimos en este tutorial, otra opcin es ampliar los mtodos de los TableAdapters mediante el uso de clases parciales. Sin embargo, utilizar esta tcnica no nos permite remplazar los mtodos existentes ni separar tan limpiamente nuestra DAL y BLL como lo hemos hecho con el enfoque adoptado en este artculo.

3. PAGINAS MAESTRAS Y NAVEGACIN DEL SITIO


Una caracterstica comn de los sitios fciles de usar es que tienen una constante, en todo el diseo de las pginas del sitio y el sistema de navegacin. En este tutorial veremos cmo crear un aspecto coherente en todas las pginas para que pueda ser fcilmente actualizado. Introduccin Una caracterstica comn de los sitios web fciles de usar es que tienen un diseo de pgina coherente en todo el sitio y un sistema de navegacin. Asp.Net 2.0 introduce dos nuevas caractersticas que simplifican enormemente la implementacin de ambos, un diseo de pgina en todo el sitio y un sistema de navegacin: paginas maestras y el sistema de navegacin del sitio. Las pginas maestras permiten a los diseadores crear una plantilla en todo el sitio con regiones editables diseadas. Esta plantilla puede ser aplicada a todas las pginas en el sitio. Las pginas Asp.Net solo deben proporcionar el contenido para las regiones editables especficas de la pgina maestra, todas las otras marcas de la pgina principal son idnticas en todas las pginas Asp.Net que utilicen la pgina maestra. Este modelo permite a los desarrolladores definir y centralizar un diseo de pgina de todo el sitio, lo que hace ms fcil crear un aspecto coherente en todas las pginas para que puedan ser fcilmente actualizadas. El sistema de navegacin del sitio proporciona un mecanismo para que los desarrolladores de pginas definan un mapa del sitio y una API para que el mapa del sitio pueda ser consultado mediante programacin. Los nuevos controles web de navegacin son el Men, TreeView y SiteMapPath que hacen que sea fcil hacer de todo o parte del mapa del sitio un elemento de interfaz de navegacin comn de los usuarios. Usaremos el proveedor de navegacin del sitio predeterminado, lo que significa que el mapa del sitio se define como un archivo con formato XML. Para ilustrar estos conceptos y hacer que nuestros tutoriales de sitio web sean ms tiles, dedicaremos esta leccin a la definicin del diseo de pgina en todo el sitio, implementando un mapa del sitio y agregando una interfaz de navegacin para el usuario. Al finalizar este tutorial tendremos un diseo de sitio web pulido para construir nuestras pginas web de tutorial.

Figura 1. El resultado final de este tutorial Paso 1: Creacin de la pgina maestra El primer paso es crear la pgina principal del sitio. En este momento nuestro sitio solo se compone del DataSet tipiado (Northwind.aspx en la carpeta App_Code), las clases BLL (ProductsBLL.vb, CategoriesBLL.vb y as sucesivamente en la carpeta App_Code), el archivo de configuracin (Web.config) y un archivo de hoja de estilos (styles.css). Limpie las pginas que usamos en los tutoriales anteriores para las demostraciones de la DAL y la BLL ya que examinaremos estos ejemplos con mayor detalle en los futuros tutoriales.

Figura 2. Los archivos de nuestro proyecto Para crear una pgina principal, hacemos clic derecho sobre el nombre del proyecto en el explorador de soluciones y elija agregar un nuevo elemento. Luego, seleccione el tipo pgina maestra de la lista de plantillas y nmbrela Site.master.

Figura 3. Agregar una pgina principal a la pgina web Defina el diseo de las pginas de todo el sitio en la pgina maestra. Puede utilizar la vista diseo y agregar los controles web que necesite, o puede agregar el marcado manualmente en la vista cdigo fuente. En mi pgina maestra se utilizan hojas de estilo para el posicionamiento y estilos, con las configuraciones CSS definidas en el archivo externo Style.css. Si bien no podemos decir desde el marcado a partir de donde mostrar, las reglas CSS se definen de tal forma que el contenido de navegacin de los <div>s es una posicin absoluta que aparece a la izquierda y tiene un ancho fijo de 200 pixeles.

Site.master
<%@ Master Language="VB" AutoEventWireup="true" CodeFile="Site.master.vb" Inherits="Site" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Working with Data Tutorials</title> <link href="Styles.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="wrapper"> <form id="form1" runat="server"> <div id="header"> <span class="title">Working with Data Tutorials</span> <span class="breadcrumb"> TODO: Breadcrumb will go here...</span> </div> <div id="content"> <asp:contentplaceholder id="MainContent" runat="server"> <!-- Page-specific content will go here... --> </asp:contentplaceholder> </div> <div id="navigation"> TODO: Menu will go here... </div> </form> </div> </body> </html>

Una pgina principal define tanto el diseo esttico de las pginas como las regiones que pueden ser editadas por las pginas Asp.Net que utilicen la pagina principal. Estas regiones de contenido editable son indicadas por el control ContentPlaceHolder, el cual podemos ver dentro del contenido del <div>. Nuestra pgina principal tiene un solo ContentPlaceHolder ContentPlaceHolders. (MainContent), pero la pgina puede contener varios

Con el marcado de arriba, si cambiamos a vista de diseo nos muestra el diseo de la pgina maestra. Cualquiera de las pginas Asp.Net que utilicen esta pgina maestra, tendrn este diseo uniforme, con la posibilidad de especificar el marcado de la regin MainContent.

Figura 4. La pgina principal a travs de la vista diseo. Paso 2: Agregar una pgina web al sitio web Con la pagina principal definida, estamos listos para agregar las paginas Asp.Net al sitio web. Comencemos agregando Default.aspx, nuestra pgina de inicio del sitio web. Haga clic en el nombre del proyecto en el explorador de soluciones y seleccione Agregar nuevo elemento. Elija la opcin WebForm de la lista de plantillas y llame al archivo Default.aspx. Adems seleccione la casilla Seleccionar pgina maestra.

Figura 5. Agregar un nuevo formulario web y marcar la casilla Seleccionar la pagina maestra. Despus de hacer clic en el botn Aceptar, se nos pide que elija la pgina maestra que esta nueva pgina Asp.Net debe utilizar. Aunque usted puede tener varias pginas maestras en el proyecto, aqu solo tenemos una. Despus de seleccionar la pgina maestra, las nuevas pginas Asp.Net contendrn el siguiente marcado:

Default.aspx
<%@ Page Language="VB" MasterPageFile="/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" Title="Untitled Page" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server"> </asp:Content>

Figura 6. Seleccione la pgina principal que esta pgina Asp.Net debe utilizar En la directiva @page hay una referencia al archivo usado de la pagina maestra (MasterPageFile=/Site.Master) y en el marcado de las paginas Asp.Net un control Content para cada uno de los controles ContentPlaceHolder definidos en la pgina principal, con el ContentPlaceholderID asignamos el control Content a un ContentPlaceHolder especifico. El control Content es donde se coloca el marcado que deseamos que aparezca en el ContentPlaceHolder correspondiente. Establezca el atributo Title de la directiva @page en Home y agregue algn contenido de bienvenida al control Content:

Default.aspx
<%@ Page Language="VB" MasterPageFile="/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" Title="Home" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server"> <h1>Welcome to the Working with Data Tutorial Site</h1> <p>This site is being built as part of a set of tutorials that illustrate some of the new data access and databinding features in ASP.NET 2.0 and Visual Web Developer.</p> <p>Over time, it will include a host of samples that demonstrate:</p>

<ul> <li>Building a DAL (data access layer),</li> <li>Using strongly typed TableAdapters and DataTables</li> <li>Master-Detail reports</li> <li>Filtering</li> <li>Paging,</li> <li>Two-way databinding,</li> <li>Editing,</li> <li>Deleting,</li> <li>Inserting,</li> <li>Hierarchical data browsing,</li> <li>Hierarchical drill-down,</li> <li>Optimistic concurrency,</li> <li>And more!</li> </ul> </asp:Content>

El atributo Title de la directiva @Page nos permite establecer el titulo de la pgina Asp.Net, aunque el elemento Title es definido en la pgina maestra. Tambin puede establecer el titulo mediante programacin utilizando Page.Title. Tambin tenga en cuenta que las referencias de la pgina maestra a las hojas de estilo (styles.css) son actualizadas automticamente para que funcionen en cualquier pagina Asp.Net, sin importar si el directorio de la pgina Asp.Net est o no relacionado con la pagina principal. Cambiando a la vista de diseo, podremos ver como se ve la pgina en un navegador. Tenga en cuenta que en la vista diseo de la pgina Asp.Net solamente se pueden editar las regiones de contenido editable, las dems regiones no editables a diferencia del ContentPlaceHolder de la pgina maestra aparecen en gris.

Figura 7. La vista diseo de la pgina Asp.Net muestra tanto regiones editables como no editables Cuando la pgina Default.aspx es visitada desde un navegador, el motor Asp.Net combina automticamente el contenido de pgina de la pgina maestra y el contenido de la pgina Asp.Net y hace que el contenido se fusione en un HTML final que es enviado al navegador. Cuando el contenido de la pgina Asp.Net es actualizado, todas las pginas que usan esta pgina maestra combinaran nuevamente su contenido con el contenido de la pgina maestra, la prxima vez que sean solicitadas. En resumen el modelo de la pgina maestra, permite definir una plantilla de diseo nica (la pgina maestra) cuyos cambios se reflejan inmediatamente en todo el sitio. Agregar las paginas Asp.Net adicionales para nuestro sitio web Tommonos un momento para agregar ms paginas Asp.Net en el sitio que eventualmente almacenaran las demostraciones de varios informes. Habr ms de 35 demostraciones en total por lo que en lugar de crear todas las pginas, crearemos las primeras. Dado tambin que habr muchas categoras de demostraciones, para gestionar mejor las demostraciones agregamos una carpeta para cada una de las categoras. Por ahora aadimos las primeras tres carpetas: BasicReporting

Filtering CustomFormatting

Por ltimo agregamos nuevos archivos como se muestra en el explorador de soluciones de la figura 8. Al agregar cada uno de los archivos, no olvide seleccionar la casilla Seleccionar pgina maestra.

Figura 8. Agregar los siguientes archivos Paso 2: Creacin de un mapa del sitio Uno de los retos de gestionar un sitio web compuesto por un puado de pginas, es ofrecer un mecanismo sencillo para que los usuarios puedan navegar por el sitio. Para empezar se debe definir la estructura de navegacin del sitio. Luego esta estructura debe ser traducida en elementos de navegacin de interfaz de usuario como los mens o rutas de navegacin. Finalmente este proceso debe ser mantenido y actualizado para paginas nuevas agregadas al sitio y eliminaciones de las existentes. Antes de Asp.Net 2.0 los desarrolladores creaban su propia estructura de navegacin del sitio, la

mantenan y trasladaban esto en elementos de interfaz de navegacin para el usuario. Sin embargo con Asp.Net 2.0, los desarrolladores pueden usar un sistema de navegacin del sitio predeterminado y de uso flexible. El sistema de navegacin del sitio de Asp.Net 2.0 proporciona un medio para que el desarrollador defina un mapa del sitio y luego acceda a esta informacin a travs de la programacin de una API. Asp.Net 2.0 incluye un proveedor de mapas de sitio que espera los datos del mapa del sitio para almacenarlos en un archivo XML formateado de una manera particular. Como el sistema de navegacin del sitio est basado en el modelo del proveedor, este puede ser extendido para soportar formas alternativas para socializacin de informacin del mapa del sitio. El artculo de Jeff Prosise Que est esperando el proveedor del mapa del sitio muestra cmo crear un proveedor del mapa del sitio que almacena el mapa del sitio en una base de datos SQL Server, otra opcin es crear un proveedor de mapa del sitio basado en la estructura del sistema de archivos. Para este tutorial sin embargo usaremos el proveedor de mapa del sitio

predeterminado que se incluye con Asp.Net 2.0. Para crear el mapa del sitio, haga clic sobre el nombre del proyecto en el explorador de soluciones y escoja agregar nuevo elemento, luego seleccione la opcin Mapa del sitio. Deje el nombre en Web.SiteMap y haga clic en el botn Aceptar.

Figura 9. Aadir un mapa del sitio a su proyecto

El archivo de mapa del sitio es un archivo XML. Tenga en cuenta que Visual Studio proporciona Intellisense para la estructura del mapa del sitio. El archivo de mapa del sitio debe tener el nodo <SiteMap> como nodo del nodo raz, el cual debe contener un elemento secundario <SiteMapNode>. El primer elemento del <SiteMapNode> puede contener un nmero arbitrario de elementos descendientes <SiteMapNode>. Defina el mapa del sitio para imitar la estructura del sistema de archivos. Es decir aada un elemento <SiteMapNode> para cada una de las tres carpetas y elementos <SiteMapNode> hijos para cada una de las pginas de Asp.Net de esas carpetas, as:

WebSite.map
<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode url="/Default.aspx" title="Home" description="Home"> <siteMapNode title="Basic Reporting" url="/BasicReporting/Default.aspx" description="Basic Reporting Samples"> <siteMapNode url="/BasicReporting/SimpleDisplay.aspx" title="Simple Display" description="Displays the complete contents of a database table." /> <siteMapNode url="/BasicReporting/DeclarativeParams.aspx" title="Declarative Parameters" description="Displays a subset of the contents of a database table using parameters." /> <siteMapNode url="/BasicReporting/ProgrammaticParams.aspx" title="Setting Parameter Values" description="Shows how to set parameter values programmatically." /> </siteMapNode> <siteMapNode title="Filtering Reports" url="/Filtering/Default.aspx" description="Samples of Reports that Support Filtering"> <siteMapNode url="/Filtering/FilterByDropDownList.aspx" title="Filter by Drop-Down List" description="Filter results using a drop-down list." /> <siteMapNode url="/Filtering/MasterDetailsDetails.aspx" title="Master-Details-Details" description="Filter results two levels down." />

<siteMapNode url="/Filtering/DetailsBySelecting.aspx" title="Details of Selected Row" description="Show detail results for a selected item in a GridView." /> </siteMapNode> <siteMapNode title="Customized Formatting" url="/CustomFormatting/Default.aspx" description="Samples of Reports Whose Formats are Customized"> <siteMapNode url="/CustomFormatting/CustomColors.aspx" title="Format Colors" description="Format the grid s colors based on the underlying data." /> <siteMapNode url="/CustomFormatting/GridViewTemplateField.aspx" title="Custom Content in a GridView" description="Shows using the TemplateField to customize the contents of a field in a GridView." /> <siteMapNode url="/CustomFormatting/DetailsViewTemplateField.aspx" title="Custom Content in a DetailsView" description="Shows using the TemplateField to customize the contents of a field in a DetailsView." /> <siteMapNode url="/CustomFormatting/FormView.aspx" title="Custom Content in a FormView" description="Illustrates using a FormView for a highly customized view." /> <siteMapNode url="/CustomFormatting/SummaryDataInFooter.aspx" title="Summary Data in Footer" description="Display summary data in the grids footer." /> </siteMapNode> </siteMapNode>
</siteMap>

El mapa del sitio define la estructura de navegacin del sitio web, la cual es una jerarqua del sitio. que define las diferentes secciones de un sitio. Cada elemento <SiteMapNode> en Web.SiteMap representa una seccin en la estructura de navegacin

Figura 10. El mapa del sitio representa una estructura de navegacin jerrquica Asp.Net expone la estructura del mapa del sitio por medio de la clase SiteMap del .Net Framework. Esta clase tiene una propiedad CurrentNode, que devuelve la informacin sobre la seccin que el usuario se encuentra visitando, la propiedad RootNode devuelve la raz del mapa del sitio (Home, en nuestro mapa del sitio). Tanto las propiedades CurrentNode y RootNode devuelven instancias SiteMapNode, las cuales tienen propiedades como ParentNode, ChildNodes, NextSibling, PreviousSibling y as sucesivamente, los cuales nos permiten navegar por la jerarqua del mapa del sitio. Paso 3: Mostrar un men basado en el mapa del sitio Acceder a los datos en Asp.Net 2.0 se puede lograr mediante programacin al igual que en Asp.Net 1.x, o mediante declaracin a travs de los nuevos controles de origen de datos. Existen varios controles de origen de datos preinstalados como el control SqlDataSource, para acceder a datos de bases relacionales, el control ObjectDataSource, para acceder a toda clase de datos y otros. Incluso usted puede crear sus propios objetos de origen de datos personalizados. Los controles de origen de datos sirven como un proxy entre tu pagina Asp.Net y los datos subyacentes. Con el fin de mostrar los valores devueltos por los controles de origen de datos, por lo general agregamos un control web y lo enlazamos a un control de origen de datos. Para enlazar un control web con un control de origen de datos, basta con establecer la propiedad DataSourceID del control web con el valor de la propiedad ID del control de origen de datos. Para ayudar con el trabajo de los datos del mapa del sitio, Asp.Net incluye el control SiteMapDataSource, el cual nos permite enlazar un control web con nuestro mapa del sitio. Dos controles web Treeview y Men se utilizan comnmente para proporcionar una interfaz de navegacin al usuario. Para enlazar los datos del mapa del sitio a uno

de estos controles, solo tenemos que aadir un SiteMapDataSource a la pgina, junto con un control Treeview o Men cuya propiedad DataSourceID se establece acordemente. Por ejemplo, podramos agregar un control men a la pgina a travs del siguiente marcado:
<div id="navigation"> <asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1"> </asp:Menu> <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" /> </div>

Para un grado de control sobre el HTML emitido, podemos enlazar al control SiteMapDataSource al control Repeater, as:
<div id="navigation"> <ul> <li><asp:HyperLink runat="server" ID="lnkHome" NavigateUrl="/Default.aspx">Home</asp:HyperLink></li> <asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1"> <ItemTemplate> <li> <asp:HyperLink runat="server" NavigateUrl='<%# Eval("Url") %>'> <%# Eval("Title") %></asp:HyperLink> </li> </ItemTemplate> </asp:Repeater> </ul> <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" ShowStartingNode="false" /> </div>

El control SiteMapDataSource devuelve la jerarqua del mapa del sitio, un nivel a la vez, empezando por el nodo raz del mapa del sitio (Home, en nuestro mapa del sitio),

luego el siguiente nivel (BasicReporting, Filtering Reports y Customized Formatting) y as sucesivamente. Cuando enlazamos el SiteMapDataSource al Repeater, este devuelve el primer nivel y crea instancias ItemTemplate para cada instancia SiteMapNode en ese nivel. . Para acceder a una propiedad particular del SiteMapNode, podemos usar Eval (propertyname), que es la forma en que recibimos las propiedades Url y Title de cada SiteMapNode para el control Hiperlink. En el ejemplo anterior el Repeater tendr el siguiente marcado:
<li> <a href="/Code/BasicReporting/Default.aspx">Basic Reporting</a> </li> <li> <a href="/Code/Filtering/Default.aspx">Filtering Reports</a> </li> <li> <a href="/Code/CustomFormatting/Default.aspx"> Customized Formatting</a> </li>

Estos nodos del mapa del sitio (BasicReporting, Filtering Reports y Customized Formatting) constituyen el segundo nivel del mapa del sitio que tenemos, no el primero. Esto se debe a que la propiedad ShowStartingNode del SiteMapDataSource, est establecida en Falso, haciendo que el SiteMapDataSource evite el nodo raz del mapa del sitio y en su lugar comienza devolviendo el segundo nivel en la jerarqua del mapa del sitio. Para mostrar los hijos de los SiteMapNode BasicReporting, Filtering Reports y Customized Formatting, podemos aadir otro Repeater para el ItemTemplate del Repeater inicial. Este segundo Repeater ser enlazado a la propiedad ChildNodes de la instancia SiteMapNode, as:
<asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1"> <ItemTemplate> <li> <asp:HyperLink runat="server" NavigateUrl='<%# Eval("Url") %>'>

<%# Eval("Title") %></asp:HyperLink> <asp:Repeater runat="server" DataSource="<%# CType(Container.DataItem, SiteMapNode).ChildNodes %>"> <HeaderTemplate> <ul> </HeaderTemplate> <ItemTemplate> <li> <asp:HyperLink runat="server" NavigateUrl='<%# Eval("Url") %>'> <%# Eval("Title") %></asp:HyperLink> </li> </ItemTemplate> <FooterTemplate> </ul> </FooterTemplate> </asp:Repeater> </li> </ItemTemplate> </asp:Repeater>

Estos dos Repeater resultan en el siguiente marcado (algunas marcas se han quitado por cuestiones de brevedad)
<li> <a href="/Code/BasicReporting/Default.aspx">Basic Reporting</a> <ul> <li> <a href="/Code/BasicReporting/SimpleDisplay.aspx"> Simple Display</a> </li> <li> <a href="/Code/BasicReporting/DeclarativeParams.aspx"> Declarative Parameters</a> </li>

<li> <a href="/Code/BasicReporting/ProgrammaticParams.aspx"> Setting Parameter Values</a> </li> </ul> </li> <li> <a href="/Code/Filtering/Default.aspx">Filtering Reports</a> ... </li> <li> <a href="/Code/CustomFormatting/Default.aspx"> Customized Formatting</a> ... </li>

Usamos estilos CSS escogidos del libro de Rachel Andrew La antologa CSS: 101 consejos esenciales, trucos y hacks, los elementos <ul> y <li> se denominan de tal forma que el marcado produce la salida visual mostrada en la Figura 11.

Figura 11. Un men compuesto por dos Repeaters y algunos estilos CSS Este men se encuentra en la pgina principal y es enlazado al mapa del sitio definido en Web.SiteMap, lo que significa que cualquier cambio en el mapa del sitio se reflejar inmediatamente en todas las pginas que utilicen la pgina maestra Site.master. Desactivacin del ViewState Todos los controles Asp.Net pueden opcionalmente mantener su estado con el ViewState, el cual es serializado con un campo del formulario escondido en el cdigo HTML presentado. El ViewState es usado por los controles para recordar su estado cambiado mediante programacin a travs de las devoluciones de datos, como los datos enlazados a un control web. Si bien el ViewState permite que la informacin sea recordada a travs de las devoluciones de datos, este incrementa el tamao del marcado que debe ser enviado al cliente y puede ocasionar un serio crecimiento, si la pagina no es monitoreada de cerca. En los controles de datos web especialmente en los GridView es especialmente notorio que se aaden decenas de kilobytes extras de

marcado en una pgina. Si bien este aumento puede ser insignificante para los usuarios de banda ancha o de una intranet, el ViewState puede agregar varios segundos de ida y vuelta para los usuarios de dial manual. Para ver el impacto de ViewState visite una pgina en un navegador y luego observe el cdigo fuente enviado por la pagina web (en Internet Explorer, vaya a men ver y seleccione la opcin fuente). Usted tambin puede activar el seguimiento de pagina (page tracing) para ver la asignacin del ViewState usada por cada uno de los controles de la pgina. La informacin del ViewState es serializada en un campo oculto del formulario llamado _VIEWSTATE, situado en un elemento <div> inmediatamente despus de la apertura de las etiquetas <form>. El ViewState es solamente mantenido cuando hay un WebForm utilizndolo, si su pgina Asp.Net no incluye un <form runat=server> en su sintaxis declarativa, no habr un campo de formulario oculto _VIEWSTATE en el marcado que se representa. El campo del formulario _VIEWSTATE generado por la pgina maestra agrega aproximadamente 1800 bytes de marcado en la pgina. Este incremento extra se debe principalmente al control Repeater, ya que el contenido del control SiteMapDataSource se conserva en el ViewState. Mientras que un extra de 1800 bytes puede no parecer mucho para emocionarse, cuando se utiliza un GridView con muchos campos y registros, el ViewState puede aumentar en un factor de 10 o ms. El ViewState puede ser deshabilitado a nivel de pgina o de control, mediante el establecimiento de la propiedad EnableViewState en Falso, lo que reduce el tamao del marcado que se representa. Dado que el ViewState de un control de datos web mantiene los datos enlazados a los controles de datos web por medio de las devoluciones de datos, al desactivar el ViewState de un control de datos web, los datos deben estar enlazados en todas y cada uno de las devoluciones de datos. En la versin 1.x de Asp.Net esta responsabilidad recay sobre los hombros de los desarrolladores de pginas, con Asp.Net 2.0, sin embargo los datos de los controles web son nuevamente enlazados a su control de origen de datos, en cada devolucin de datos si es necesario. Para reducir el ViewState de la pgina, establecemos la propiedad EnableViewState de los controles Repeater en Falso. Esto puede realizarse a travs de la ventana

propiedades en el diseador o mediante declaracin en la vista cdigo fuente. Despus de hacer este cambio el marcado declarativo del Repeater ser similar a este:
<asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1" EnableViewState="False"> <ItemTemplate> ... <i>ItemTemplate contents omitted for brevity</i> ... </ItemTemplate> </asp:Repeater>

Despus de este cambio el tamao de la pagina presentada se redujo a solo 52 bytes, un 97% de ahorro en el tamao del ViewState. A lo largo de los tutoriales de esta serie vamos a desactivar de forma predeterminada el ViewState de los controles de datos web con el fin de reducir el tamao del marcado representado. En la mayora de los ejemplos la propiedad EnableViewState se establece en falso y no se menciona el hecho. La nica vez que el ViewState ser discutido es en los escenarios donde debe estar habilitado para que los controles de datos web puedan proporcionar la funcionalidad esperada. Paso 4: Agregar ruta de navegacin Para completar la pgina principal, vamos a agregar un elemento de ruta de navegacin como interfaz de usuario para cada pgina. La ruta de navegacin muestra rpidamente a los usuarios su posicin actual dentro de la jerarqua del sitio. Agregar una ruta de navegacin en Asp.Net 2.0 es fcil, solo agregamos un control SiteMapPath a la pagina, no necesita cdigo. Para nuestro sitio, agregar este control a la cabecera <div>:
<span class="breadcrumb"> <asp:SiteMapPath ID="SiteMapPath1" runat="server"> </asp:SiteMapPath> </span>

La ruta de navegacin muestra la pgina actual en la que se encuentra el usuario en la jerarqua del mapa del sitio, as como los nodos del mapa del sitio antecesores, hasta llegar a la raz (Home, en el mapa del sitio)

Figura 12. La ruta de navegacin muestra la pgina actual y sus antecesoras en el mapa del sitio Paso 5: Aadir la pgina por defecto para cada seccin Los tutoriales de nuestro sitio web, estn divididos en varias categoras, BasicReporting, Filtering Reports, Customized Formatting y as sucesivamente, con un carpeta para cada categora y los tutoriales correspondientes en las paginas Asp.Net dentro de esa carpeta. Cada carpeta tiene una pgina default.aspx. En esta pgina mostraremos todos los tutoriales para la seccin actual. Es decir en la carpeta BasicReporting, la pgina Defult.aspx tendr vnculos con SimpleDisplay.aspx, DeclarativeParams.aspx y ProgramaticParams.aspx. Aqu una vez ms, podemos utilizar la clase SiteMap y un control de datos web para mostrar esta informacin basada en el mapa del sitio definido por el Web.SiteMap. Mostraremos nuevamente una lista desordenada con un Repeater, pero esta vez vamos a mostrar el titulo y la descripcin de los tutoriales. Como el marcado y el cdigo necesitan ser repetidos para cada pgina Default.aspx, podemos encapsular esta interfaz lgica de usuario en un control de usuario. Creamos una carpeta en nuestro sitio web llamada UserControls y le aadimos un nuevo elemento de tipo control de usuario web llamado SectionLevelTutorialListing.ascx y agregamos el siguiente marcado:

Figura 13. Agregar un nuevo control de usuario web a la carpeta UserControls

SectionLevelTutorialListing.ascx
<%@ Control Language="VB" AutoEventWireup="true" CodeFile="SectionLevelTutorialListing.ascx.vb" Inherits="UserControls_SectionLevelTutorialListing" %> <asp:Repeater ID="TutorialList" runat="server" EnableViewState="False"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate> <li><asp:HyperLink runat="server" NavigateUrl='<%# Eval("Url") %>' Text='<%# Eval("Title") %>'></asp:HyperLink> - <%# Eval("Description") %></li> </ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>

SectionLevelTutorialListing.ascx.vb
Partial Class UserControls_SectionLevelTutorialListing Inherits UserControl Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load If SiteMap.CurrentNode IsNot Nothing Then TutorialList.DataSource = SiteMap.CurrentNode.ChildNodes TutorialList.DataBind() End If End Sub End Class

En el ejemplo anterior del Repeater, enlazamos los datos del SiteMap al Repeater mediante declaracin, sin embargo el control de usuario SectionLevelTutorialListing se realiz mediante programacin. En el controlador de eventos Page_Load se realiza una verificacin para asegurarse de que las Url de las pginas se asignan a un nodo del mapa del sitio. Si este control de usuario se utiliza en una pgina que no tiene su correspondiente entrada <SiteMapNode>, entonces SiteMap.CurrentNode devolver Nothing y ningn dato se enlazar al Repeater. Suponiendo que tenemos un CurrentNode, enlazamos su coleccin ChildNodes al Repeater. Como el mapa del sitio

est configurado de tal manera que la pagina Default.aspx de cada seccin es el nodo primario de todos los tutoriales de esa seccin, el cdigo mostrara vnculos y descripciones de todos los tutoriales de la seccin, como se muestra en la pantalla que aparece a continuacin. Una vez que se haya creado el Repeater, abra las paginas Default.aspx en cada una de las carpetas, vaya a vista diseo y basta con solo arrastrar el control de usuario desde el explorador de soluciones a la superficie de diseo en la que deseamos aparezca la lista de los tutoriales.

Figura 14. Se ha aadido un control de usuario a Default.aspx

Figura 15. Los Tutoriales de reportes Bsicos son mostrados RESUMEN Con el mapa del sitio definido y la pgina principal completa, tenemos un diseo de pgina coherente y un esquema de navegacin para nuestros tutoriales de datos relacionados. Independientemente del nmero de pginas que aadimos a nuestro sitio, la actualizacin del diseo de pgina o la informacin de navegacin del sitio es un proceso de trabajo simple y rpido debido a que la informacin est centralizada. En concreto la informacin del diseo de pgina se define en la pgina maestra Site.master y el mapa del sitio en Web.SiteMap. No tuvimos necesidad de escribir ningn cdigo para lograr este gran diseo de pgina del sitio y el esquema de navegacin y nos reservamos la compatibilidad completa con el diseador de Visual Studio. Una vez completada la capa de acceso a datos y la capa lgica de negocios, teniendo un diseo de pgina coherente y la navegacin del sitio definida, estamos listos para comenzar a explorar los enfoques comunes de informacin. En los prximos tres tutoriales veremos tareas de reporte bsico mostrando datos devueltos desde la BLL en los controles GridView, FormView y DetailsView.

INFORMES BSICOS

4. VISUALIZACIN DE DATOS CON EL OBJECTDATASOURCE


En este tutorial veremos el control ObjectDataSource. Usando este control podemos enlazar los datos devueltos desde la BLL creada en el tutorial anterior, sin necesidad de tener que escribir ni una lnea de cdigo. Introduccin Con nuestra arquitectura de aplicacin y el diseo de pgina web completos, estamos listos para empezar a explorar la manera de realizar una cantidad de tareas relacionadas comnmente con los datos y su presentacin. En los tutoriales anteriores vimos la manera de enlazar datos mediante programacin desde la DAL y la BLL a un control de datos web en una pgina Asp.Net. Esta sintaxis asigna la propiedad DataSource del control de datos web a los datos para mostrarlos y luego llama al mtodo DataBind () del control, este fue el enfoque utilizado en las aplicaciones Asp.Net 1.x y puede seguir utilizndose en Asp.Net 2.0. Sin embargo, los nuevos controles de origen de datos de Asp.Net 2.0 ofrecen una manera declarativa para trabajar con datos. Usando estos controles podemos enlazar los datos devueltos desde la capa BLL creada en el tutorial anterior, sin tener que escribir ni una lnea de cdigo. Asp.net 2.0 incluye cinco controles de origen de datos preinstalados, SqlDataSource, AccessDataSource, ObjectDataSource XmlDataSource y SiteMapDataSource, aunque si es necesario, usted puede construir sus propios controles de origen de datos personalizados. Puesto que ya hemos desarrollado una estructura de aplicacin, usaremos el ObjectDataSource contra nuestras clases BLL.

Figura 1. Asp.net 2.0 incluye cinco controles de origen de datos preinstalados El ObjectDataSource funciona como un proxy para trabajar con algn otro objeto. Para configurar el ObjectDataSource especificamos el objeto subyacente y como sus mtodos se asignaran a los mtodos Select, Update, Insert y Delete del el ObjectDataSource. Una vez que hemos especificado el objeto subyacente y sus mtodos se han asignado a los del ObjectDataSource, podemos enlazar ObjectDataSource a un control de datos Web. Asp.Net viene con muchos controles de datos web, incluyendo los controles GridView, DetailsView, RadioButtonList y DropDownList entre otros. Durante el ciclo de vida de la pgina, los controles de datos web podran necesitar acceder a sus datos enlazados, lo cual se realiza invocando el mtodo Select de su ObjectDataSource, si el control de datos web soporta llamadas de insercin actualizacin y eliminacin, las llamadas pueden hacerse a los mtodos Insert, Delete y Update de su ObjectDataSource. Estas llamadas luego se enrutan por el ObjectDataSource a los correspondientes mtodos del objeto subyacente, como se ilustra en el siguiente diagrama.

Figura 2. El ObjectDataSource sirve como proxy Aunque el ObjectDataSource se puede utilizar para invocar los mtodos para insertar, actualizar y eliminar datos, nos enfocaremos en la devolucin de datos, en tutoriales futuros exploraremos el uso del ObjectDataSource y los controles de datos web para modificar datos. Paso 1: Agregar y configurar el control ObjectDataSource Comenzamos abriendo la pgina SimpleDisplay.aspx de la carpeta BasicReporting, cambie a la vista diseo y luego arrastre un control ObjectDataSource desde la caja de herramientas hasta la superficie de diseo de la pgina. El ObjectDataSource aparece como un cuadro gris en la superficie de diseo, ya que no produce ningn marcado; este simplemente accede a los datos invocando un mtodo desde un objeto especifico. Los datos devueltos por un ObjectDataSource pueden ser mostrados por un control de datos web, como los controles GridView, FormView, DetailsView y as sucesivamente. Nota: De forma alternativa, podemos agregar los controles de datos web a la pgina y luego desde la etiqueta inteligente, elija la opcin <Nuevo origen de datos> de la lista desplegable. Para especificar el objeto subyacente del ObjectDataSource y como los mtodos de ese objeto se asignan a los del ObjectDataSource, haga clic en el enlace configurar origen de datos desde la etiqueta del ObjectDataSource.

Figura 3. Haga clic en el enlace Configurar Origen de datos desde la etiqueta inteligente El resultado ser el asistente de configuracin de origen de datos. Primero debemos especificar el objeto con el cual trabaja el ObjectDataSource. Si la opcin Mo strar solo componentes de datos esta activada, la lista desplegable mostrada en esta pantalla, solo mostrar aquellos objetos que han sido decorados con el atributo DataObject. Actualmente la lista incluye los TableAdapters en el DataSet tipiado y las clases BLL que hemos creado en el tutorial anterior. Si usted olvido agregar el atributo DataObject a la capa lgica de negocios, entonces no podr verlas en la lista. En este caso, desactive la casilla Mostrar solo componentes de datos para ver todos los objetos, lo cual incluye las clases BLL (junto con las otras clases en el DataSet tipiado, los DataTables, DataRows y as sucesivamente). En la primera pantalla, seleccione la clase ProductsBLL del la lista desplegable y haga clic en siguiente.

Figura 4. Especificar el objeto para usar con el control ObjectDataSource

La siguiente pantalla del asistente le pide que seleccione el mtodo que el ObjectDataSource debe invocar. El cuadro combinado muestra la lista de los mtodos que devuelven datos en el objeto seleccionado en la pantalla anterior. Aqu vemos GetProductByProductId, GetProducts, GetProductsByCategoryID y GetProductsBySupplierID. Seleccione el mtodo GetProducts de la lista desplegable y haga clic en Finalizar (Si ha agregado DataObjectMethodAttribute a los mtodos de la ProductsBLL, como se muestra en el tutorial anterior, esta opcin estar seleccionada por defecto).

Figura 5. Elija mtodos para devolver datos desde la pestaa SELECT Configure manualmente el ObjectDataSource El asistente de configuracin de origen de datos del ObjectDataSource ofrece una forma rpida de especificar el objeto que utiliza y como asociar los mtodos del objeto que son invocados. Sin embargo se puede configurar el ObjectDataSource por medio de sus propiedades, ya sea a travs de la ventana Propiedades o a travs del marcado declarativo. Sencillamente establezca la propiedad TypeName del tipo de objeto subyacente que va a utilizar y el SelectMethod al mtodo para invocar cuando recuperamos datos.
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProducts" TypeName="ProductsBLL"> </asp:ObjectDataSource>

Incluso si prefiere el asistente de configuracin de origen de datos puede haber ocasiones en las que es necesario configurar manualmente el ObjectDataSource, ya que el asistente solo muestra las clases creadas por el desarrollador. Si desea enlazar el ObjectDataSource a una clase en el .Net Framework como la clase MemberShip, para acceder a la informacin de la cuenta de usuario o la clase Directory para trabajar con la informacin de los archivos del sistema, necesitara configurar manualmente las propiedades del ObjectDataSource. Paso 2: Agregar un control de datos web y enlazarlo al ObjectDataSource Una vez que ha agregado el ObjectDataSource a la pgina y lo ha configurado, estamos listos para agregar controles de datos web a la pgina para mostrar los datos devueltos por el mtodo Select del ObjectDataSource. Cualquier control de datos web puede enlazarse a un ObjectDataSource, veremos como mostrar los datos del ObjectDataSource en el GridView, FormView y DetailsView. Enlazar un GridView al ObjectDataSource Agregue un control GridView desde el cuadro de herramientas a la superficie de diseo de la pgina SimpleDisplay.aspx. Desde la etiqueta inteligente del GridView, elija el control ObjectDataSource que se agrego en el paso 1. Esto creara automticamente un BoundField en el control GridView para cada propiedad que devuelva los datos del mtodo Select del ObjectDataSource (es decir las propiedades definidas por el DataTable Products).

Figura 6. Un GridView se ha agregado y enlazado al ObjectDataSource

Luego puede personalizar, reorganizar o eliminar los BoundFields en el GridView, haga clic en la opcin Editar Columnas en la etiqueta inteligente.

Figura 7. Administrar los BoundFields del GridView por medio del cuadro de dialogo Editar Columnas Tmese un tiempo para modificar los BoundFields del GridView, eliminando los BoundFields ProductID, SupplierID, CategoryID, QuantityPerUnit, UnitsInStock, UnitsOnOrder y ReorderLevel. Basta con seleccionar el BoundField de la lista inferior del lado izquierdo y hacer clic en el botn eliminar (en la X roja) para eliminarlos. Luego reorganice los BoundFields para que los BoundFields CategoryName y SupplierName precedan al BoundField UnitPrice seleccionando estos BoundFields y haciendo clic en la flecha hacia arriba. Ajuste las propiedades HeaderText de los BoundFields restantes a Products, Category, Supplier y Price respectivamente. Luego de formato al BoundField Price como moneda ajustando la propiedad HtmlEncode del BoundField en Falso y su propiedad DataFormatString en {0:c}. Para finalizar alinee horizontalmente el precio a la derecha y centre la casilla de verificacin Discontinued por medio de la propiedad ItemStyle/HorizontalAlign.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product"

SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice"> <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued"> <ItemStyle HorizontalAlign="Center" /> </asp:CheckBoxField> </Columns> </asp:GridView>

Figura 8. Los BoundFields del GridView han sido personalizados Uso de temas para un look consistente Estos tutoriales se esfuerzan por remover cualquier configuracin de estilos a nivel de controles, en su lugar siempre que sea posible usamos hojas de estilo definidas en un archivo externo. El archivo Styles.css contiene clases CSS DataWebControlStyle, HeaderStyle, RowStyle y AlternatingRowStyle que deben usarse para dictar la apariencia de los controles de datos web usados en estos tutoriales. Para realizar esto, podramos establecer la propiedad CssClass del GridView a DataWebControlStyle y sus

propiedades HeaderStyle, RowStyle y AlternatingRowStyle a las propiedades Css correspondientes. Si establecemos estas propiedades CssClass en el control Web, necesitaremos recordar establecer explcitamente los valores de estas propiedades para todos y cada uno de los controles de datos web de nuestros tutoriales. Un enfoque ms manejable es definir por defecto las propiedades Css relacionadas para los controles GridView, FormView y DetailsView utilizando un tema. Un tema es una coleccin de propiedades, imgenes y clases Css a nivel de control que se pueden aplicar a las pginas de un sitio para hacer cumplir un look comn. Nuestro tema no incluye imgenes o archivos Css (dejaremos la hoja de estilos Style.css como est definida en la carpeta raz de la aplicacin web) pero contar con dos skins. El skin es un archivo que define las propiedades por defecto de un control web. En concreto vamos a tener un archivo skin para los controles GridView y DetailsView, que indica por defecto las propiedades Css relacionadas. Comience agregando un archivo skin a su proyecto denominado GridView.skin haciendo clic derecho sobre el nombre del proyecto en el Explorador de Soluciones y elija agregar nuevo elemento.

Figura 9. Aada un archivo piel llamado GridView.skin Los archivos skin deben ser colocados en un tema, los cuales estn localizados en la carpeta App_Themes. Puesto que aun no tiene ninguna carpeta, Visual Studio amablemente se ofrece a crearla por nosotros cuando creamos nuestro primer skin.

Haga clic en S para crear la carpeta App_Themes y colocar all el nuevo archivo GridView.skin.

Figura 10. Visual Studio crear la carpeta App_Themes Esto creara un nuevo tema en la carpeta App_Themes llamada GridView con el archivo GridView.skin.

Figura 11. El tema GridView ha sido agregado a la carpeta App_Themes Cambie el tema GridView a DataWebControls (haga clic derecho sobre la subcarpeta GridView en la carpeta GridView y seleccione cambiar nombre). Luego introduzca el siguiente marcado en el archivo GridView.skin:
<asp:GridView runat="server" CssClass="DataWebControlStyle"> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> <RowStyle CssClass="RowStyle" /> <HeaderStyle CssClass="HeaderStyle" /> </asp:GridView>

Esto define por defecto las propiedades para las propiedades CssClass relacionadas para cualquier GridView en cualquier pgina que utilice el tema DataWebControls.

Aadiremos otro skin para el DetailsView, un control de datos web que utilizaremos en breve. Aada un nuevo skin para el tema DataWebControls llamado DetailsView.skin y agregue el siguiente marcado:
<asp:DetailsView runat="server" CssClass="DataWebControlStyle"> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> <RowStyle CssClass="RowStyle" /> <FieldHeaderStyle CssClass="HeaderStyle" /> </asp:DetailsView>

Con nuestro tema definido, el ltimo paso es aplicar el tema a nuestra pgina de Asp.Net. Un tema puede ser aplicado en forma de pgina a pgina o para todas las pginas del sitio web. Usaremos este tema para todas las pginas del sitio web. Para realizar esto, agregamos el siguiente marcado a la seccin <system.web> del Web.config.
<pages styleSheetTheme="DataWebControls" />

Esto es todo lo que hay que hacer! El valor del StyleSheetTheme indica que las propiedades especificadas en el tema, no deben ser remplazadas por las propiedades definidas a nivel de control. Para especificar que la configuracin del tema debe estar por encima de los ajustes de control, se utiliza el atributo tema en lugar de StyleSheetTheme, por desgracia la configuracin del tema no aparece en la vista de diseo de Visual Studio. Consulte los temas Un vistazo a los temas y mascaras en Asp.Net y Estilos del lado-servidor utilizando temas para obtener ms informacin sobre temas y mscaras; Ver cmo aplicar temas de Asp.Net para mayor informacin sobre la configuracin de una pgina para utilizar un tema.

Figura 12. El GridView muestra la informacin ProductName, Category, Supplier, Price y Discontinued Mostrando un registro a la vez en un DetailsView El GridView muestra una fila para cada registro devuelto por el control de origen de datos al que esta enlazado. Sin embargo, hay ocasiones en que deseamos mostrar nicamente un registro o solo un registro a la vez. El control DetailsView ofrece esta funcionalidad, hacindolo como un table <HTML> con dos columnas y una fila por cada columna o propiedad enlazada al control. Usted puede pensar en el DetailsView como un GridView con un registro nico rotado 90 grados. Comenzamos agregando un control DetailsView encima del GridView en la pgina SimpleDisplay.aspx Luego enlazamos el control al mismo ObjectDataSource como con el GridView. Al igual que con el GridView se aadir un BoundField al DetailsView por cada propiedad de vuelta en el objeto por el mtodo Select del ObjectDataSource. La nica diferencia es que en el DetailsView los BoundFields estn dispuestos horizontalmente y no verticalmente.

Figura 13. Aadir un DetailsView a la pgina y enlazarlo al ObjectDataSource Al igual que en el GridView, los BoundFields del DetailsView pueden ser ajustados para ofrecer una visualizacin ms personalizada de los datos devueltos por el ObjectDataSource. La figura 14 muestra el DetailsView luego de que sus BoundFields y sus propiedades CssClass han sido configuradas para dar un aspecto similar al del ejemplo con el GridView.

Figura 14. El DetailsView muestra un solo registro Tenga en cuenta que el DetailsView solo muestra el primer registro devuelto por su origen de datos. Para permitir al usuario pasar uno a uno a travs de todos los registros, debemos habilitar la paginacin en el DetailsView. Para ello regresamos a Visual Studio y seleccionamos la casilla Habilitar Paginacin en la etiqueta inteligente del DetailsView.

Figura 15. Habilitar la paginacin en el control DetailsView

Figura 16. Con la paginacin habilitada, el DetailsView permite al usuario ver cualquiera de los productos Hablaremos ms acerca de la paginacin en futuros tutoriales.

Un diseo ms flexible para mostrar un solo registro a la vez El DetailsView es bastante rgido en la forma en que muestra cada registro devuelto por el ObjectDataSource. Podramos desear una visin ms flexible de los datos. Por ejemplo, en lugar de querer mostrar el nombre del Producto, Categora, Proveedor, Precios e informacin de Descontinuacin cada uno en una fila, es posible que deseemos mostrar el nombre del producto y el precio en un encabezamiento <h4>, y la informacin de la categora y el proveedor debajo de ellos con un tamao de fuente ms pequeo. Tambin podramos no querer mostrar los nombres de las propiedades (Productos, categoras y otros) al lado de los valores. El control FormView proporciona este nivel de personalizacin. En lugar de utilizar campos (como lo hacen el GridView y el DetailsView), el FormView utiliza plantillas, que permiten una mezcla de controles., HTML esttico y sintaxis de enlace de datos. Si est familiarizado con el control Repeater desde Asp.Net 1.x, puede pensar en el FormView como un Repeater para mostrar un solo registro. Agregue un FormView a la superficie de diseo de la pgina SimpleDisplay.aspx. Inicialmente el FormView se muestra como un bloque gris, informndonos que tenemos que ofrecer como mnimo el control ItemTemplate.

Figura 17. El FormView debe incluir un ItemTemplate

Usted puede enlazar directamente el FormView a un control de origen de datos por medio de la etiqueta inteligente del FormView, el cual crear automticamente por defecto un ItemTemplate. (junto con un EditItemTemplate y un InsertItemTemplate si las propiedades InsertMethod e UpdateMethod del control ObjectDataSource han sido establecidas). Sin embargo para este ejemplo enlazaremos los datos al FormView y estableceremos su ItemTemplate manualmente. Comenzaremos estableciendo la propiedad DataSourceID del FormView al ID del control ObjectDataSource, ObjectDataSource1. Luego creamos el ItemTemplate para que muestre el nombre del producto y su precio en un elemento <h4> y la categora y el nombre del proveedor debajo en un tamao de fuente ms pequeo.
<asp:FormView ID="FormView1" runat="server" DataSourceID="ObjectDataSource1" EnableViewState="False"> <ItemTemplate> <h4> <%# Eval("ProductName") %> (<%# Eval("UnitPrice", "{0:c}") %>) </h4> Category: <%# Eval("CategoryName") %>; Supplier: <%# Eval("SupplierName") %> </ItemTemplate> </asp:FormView>

Figura 18. El primer producto Chai es mostrado en un formato personalizado

El <%Eval(PropertyName)%> es la sintaxis de enlace a datos. El mtodo Eval devuelve el valor de la propiedad especificada para el objeto que est siendo actualmente enlazada al control FormView. Echemos un vistazo al artculo Sintaxis de Enlace de datos simplificado y ampliado en Asp.net 2.0 de Alex de Homero para tener ms informacin sobre las entradas y salidas de enlace de datos. Al igual que el DetailsView, el FormView solo muestra el primer registro devuelto por el ObjectDataSource. Usted puede habilitar la paginacin en el FormView para permitir a los usuarios recorrer los productos uno a la vez. RESUMEN El acceso y la visualizacin de datos de una capa lgica de negocios se pueden lograr sin necesidad de escribir una lnea de cdigo gracias al control ObjectDataSource de Asp.Net 2.0. El control ObjectDataSource llama a un mtodo especfico de una clase y devuelve los resultados. Estos resultados se pueden mostrar en un control de datos web enlazado al control ObjectDataSource. En este tutorial observamos el enlace del ObjectDataSource a los controles GridView, FormView y DetailsView. Hasta ahora solo hemos visto como usar el ObjectDataSource para invocar un mtodo sin parmetros, pero y si deseamos invocar un mtodo que espera parmetros de entrada, tal como el GetproductsByCategoryID (categoryID) de la clase ProductsBLL? Con el fin llamar a un mtodo que espera uno o ms parmetros debemos configurar el ObjectDataSource para especificar los valores de estos parmetros. Veremos cmo lograr esto en nuestro prximo tutorial.

5. DECLARACIN DE PARAMETROS
En este tutorial vamos a ilustrar como usar un parmetro estableciendo un valor no modificable para seleccionar los datos a mostrar en un control DetailsView. Introduccin En el tutorial anterior vimos como mostrar los datos con los Controles GridView, DetailsView y FormView enlazados a un control ObjectDataSource que invocaba el mtodo GetProducts () de la clase ProductsBLL. El mtodo GetProducts () devuelve un DataTable fuertemente tipiado poblado con todos los registros de la base de datos de la tabla Products. La clase ProductsBLL contiene mtodos adicionales para la devolucin de subconjuntos de los productos GetProductsByProductID (productID), GetProductsByCategoryID (categoryID) y GetProductsBySupplierID (supplierID). Estos tres mtodos esperan un parmetro de entrada que indica como filtrar la informacin devuelta de los productos. El ObjectDataSource se puede utilizar para llamar mtodos que esperan parmetros de entrada, pero para hacerlo tenemos que especificar el lugar de donde vienen los valores de los parmetros. Los valores de los parmetros pueden ser fuertemente codificados o provenir de fuentes dinmicas, incluyendo: valores de cadenas de consulta, variables de sesin, el valor de una propiedad de un control web de la pgina, entre otros. Para este tutorial vamos a empezar mostrando cmo usar un parmetro establecido como un valor no modificable. En concreto veremos la adicin de un DetailsView que muestra la informacin de un producto especfico de nombre Mix Gumbo del Chef Anton, el cual tiene un ProductID de 5. Luego veremos cmo establecer el valor de los parmetros basados en un control web. En particular, utilizaremos un cuadro de texto para permitir que el usuario ingrese un pas, despus de lo cual puede hacer clic en un botn para ver la lista de proveedores que residen en ese pas. Usando un valor de parmetro no modificable Para el primer ejemplo empezaremos agregando un control DetailsView a la pgina DeclarativeParams.aspx de la carpeta BasicReporting. Desde la etiqueta inteligente del DetailsView, seleccione <Nuevo Origen de datos> en la lista desplegable y elija agregar un ObjectDataSource.

Figura 1. Aadir un ObjectDataSource a la pgina Esto iniciara automticamente el asistente de configuracin de Origen de datos del control ObjectDataSource. Seleccione la clase ProductsBLL en la primera pantalla del asistente.

Figura 2. Seleccione la clase ProductsBLL Como queremos mostrar la informacin sobre un producto en particular, necesitamos usar el mtodo GetProductsByProductID (productID).

Figura 3. Elija el mtodo GetProductsByProductID (productID) Dado que el mtodo que se ha seleccionado incluye un parmetro, hay una pantalla adicional en el asistente, donde se nos pide definir el valor que se utilizara para el parmetro. La lista de la izquierda muestra todos los parmetros del mtodo seleccionado. Para el mtodo GetProductByProductID (productID) solo hay uno, el productID. A la derecha podemos especificar el valor del parmetro seleccionado. La lista desplegable muestra las posibles fuentes de los parmetros. Puesto que se desea especificar un valor no modificable de 5 para el parmetro ProductID, dejamos la fuente del parmetro en Ninguno y escribimos 5 en el cuadro de texto de valor por defecto.

Figura 4. Usaremos un valor de parmetro de 5 fuertemente codificado para el parmetro productID Despus de completar el asistente de configuracin del origen de datos, el marcado declarativo del control ObjectDataSource incluye un objeto Parameter en la coleccin SelectParameters para cada uno de los parmetros de entrada esperados por el mtodo definido en la propiedad SelectMethod. Dado que el mtodo que estamos especificando solo espera un parmetro de entrada, ParameterID es la nica entrada aqu. La coleccin SelectParameters puede contener cualquier clase que se derive de la clase Parameter en el espacio de nombres System.Web.UI.WebControls. Para los valores de parmetros no modificables se utiliza la clase base Parameter, pero para las otras opciones de origen de parmetros se utiliza una clase Parameter derivada; si es necesario tambin podemos crear nuestros propios tipos parameter personalizados.
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProductByProductID" TypeName="ProductsBLL"> <SelectParameters> <asp:Parameter DefaultValue="5" Name="productID" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Nota: Si has ido siguiendo los pasos en tu computador, el marcado declarativo que debes ver en este momento incluye valores para las propiedades InsertMethod,

UpdateMethod

DeleteMethod,

as

como

DeleteParameters.

El

asistente

de

configuracin de origen de datos del ObjectDataSource define automticamente los mtodos del ProductsBLL para actualizar, insertar y eliminar, por lo tanto a menos que los especifique claramente por fuera, sern incluidos en el anterior marcado. Cuando visitamos la pgina, el control de datos web invoca el mtodo Select del ObjectDataSource, el cual llamar el mtodo GetproductsByProductID (productID) de la clase ProductsBLL, empleando el valor no modificable de 5 para el parmetro de entrada productID. El mtodo devuelve un objeto ProductsDataTable fuertemente tipiado que incluye una sola fila con informacin acerca de Gumbo Mix del chef Anton (el producto con el productID de 5)

Figura 5. Se muestra la informacin acerca del Gumbo Mix del Chef Anton Establecer el valor del parmetro a la propiedad valor de un control Web Los valores de los parmetros del ObjectDataSource tambin pueden establecerse en funcin del valor de un control web de la pgina. Para ilustrar esto vamos a tener un GridView que muestre todos los proveedores que se encuentren en un pas determinado por el usuario. Para realizar esto, comenzamos agregando un control TextBox a la pgina, en el cual el usuario puede introducir un nombre del pas. Ajuste la propiedad ID del control TextBox en CountryName. Agregue tambin un control web Button.

Figura 6. Agregar un control TextBox a la pgina con su ID en CountryName Luego agregue un control GridView a la pgina y desde la etiqueta inteligente aada un nuevo ObjectDataSource. Como queremos mostrar la informacin del proveedor, seleccionamos la clase SuppliersBLL en la primera pantalla del asistente. En la segunda pantalla elegimos el mtodo GetSuppliersByCountry (country).

Figura 7. Elija el mtodo GetSuppliersByCountry (country) Como el mtodo GetSuppliersByCountry (country) tiene un parmetro de entrada, el asistente incluye una nueva pantalla para elegir el valor del parmetro. Esa vez establecemos el origen del parmetro en Control. Esto poblar la lista desplegable ControlID con los nombres de los controles de la pgina, seleccione el control CountryName de la lista. Cuando se visita por primera vez la pgina, el TextBox CountryName estar en blanco, por lo que no se devuelven resultados y no aparecer

nada. Si deseamos mostrar algunos resultados de forma predeterminada, entonces establezca el DefaultValue del TextBox.

Figura 8. Establezca el valor del parmetro al valor del control CountryName El marcado declarativo del ObjectDataSource difiere ligeramente del de nuestro primer ejemplo, ya que utiliza un ControlParameter en lugar de un objeto Parameter estndar. Un ControlParameter tiene propiedades adicionales para especificar el ID del control Web y el valor de la propiedad que se usara para el parmetro (PropertyName). El asistente de configuracin de origen de datos es lo suficientemente inteligente para determinar que para un TextBox es posible que se utilice la propiedad Text para el valor del parmetro. Sin embargo si desea usar un valor de propiedad diferente del control web, puede cambiar aqu el valor de PropertyName o pulsando el botn Mostrar propiedades avanzadas en el asistente.
<asp:ObjectDataSource ID="ObjectDataSource2" runat="server" SelectMethod="GetSuppliersByCountry" TypeName="SuppliersBLL"> <SelectParameters> <asp:ControlParameter ControlID="CountryName" Name="country" PropertyName="Text" Type="String" /> </SelectParameters> </asp:ObjectDataSource>

Cuando visitamos la pgina por primera vez el TextBox CountryName esta vacio. El mtodo Select del ObjectDataSource es invocado por el GridView, pero un valor de Nothing es pasado al mtodo GetSuppliersByCountry (country). El TableAdapter convierte el Nothing a un valor Null para la base de datos (DBNull.Value), pero la consulta utilizada por el mtodo GetSuppliersByCountry (country) est escrita de tal forma que no devuelve ningn valor cuando se especifica un valor de Null para el parmetro @CategoryID. En resumen no se devuelve ningn proveedor. Una vez que el usuario ingresa un pas y hace clic en el botn Mostrar proveedores se origina una devolucin de datos, el mtodo Select del ObjectDataSource es requerido, pasando el valor Text del control TextBox como el parmetro Country.

Figura 9. Se muestran los proveedores de Canada Mostrar todos los proveedores de forma predeterminada En lugar de no mostrar ningn proveedor la primera vez que ingresamos a la pgina, deseamos mostrar todos los proveedores en un primer momento, lo que permitir al usuario filtrar la lista al introducir un nombre de pas en el cuadro de texto. Cuando el cuadro de texto est vaco, el mtodo GetSuppliersByCountry (country) de la clase SuppliersBLL pasa un valor de Nothing como su parmetro de entrada Country. Este valor de Nothing es transmitido al mtodo GetSuppliersByCountry de la DAL donde es traducido a un valor Null para la base de datos para el parmetro Country en la siguiente consulta:
SELECT FROM SupplierID, CompanyName, Address, City, Country, Phone Suppliers

WHERE Country = @Country

La expresin Country=Null siempre devolver falso, incluso para aquellos registros cuya columna Country tienen un valor Null, por lo tanto no se devuelven registros.

Para devolver todos los proveedores, cuando el TextBox Country esta vacio, podemos ampliar el mtodo GetSuppliersByCountry (country) en la BLL para invocar el mtodo GetSuppliers () cuando su parmetro pas es Nothing o en caso contrario llama al mtodo GetSuppliersByCountry (country). Esto tendr el efecto de devolver todos los proveedores cuando no se especifique ningn pas y devolver el subconjunto de proveedores cuando se incluye un valor para el parmetro pas. Cambie el mtodo GetSuppliersByCountry (country) del mtodo SuppliersBLL a lo siguiente:
Public Function GetSuppliersByCountry(country As String) _ As Northwind.SuppliersDataTable If String.IsNullOrEmpty(country) Then Return GetSuppliers() Else Return Adapter.GetSuppliersByCountry(country) End If End Function

Con este cambio la pgina DeclarativeParams.aspx muestra todos los proveedores, cuando la visitamos por primera vez (o cada vez que el TextBox CountryName este vaco).

Figura 10. Todos los proveedores se muestran de forma predeterminada Resumen Con el fin de especificar mtodos con parmetros de entrada, es necesario especificar los valores de los parmetros de la coleccin SelectParameters del ObjectDataSource. Los diferentes tipos de parmetros permiten que el valor del parmetro sea obtenido de diferentes fuentes. El tipo de parmetro por defecto utiliza un valor no modificable, pero con la misma facilidad (y sin una lnea de cdigo) los valores de los parmetros pueden ser obtenidos de una cadena de consulta, variables de sesin, cookies, e incluso valores ingresados por el usuario en los controles web de la pgina. Los ejemplos que vimos en este tutorial muestran cmo utilizar valores de parmetros mediante declaracin. Sin embargo pude haber momentos en que deseamos utilizar una fuente parmetro que no est disponible, como por ejemplo la fecha y hora actuales, o si nuestro sitio utiliza Membership (suscripcin), el ID del usuario visitante. Para estos escenarios se pueden establecer los valores de los parmetros mediante programacin antes de invocar el mtodo de su objeto subyacente. Veremos cmo realizar en el siguiente tutorial.

6. DEFINICIN MEDIANTE PROGRAMACIN DE LOS VALORES DE LOS PARAMETROS DEL OBJECTDATASOURCE


En este tutorial veremos la adicin de un mtodo a nuestra DAL y BLL que acepta un nico parmetro de entrada y devuelve datos. El ejemplo establecer este parmetro mediante programacin. Introduccin Como vimos en el tutorial anterior existen un nmero de opciones disponibles para declarar los valores de los parmetros que son pasados a los mtodos del ObjectDataSource. Si el valor del parmetro no es modificable, viene de un control web de la pgina o de cualquier otra fuente que sea legible por el objeto Parameter del origen de datos, el valor puede ser enlazado al parmetro de entrada sin necesidad de escribir una lnea de cdigo. Sin embargo, puede haber ocasiones cuando el valor del parmetro viene de alguna fuente que no est incorporada en los orgenes de datos predefinidos para el objeto Parameter. Si nuestro sitio soporta cuentas de usuario, podramos desear establecer el parmetro basados en los UserID de los visitantes actualmente conectados, o es posible que necesitemos personalizar el valor del parmetro antes de enviarlo junto con el mtodo del objeto subyacente del ObjectDataSource. Cada vez que el mtodo Select del ObjectDataSource es invocado, el ObjectDataSource primero eleva su evento Selecting. Luego es invocado el mtodo del objeto subyacente del ObjectDataSource. Una vez completado esto se genera el evento seleccionado del ObjectDataSource (La figura 1 muestra la secuencia de eventos). Los valores de los parmetros pasados al mtodo del objeto subyacente del ObjectDataSource pueden ser personalizados en un controlador de eventos para el evento Selecting.

Figura 1. Los eventos Selecting y Select del ObjectDataSource se generan antes y despus de invocar el mtodo del objeto subyacente En este tutorial veremos la adicin de un mtodo a nuestra DAL y BLL que acepta un solo parmetro de entrada Month, de tipo Integer y devuelve un objeto EmployeesDataTable poblado con aquellos empleados cuyo aniversario de contratacin es en el mes especificado Nuestro ejemplo establece este parmetro mediante programacin basado en el mes en curso y mostrando una lista de Empleados de aniversario este mes Paso 1: Agregar un mtodo al EmployeesTableAdapter Para nuestro primer ejemplo tenemos que aadir un mtodo para recuperar aquellos empleados cuya HireDate ocurri en un determinado mes. Para proporcionar esta funcionalidad, de acuerdo con nuestra arquitectura tenemos que crear un mtodo en el EmployeesTableAdapter que se asigne a la sentencia SQL adecuada. Para realizar esto primero abrimos el DataSet tipiado Northwind. Haga clic en la etiqueta EmployeesTableAdapter y elija agregar consulta.

Figura 2. Agregue una nueva consulta a EmployeesTableAdapter Seleccione agregar una instruccin Select que devuelva filas. Cuando lleguemos a la pantalla de declaracin especfica de la sentencia Select, la sentencia SELECT por defecto para el EmployeesTableAdapter ya est cargada. Solo tenemos que agregar la clausula WHERE DATEPART(m, HireDate)=@Month. DATEPART es una funcin T-SQL que devuelve una parte en particular de una fecha determinada de tipo datetime, en este caso estamos utilizando DATEPART para devolver el mes de la columna HireDate.

Figura 3. Devuelve solo las filas donde la columna HireDate es menor o igual al parmetro @HiredBeforeDate

Para

finalizar

cambie

los

nombres

de

los

mtodos

FillBy

GetData

FillByHiredDateMonth y GetEmployessByHiredDateMonth, respectivamente.

Figura 4. Elija los nombres ms adecuados para los mtodos FillBy y GetDataBy Haga clic en Finalizar para completar el asistente y regresar a la superficie de diseo del DataSet. El EmployeesTableAdapter ahora debe incluir un nuevo conjunto de mtodos para acceder a los empleados contratados en un determinado mes.

Figura 5. Los nuevos mtodos aparecen en la superficie de diseo del DataSet Paso 2: Agregar al mtodo GetEmployeesByHiredDateMonth (month) a la capa lgica de negocio

Como nuestra arquitectura de aplicacin utiliza una capa separada de lgica de negocios y lgica de acceso a datos, tenemos que aadir un mtodo a nuestra BLL que llame a la DAL para recuperar los empleados que fueron contratados antes de una fecha determinada. Abra el archivo EmployeesBLL.vb y agregue el siguiente mtodo:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetEmployeesByHiredDateMonth(ByVal month As Integer) _ As Northwind.EmployeesDataTable Return Adapter.GetEmployeesByHiredDateMonth(month) End Function

Al igual que los otros mtodos de esta clase, GetEmployeesByHiredDateMonth (month) simplemente llama a la DAL y devuelve los resultados. Paso 3: Mostrar los empleados cuyo aniversario de contratacin es este mes El paso final para este ejemplo es mostrar aquellos empleados cuyo aniversario de contratacin es este mes. en Comience la carpeta con agregando un GridView y a la un pgina nuevo en ProgrammaticParams.aspx utilice la clase BasicReporting el agregue

ObjectDataSource como origen de datos. Configure el ObjectDataSource para que EmployeesBLL SelectMethod establecido GetEmployeesByHiredDateMonth (month).

Figura 6. Utilice la clase Employees BLL

Figura 7. Seleccione el mtodo GetEmployeesByHiredDateMonth (month) La ltima pantalla nos pide suministrar el origen del valor del parmetro month. Puesto que vamos a establecer este valor mediante programacin, dejamos la opcin de origen del parmetro en Ninguno y hacemos clic en Finalizar.

Figura 8. Deje establecido el origen del parmetro en Ninguno Esto crear un objeto Parameter en la coleccin SelectParameters del ObjectDataSource que no tiene un valor especificado.
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetEmployeesByHiredDateMonth" TypeName="EmployeesBLL">

<SelectParameters> <asp:Parameter Name="month" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Para establecer este valor mediante programacin, necesitamos crear un controlador de eventos para el evento Selecting del ObjectDataSource. Para realizar esto vaya a la Vista Diseo y haga doble clic en el ObjectDataSource. Tambin puede seleccionar el ObjectDataSource, ir a la ventana propiedades y hacer doble clic en el icono del rayo. Luego haga doble clic en el cuadro de texto al lado del evento Selecting o escriba el nombre del controlador de eventos que desee utilizar. Como tercera opcin, puede crear el controlador de eventos mediante la seleccin del ObjectDataSource y su evento Selecting en las dos listas desplegables en la parte superior de la vista cdigo de la pgina de la clase subyacente.

Figura 9. Haga clic en el icono del rayo en la ventana propiedades para listar los eventos del control web Los tres enfoques agregan un nuevo controlador de eventos para el evento Selecting del ObjectDataSource a la clase de cdigo subyacente de la pgina. En este controlador de eventos podemos leer y escribir los valores de los parmetros utilizando e.InputParameters(parameterName), donde parameterName es el valor del atributo Name en la etiqueta <asp:Parameter> (la coleccin InputParameters tambin se puede

indexar ordinalmente, como en e.InputParameters (index)). Para establecer el parmetro month al mes actual, agregamos el siguiente controlador para el evento Selecting:
Protected Sub ObjectDataSource1_Selecting _ (sender As Object, e As ObjectDataSourceSelectingEventArgs) _ Handles ObjectDataSource1.Selecting e.InputParameters("month") = DateTime.Now.Month End Sub

Cuando visitamos esta pgina a travs del navegador, podemos observar que solamente un empleado fue contratado este mes (marzo) Laura Callahan, quien ha estado en la compaa desde 1994.

Figura 10. Se muestran los empleados cuyo aniversario es este mes Resumen Si bien los valores de los parmetros del ObjectDataSource normalmente se pueden establecer mediante declaracin, sin necesidad de escribir una lnea de cdigo, es sencillo configurar los valores de los parmetros mediante programacin. Todo lo que necesitamos hacer es crear un controlador de eventos para el evento Selecting del ObjectDataSource, el cual se genera antes que el mtodo del objeto subyacente es invocado y establecemos manualmente los valores para uno o ms parmetros por medio de la coleccin InputParameters. Este tutorial finaliza la seccin de Reporte Bsico. En el siguiente tutorial se inicia el filtrado y la seccin de los escenarios Maestro-Detalle, en la cual veremos tcnicas para

permitir al visitante filtrar los datos y profundizar a partir de un informe maestro en un informe de detalles.

MAESTRO / DETALLE

7. FILTRADO MAESTRO/DETALLE CON UN DROPDOWNLIST


En este tutorial veremos cmo mostrar los registros maestros en un control DropDownList y los detalles del tem de la lista seleccionado en un GridView. Introduccin Un tipo comn de informes es el informe maestro/detalle; en los que el informe comienza mostrando un conjunto de registros maestros. El usuario puede profundizar en uno de estos registros maestros, para ver los detalles de este registro maestro. Los informes Maestro/detalle son la opcin ideal para la visualizacin de relaciones uno a varios, como por ejemplo un informe que muestre todas las categoras y luego permita al usuario seleccionar una en particular y mostrar los productos asociados a ella. Adems los informes maestro/detalle son tiles para mostrar informacin detallada de tablas amplias (es decir aquellas que tienen un gran nmero de columnas). Por ejemplo el nivel maestro de un informe maestro/detalle podra mostrar solamente el nombre del producto y el precio de los productos en la base de datos y profundizando en un producto particular mostrara campos adicionales del producto (categora, proveedor, cantidad por unidad y as sucesivamente). Existen muchas formas en las cuales podemos implementar un informe

maestro/detalle. Durante este y los prximos tres tutoriales veremos una variedad de informes maestro/detalle. En este tutorial veremos cmo mostrar los registros maestros en un control DropDownList y los detalles de elemento de la lista seleccionado en un control GridView. Este informe maestro/detalle en particular muestra una lista de categoras e informacin de los productos. Paso 1: Visualizacin de las categoras en un DropDownList Nuestro informe maestro/detalle muestra una lista de categoras en un DropDownList, con los productos del elemento de la lista seleccionado ms abajo de la pagina en un GridView. Entonces la primera tarea que tenemos por delante es hacer que las categoras aparezcan en un DropDownList. Abra la pgina FilterByDropDownList.aspx de la carpeta Filtering, arrastre un DropDownList de la caja de herramientas al diseador de la pagina y establezca su propiedad ID en Categories. Luego haga clic en el enlace Elegir Origen de datos en la etiqueta inteligente del DropDownList. Esto mostrara el asistente de configuracin de origen de datos.

Figura 1. Especificar el origen de datos del DropDownList Seleccione agregar un nuevo ObjectDataSource llamado CategoriesDataSource que invoque el mtodo GetCategories () de la clase CategoriesBLL.

Figura 2. Agregar un nuevo ObjectDataSource llamado CategoriesDataSource

Figura 3. Elija utilizar la clase CategoriesBLL

Figura 4. Configuracin del ObjectDataSource para que utilice el mtodo GetCategories Despus de configurar el ObjectDataSource tenemos que especificar qu campo del origen de datos se debe mostrar en el DropDownList y cual debe estar asociado como

el valor del elemento de la lista seleccionado. Haga de CategoryName el campo para mostrar y CategoryID el valor de cada elemento de la lista.

Figura 5. Haga el campo CategoryName para mostrar y use CategoryID como el valor del DropDownList Hasta este momento tenemos un control DropDownList poblado con los registros de la tabla Categories (todo se logro en seis segundos). La figura 6 muestra nuestro proceso hasta ahora visto a travs de un navegador.

Figura 6. Un DropDownList muestra la lista de las categoras actuales

Paso 2: Agregar el GridView Productos El ltimo paso de nuestro informe maestro/detalle es mostrar los productos asociados con la categora seleccionada. Para realizar esto, agregue un GridView a la pgina y agregue un nuevo ObjectDataSource llamado ProductsDataSource. Haga que el control ProductsDataSource tome sus datos del mtodo GetProductsByCategoryID (categoryID) de la clase productsBLL.

Figura 7. Seleccione el mtodo GetProductsByCategoryID (categoryID) Despus de elegir este mtodo, el asistente del ObjectDataSource nos solicita el valor para el parmetro categoryID del mtodo. Para utilizar el valor de los elementos del DropDownList Categories establezca el origen de los parmetros en Control y el ControlID en Categories.

Figura 8. Establezca el parmetro CategoryID con el valor del DropDownList Categories Tome un momento para ver nuestro progreso a travs del navegador. La primera vez que visite la pgina, se muestran los productos pertenecientes a la categora seleccionada (bebidas) como se observa en la figura 9, pero al cambiar el DropDownList no se actualizan los datos. Esto se debe a que para que el GridView se actualice debe ocurrir una devolucin de datos. Para realizar esto, tenemos dos opciones (ninguna de ellas requiere escribir cdigo):

Establecer la propiedad AutoPostBack del DropDownList Categories en True.


(Esto se puede realizar marcando la opcin Habilitar AutoPostBack en la etiqueta inteligente del DropDownList). Esto ocasiona que se produzca una devolucin de datos cada vez que la opcin seleccionada en el DropDownList sea cambiada por el usuario. Por lo tanto cuando el usuario seleccione una nueva categora del DropDownList se producir una nueva devolucin de datos y el GridView se actualizara con los productos de la categora que acabe de seleccionar. (Este es el enfoque usado en este tutorial)

Aadir un control Button al sitio web al lado del DropDownList. Establezca su


propiedad Text en actualizar. Con este enfoque el usuario tendr que seleccionar una nueva categora y luego hacer clic en el botn. Al hacer clic habr una devolucin de datos y el GridView actualizara la lista de los productos de la categora seleccionada.

Las figuras 9 y 10 muestran el informe maestro/detalle en accin:

Figura 9. Cuando se visita por primera vez la pgina se muestran los productos de Bebidas

Figura 10. Seleccionar Produce genera automticamente un PostBack y actualiza el GridView Agregue un Seleccione una categora a la lista de elementos

La primera vez que visite la pgina FilterByDropDownList.aspx, el primer elemento de la lista del DropDownList Categories (bebidas) est seleccionado por defecto, mostrando los productos de bebidas en el GridView. En lugar de mostrar los productos de la primera categora, podramos querer tener seleccionado un elemento de la lista del DropDownList que diga algo as como: -Seleccione una categora-. Para agregar un nuevo elemento a la lista del DropDownList, vaya a la ventana propiedades y haga clic en los puntos suspensivos de la propiedad Items. Agregue un nuevo elemento de la lista con el Text -Elija una categora- y el Value -1.

Figura 11. Agregue un elemento -Seleccione una categora- a la lista De forma alternativa, usted puede agregar el elemento a la lista aadiendo el siguiente marcado de cdigo al control DropDownList:
<asp:DropDownList ID="categories" runat="server" AutoPostBack="True" DataSourceID="categoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID" EnableViewState="False"> <asp:ListItem Value="-1"> -- Choose a Category </asp:ListItem> </asp:DropDownList>

Adems necesitamos establecer el AppendDataBoundItems del DropDownList en True, porque cuando las categoras son enlazadas al DropDownList desde el ObjectDataSource sobrescriben cualquier elemento agregado de forma manual a la lista si AppendDataBoundItems es False

Figura 12. Establezca la propiedad AppendDataBoundItems en True Despus de estos cambios, la primera vez que visite la pgina esta seleccionada la opcin -Seleccione una categora- y no se muestra ningn producto.

Figura 13. En la pgina inicialmente no se carga ningn producto La razn por la que no se muestra ningn producto cuando esta seleccionada la opcin -Seleccione una categora- se debe a que su valor es -1 y no existe ningn producto en la base de datos cuyo CategoryID sea -1. Si este es el comportamiento que desea, entonces en este momento ha terminado, sin embargo, si desea visualizar los

productos de todas las categoras cuando el elemento de la lista -Seleccione una categora- es seleccionado, regrese a la clase ProductsBLL y personalice el mtodo GetProductsByCategoryID (categoryID) para que invoque el mtodo GetProducts () si el parmetro CategoryID que se pase es menor a cero:
Public Function GetProductsByCategoryID(categoryID As Integer) _ As Northwind.ProductsDataTable If categoryID < 0 Then Return GetProducts() Else Return Adapter.GetProductsByCategoryID(categoryID) End If End Function

La tcnica utilizada aqu es similar al enfoque empleado para mostrar todos los proveedores en el tutorial Declaracin de Parmetros, aunque para este ejemplo estamos usando un valor de -1 en lugar de Null para indicar que se deben recuperar todos los productos. Esto es porque el parmetro categoryID del mtodo GetProductsByCategoryID (categoryID) espera que se le pase un valor entero, mientras que en el tutorial de Declaracin de Parmetros se tena un parmetro de entrada que esperaba que se le pasara una cadena. La figura 14 muestra una captura de pantalla de FilterByDropDownList.aspx cuando la opcin -Seleccione una categora- esta seleccionada. Aqu se muestran de forma predeterminada todos los productos y el usuario puede reducir la lista seleccionando una categora especfica.

Figura 14. Ahora aparecen todos los productos de forma predeterminada Resumen Al mostrar los datos relacionados jerrquicamente, a menudo ayuda presentar los datos utilizando informes maestro/detalle desde los cuales el usuario puede empezar a leer los datos desde la parte alta de la jerarqua y luego profundizar en los detalles. En este tutorial examinamos la construccin de un informe maestro/detalle simple que muestra los productos de una categora seleccionada. Esto se logr mediante un DropDownList para mostrar las categoras y un GridView para mostrar los productos que pertenecen a la categora seleccionada.

8. FILTRADO MAESTRO/DETALLE CON DOS DROPDOWNLISTS


En este tutorial ampliaremos la relacin maestro/detalle agregando una tercera capa, usando dos controles DropDownList para seleccionar los registros padres y abuelos que se deseen. Introduccin En el tutorial anterior examinamos como mostrar un informe maestro/detalle sencillo con un nico DropDownList poblado con las categoras y un GridView que muestra los productos que pertenecen a la categora seleccionada. Este enfoque del reporte funciona muy bien cuando visualizamos los registros que tienen una relacin uno a varios y puede ser fcilmente ampliado para trabajar en escenarios que incluyen mltiples relaciones uno a varios. Por ejemplo un sistema de ingreso de pedidos que tengan tablas que correspondan a los clientes, rdenes y productos en lnea. Un determinado cliente puede tener varios pedidos y cada pedido consta de varios elementos. Estos datos se pueden presentar al usuario con dos DropDownLists y un GridView. El primer DropDownList tendr una lista de elementos para cada cliente en la base de datos, el segundo contiene los pedidos del cliente seleccionado. Un GridView listara los elementos del pedido seleccionado. Si bien la base de datos Northwind incluye una informacin canonca

cliente/pedido/detalle de pedido en sus tablas Customers, Orders, Order Details, estas tablas no son capturadas en nuestra arquitectura. Sin embargo podemos ilustrarlo con dos DropDownList dependientes. El primer DropDownList muestra las categoras y el segundo muestra los productos que pertenecen a la categora seleccionada Luego el DetailsView recoge los detalles del producto seleccionado. Paso 1: Crear y poblar el control DropDownList de las categoras Nuestro primer objetivo es agregar un DropDownList que muestre las categoras. Estos pasos fueron examinados con detalle en el tutorial anterior, pero los resumiremos aqu para realizar el proceso. Abra la pgina MasterDetailsDetails.aspx de la carpeta Filtering, agregue un DropDownList a la pgina, configure su propiedad ID en Categories y luego haga clic en el enlace configurar origen de datos en su etiqueta inteligente. Desde el asistente de configuracin de origen de datos seleccione agregar un nuevo origen de datos.

Figura 1. Agregue un nuevo origen de datos al DropDownList El nuevo origen de datos debe ser naturalmente un ObjectDataSource. Llame este nuevo ObjectDataSource CategoriesDataSource y haga que el objeto invoque el mtodo GetCategories () de la clase CategoriesBLL.

Figura 2. Escoja usar la clase CategoriesBLL

Figura 3. Configure el ObjectDataSource para que utilice el mtodo GetCategories () Despus de configurar el ObjectDataSource, aun tenemos que definir qu campo del origen de datos se debe mostrar en el DropDownList Categories y cual debe configurarse como el valor de los elementos de la lista. Establezca CategoryName como el campo para mostrar y CategoryID como el valor de cada elemento de la lista.

Figura 4. Haga que el DropDownList muestre el campo CategoryName y use como valor el CategoryID

Hasta este momento tenemos un control DropDownList (Categories) poblado con los registros de la tabla Categories. Queremos que cuando el usuario escoja una nueva categora del DropDownList se produzca una devolucin de datos con el fin de que se actualice el DropDownList Products que vamos a crear en el paso 2. Por lo tanto seleccione la opcin Habilitar AutoPostBack en la etiqueta inteligente del DropDownList Categories.

Figura 5. Habilite AutoPostBack del DropDownList Categories Paso 2: Visualizacin de los productos de la categora seleccionada en un segundo DropDownList Con el DropDownList Categories terminado, nuestro siguiente paso es mostrar un DropDownList de los productos que pertenecen a la categora seleccionada. Para ello, agregue otro DropDownList a la pgina denominado ProductsByCategory. Al igual que con el DropDownList Categories, cree un nuevo ObjectDataSource para el DropDownList ProductsByCategory llamado ProductsByCategoryDataSource.

Figura 6. Agregar un nuevo origen de datos al DropDownList ProductsByCategory

Figura 7. Crear un nuevo ObjectDataSource llamado ProductsByCategoryDataSource Como el DropDownList necesita mostrar solamente los productos que pertenecen a la categora seleccionada, haga que el ObjectDataSource invoque el mtodo GetProductsByCategoryID (categoryID) de la clase ProductsBLL.

Figura 8. Opte por utilizar la clase ProductsBLL

Figura 9. Configure el ObjectDataSource para que utilice el mtodo GetProductsByCategoryID (categoryID) En el ltimo paso del asistente es necesario especificar el valor del parmetro CategoryID. Asigne este parmetro al elemento seleccionado de la lista del DropDownList Categories.

Figura 10. Tome el valor del parmetro categoryID del DropDownList Categories Con el ObjectDataSource configurado, todo lo que queda es especificar los campos del origen de datos que se utilizaran para mostrar y como valor de los elementos de la lista del DropDownList. Utilice el campo ProductName para mostrar y ProductID como valor.

Figura 11. Especifique los campos del origen de datos usado por las propiedades Text y Value del ListItem del control DropDownList Con el ObjectDataSource y el DropDownList ProductsByCategory configurados, la pgina mostrar dos DropDownList: el primero muestra las categoras, mientras que el segundo muestra los productos que pertenecen a la categora seleccionada. En primer lugar cuando el usuario selecciona una categora, se produce una devolucin de datos y el segundo DropDownList se actualiza, mostrando los productos que pertenecen a la categora que acaba de seleccionar. Las figuras 12 y 13 muestran la pgina MasterDetailsDetails.aspx en accin, vista a travs de un navegador.

Figura 12. Cuando visite por primera vez la pgina, la categora bebidas esta seleccionada

Figura 13. Seleccionando una nueva categora vemos los productos de la nueva categora Actualmente cuando se cambia el DropDownList ProductsByCategory, no se produce ninguna devolucin de datos. Sin embargo queremos que se produzca una devolucin de datos una vez agreguemos el DetailsView para mostrar los detalles del producto seleccionado (paso 3). Por lo tanto verifique que la opcin Habilitar AutoPostBack en la etiqueta inteligente del DropDownList ProductsByCategory este seleccionada.

Figura 14. Active la funcin AutoPostBack del DropDownList ProductsByCategory

Paso 3: Utilice un DetailsView para mostrar los detalles del producto seleccionado El paso final consiste en mostrar los detalles del producto seleccionado en un DetailsView. Para realizar esto, agregue un DetailsView a la pgina, configure su propiedad ID en ProductDetails y utilice un nuevo ObjectDataSource. Configure este ObjectDataSource para que tome sus datos del mtodo GetProductByProductID (productID) de la clase ProductsBLL que utilice el valor seleccionado en el DropDownList ProductsByCategory como el valor del parmetro productID.

Figura 15. Seleccione utilizar la clase ProductsBLL

Figura 16. Configure el ObjectDataSource para que utilice el mtodo GetProductByProductID (productID)

Figura 17. Tome el valor del parmetro productID del DropDownList ProductsByCategory Usted puede optar por mostrar cualquiera de los campos disponibles en el DetailsView ProductDetails. Hemos optado por quitar los campos ProductID, SupplierID y CategoriesID y reordenar y dar formato a los campos restantes. Adems limpiamos las propiedades Height y Width, permitiendo que el DetailsView ample el ancho necesario

para mostrar mejor los datos, en lugar de obligarlo a tener un tamao especfico. El marcado completo aparece a continuacin:
<asp:DetailsView ID="ProductDetails" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <Fields> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="Units In Stock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="Units On Order" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="Reorder Level" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Fields> </asp:DetailsView>

Tmese un momento para probar la pgina MasterDetailsDetails.aspx en un navegador. A primera vista puede parecer que todo funciona como queremos, pero hay un problema sutil. Cuando se elige una nueva categora, el DropDownList ProductsByCategory se actualiza para incluir los productos de la categora seleccionada, pero el DetailsView ProductDetails continua mostrando la informacin del producto anterior. El DetailsView es actualizado cuando seleccionamos un producto diferente para la categora seleccionada. Por otra parte si bien la prueba es suficiente, usted encontrar que si elige continuamente nuevas categoras (como la eleccin de bebidas

del DropDownList Categories, luego condimentos. Luego confites) cada nueva seleccin de otra categora hace que el DetailsView ProductDetails se actualice. Para ayudar a concretar este problema, veamos un ejemplo especfico. La primera vez que visite la pgina, la categora bebidas ha sido seleccionada y los productos relacionados con ella son cargados al DropDownList ProductsByCategory. Chai es el producto seleccionado y sus detalles se muestran en el DetailsView ProductDetails como se observa en la figura 18.

Figura 18. Los detalles del producto seleccionado son mostrados en un DetailsView Si cambia la categora seleccionada bebidas por condimentos, se produce una devolucin Chai. de datos y en consecuencia se actualiza el DropDownList PorductsByCategory, pero el DetailsView sigue mostrando los detalles del Producto

Figura 19. Los datos del producto seleccionado antes continan vindose Escoger un nuevo producto de la lista actualiza el DetailsView como esperbamos. Si selecciona una categora despus de cambiar el producto, una vez ms el DetailsView no se actualizar. Sin embargo si en vez de escoger un nuevo producto, usted selecciona una nueva categora; El DetailsView volver a actualizarse, entonces Qu diablos est pasando aqu? El problema es un error de tiempo en el ciclo de vida de la pgina. Cada vez que se solicita una pgina se procede a travs de una serie de medidas para su presentacin. En uno de estos pasos, los controles ObjectDataSource verifican para ver si alguno de sus valores SelectParameters ha cambiado. Si es as, el control de datos web enlazado al ObjectDataSource sabe que necesita actualizar su pantalla. Por ejemplo cuando se selecciona una nueva categora, el ObjectDataSource ProductsByCategoryDataSource detecta que los valores de sus parmetros han cambiado y vuelve a enlazar el DropDownList, obteniendo los productos de la categora seleccionada. El problema que se plantea en esta situacin es que el punto en el ciclo de vida de la pgina en el que los ObjectDataSource verifican el cambio de sus parmetros se produce antes que se re consoliden los datos asociados a los controles web. Por lo tanto al seleccionar una nueva categora el ObjectDataSource ProductsByCategoryDataSource detecta un cambio en el valor de su parmetro. Sin embargo el ObjectDataSource usado por el DetailsView ProductDetails no nota ninguno

de estos cambios porque el DropDownList ProductsByCategory no ha sido re enlazado aun. Despus en el ciclo de vida de la pgina el DropDownList ProductsByCategory vuelve a enlazar su ObjectDataSource tomando los productos para la nueva categora seleccionada. Mientras que el valor del DropDownList ProductsByCategory ha cambiado, el ObjectDataSource del DetailsView ProductDetails ya ha hecho su verificacin de valores de parmetros, por lo tanto el DetailsView, muestra los resultados anteriores. Esta interaccin se muestra en la figura 20.

Figura 20. El valor del DropDownList ProductsByCategory cambia luego que el ObjectDataSource del DetailsView ProductDetails verifica lo cambios Para remediar esta situacin, tenemos que volver a vincular el DetailsView ProductDetails explcitamente luego que el DropDownList ProductsByCategory se haya consolidado. Podemos realizar esto llamando al mtodo DataBind () del DetailsView ProductDetails cuando se genere el evento DataBound del DropDownList ProductsByCategory. Agregue el siguiente cdigo de marcado al controlador de eventos de la clase subyacente de la pgina de cdigo de MasterDetailsDetails.aspx (Refirase a Establecer eventos):
Protected Sub ProductsByCategory_DataBound(sender As Object, e As EventArgs) _ Handles ProductsByCategory.DataBound ProductDetails.DataBind() End Sub

mediante

programacin

los

valores

de

los

parmetros

del

ObjectDataSource para una discusin acerca de cmo agregar un controlador de

Despus de agregar esta llamada explicita al mtodo DataBind () del DetailsView ProductDetails, el tutorial funciona como esperbamos. La figura 21 muestra como este cambio remedia nuestro anterior problema.

Figura 21. El DetailsView ProductDetails es explcitamente actualizado cuando se emite el evento DataBound del DropDownList ProductsByCategory Resumen El DropDownList sirve como un elemento de interfaz de usuario ideal para informes maestro/detalle donde existe una relacin uno a varios entre los registros maestros y detalles. En el tutorial anterior vimos como utilizar un solo DropDownList para filtrar los productos que pertenecen a la categora seleccionada. En este tutorial hemos remplazado el GridView con un DropDownList de productos y utilizamos un DetailsView para mostrar los detalles del producto seleccionado. Los conceptos discutidos en este tutorial se pueden ampliar fcilmente a los modelos de datos relacionados con varias relaciones uno a varios, tales como los clientes, pedidos y artculos ordenados. En general usted siempre puede agregar a un DropDownList para cada una de las entidades de las relaciones uno a varios.

9. FILTRADO MAESTRO/DETALLE A TRAVS DE DOS PGINAS


En este tutorial implementaremos este enfoque usando un GridView para mostrar los proveedores en la base de datos. Cada fila de proveedores en el GridView contendr un enlace Ver Productos que al hacer clic sobre el lleva a los usuarios a otra pgina que enumera los productos para el proveedor especificado. Introduccin En los ltimos dos tutoriales vimos como mostrar informes maestro/detalle en una pgina web utilizando DropDownLists para mostrar los registros maestros y controles GridView o DetailsView para mostrar los detalles. Otro enfoque comn utilizado para los informes maestro/detalles es tener los registro maestros en una pgina y mostrar los detalles en otra. Un sitio web de foros, como los foros Asp.Net es un gran ejemplo de este enfoque en la prctica. Los foros Asp.Net se componen de una variedad de foros de introduccin, formularios WebForms, Controles de presentacin de datos y as sucesivamente. Cada foro est compuesto de muchos hilos y cada hilo se compone de un nmero de anuncios. En la pgina principal de foros Asp.Net, se enumeran los foros. Al hacer clic en un foro nos conduce a una pgina ShowForum.aspx, que muestra los hilos de ese foro. As mismo al hacer clic en un tema te lleva a ShowPost.aspx que muestra los mensajes del tema sobre el que se hizo clic. En este tutorial aplicaremos este enfoque mediante el uso de un GridView para mostrar los proveedores en la base de datos. Cada fila de proveedores en el GridView contendr un enlace Ver Productos que cuando se hace clic sobre l, llevar al usuario a otra pgina que enumera los productos del proveedor seleccionado. Paso 1: Agregar las pginas SupplierListMaster.aspx y ProductsForSupplierDetails.aspx a la carpeta Filtering Al definir el diseo de la pgina hemos aadido una serie de pginas de arranque en las carpetas BasicReporting, Filtering y Customized Formatting. Sin embargo en ese momento no aadimos una pgina de inicio para este tutorial, as que tmese un momento para agregar dos nuevas pginas a la carpeta Filtering: SupplierListMaster.aspx y ProductsForSupplierDetails.aspx. SupplierListMaster mostrar los registros maestros (Los proveedores), mientras que ProductsForSupplierDetails mostrar los productos del proveedor seleccionado. Al crear estas dos pginas asegrese de asociarlos a la pgina maestra Site.Master.

Figura 1. Agregar las pginas SupplierListMaster.aspx y ProductsForSupplierDetails.aspx a la carpeta Filtering Adems al aadir las pginas asegrese de actualizar el archivo del mapa del sitio Web.SiteMap correctamente. Para este tutorial solo tenemos que aadir la pgina SupplierListMaster.aspx al mapa del sitio, usando el siguiente contenido XML como un hijo del elemento <SiteMapNode> Filtering Reports:
<siteMapNode url="/Filtering/SupplierListMaster.aspx" title="Master/Detail Across Two Pages" description="Master records on one page, detail records on another." />

Nota: Usted puede ayudar a automatizar el proceso de actualizacin del archivo del mapa del sitio al aadir nuevas pginas Asp.Net usando gratis el SiteMapMacro de Visual Studio de K. Scott Allen. Paso 2: Visualizacin de la lista de proveedores SupplierListMaster.aspx Con las pginas SupplierListMaster.aspx y ProductsForSuppliers.aspx creadas, nuestro siguiente paso es crear el GridView de los proveedores en SupplierListMaster.aspx. Aada un control GridView a la pgina y enlcelo a un nuevo ObjectDataSource. Este ObjectDataSource debe utilizar el mtodo GetSuppliers () de la clase SuppliersBLL para devolver todos los proveedores.

Figura 2. Seleccione la clase SuppliersBLL

Figura 3. Configure el ObjectDataSource para que use el mtodo GetSuppliers () Debemos incluir un enlace titulado Ver Productos en cada dila del GridView, para que cuando se haga clic lleve al usuario a ProductsForSuppliersDetails.aspx pasando el valor de SupplierID de la fila seleccionada empleando una cadena de consulta. Por ejemplo si el usuario hace clic en el enlace Ver Productos de Comerciantes de Tokio (que tiene un valor de SupplierID de 4), deben ser enviados a ProductsForSuppliersDetails.aspx?SupplierID=4. Para lograr esto, agregue un HiperLinkField al GridView, el cual agrega un hipervnculo a cada fila del GridView. Comience haciendo clic en el enlace Editar columnas de la etiqueta inteligente del GridView. Luego seleccione el HiperLinkField de la lista en la

parte superior izquierda y haga clic en agregar para incluir el HiperLinkField en la lista de campos del GridView.

Figura 4. Agregar un HiperLinkField al GridView El HiperLinkFied puede ser configurado para utilizar el mismo texto o valores Url del link en cada fila del GridView, o puede basar estos valores en los valores de datos enlazados de cada fila particular. Para especificar un valor esttico en todas las filas utilice las propiedades Text o NavigateUrl del HiperLinkFied. Como queremos que el texto del enlace sea el mismo para todas las filas, establezca la propiedad Text del HiperLinkField en Ver Productos.

Figura 5. Establezca la propiedad Text del HiperLinkField en Ver Productos

Para establecer el texto o los valores Url basados en los datos subyacentes enlazados a la fila del GridView, especificamos los campos de datos, texto o valores Url que deben ser sacados de las propiedades DataTextField o DataNavigateUrlFields. El DataTextField solo puede establecerse como un nico campo de datos, sin embargo, DataNavigateUrlFields puede establecer una lista de campos de datos delimitada por comas. Con frecuencia necesitamos basar el texto o la direccin Url en una combinacin del valor del campo de datos de la fila actual y algn marcado esttico. En este tutorial por ejemplo, queremos que la direccin de los vnculos del HiperLinkField sean ProductsforSupplierDetails.aspx?SupplierID=supplierID, donde supplierID es el valor de SupplierID de cada fila del GridView. Tenga en cuenta que necesitamos tanto el marcado esttico como los valores de datos impulsados aqu: la parte del Url del Link ProductsforSupplierDetails.aspx?SupplierID= fila. Para indicar una combinacin de valores estticos y datos impulsados, utilice las propiedades el valor DataTextFormatString del campo y DataNavigateUrlFormatString. en las propiedades En estas o propiedades ingrese el marcado esttico necesario y utilice el marcador {0} si desea que especificado DataTextField DataNavigateUrlFields aparezca. Si la propiedad DataNavigateUrlFields cuenta con varios campos especificados, use {0} si desea el valor del primer campo insertado, {1} para el valor del segundo campo y as sucesivamente. Aplicando esto a nuestro tutorial, tenemos que establecer la propiedad es esttica, mientras que la parte supplierID es el valor impulsado ya que su valor es el valor SupplierID propio de cada

DataNavigateUrlFields a SupplierID, ya que es el campo de datos cuyo valor necesitamos especificar para cada fila y la propiedad DataNavigateUrlFormatString en ProductsforSupplierDetails.aspx?SupplierID={0}.

Figura 6. Configure el HiperLinkField para incluir el hipervnculo adecuado, basndose en el SupplierID Despus de aadir el HiperLinkField, no dude en personalizar y ordenar los campos del GridView. El siguiente marcado muestra el control GridView despus de que se la han hecho algunas modificaciones menores a nivel de campo.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="SupplierID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <Columns> <asp:HyperLinkField DataNavigateUrlFields="SupplierID" DataNavigateUrlFormatString= "ProductsForSupplierDetails.aspx?SupplierID={0}" Text="View Products" /> <asp:BoundField DataField="CompanyName" HeaderText="Company" SortExpression="CompanyName" /> <asp:BoundField DataField="City" HeaderText="City" SortExpression="City" /> <asp:BoundField DataField="Country" HeaderText="Country" SortExpression="Country" /> </Columns> </asp:GridView>

Tmese un momento para ver la pgina SupplierListMaster.aspx en un navegador. La figura 7 muestra actualmente la lista de todos los proveedores incluyendo un enlace Ver Productos. Al hacer clic en uno de los enlaces Ver Productos nos llevar a

ProductsForSupplierDetails.aspx, pasando el SupplierID del proveedor en la cadena de consulta.

Figura 7. Cada fila de Proveedor contiene un enlace Ver Productos Paso 3: Mostrar los productos de los proveedores en la pgina

ProductsForSupplierDetails.aspx Hasta el momento la pgina SupplierListMaster.aspx est enviando a los usuarios a ProductForSupplierDetails.aspx pasando el SupplierID del proveedor seleccionado a travs de una cadena de consulta. El paso final del tutorial es mostrar los productos en un GridView en la pgina ProductsForSupplierDetails.aspx cuyo SupplierID es igual al SupplierID pasada a travs de la cadena de consulta. Para realizar esto, comience agregando un GridView a la pgina ProductsForSupplierDetails.aspx, con un nuevo control ObjectDataSource llamado ProductsBySupplierDataSource que invoque el mtodo GetProductsBySupplierID (supplierID) de la clase ProductsBLL.

Figura 8. Agregar un nuevo ObjectDataSource llamado ProductsBySupplierDataSource

Figura 9. Seleccione la clase ProductsBLL

Figura 10. Haga que el ObjectDataSource invoque el mtodo GetProductsBySupplierID (supplierID) El paso final del asistente de configuracin de origen de datos nos solicita que proporcionemos el origen del parmetro supplierID del mtodo GetProductsBySupplierID (supplierID). Para utilizar el valor de la cadena de consulta establecemos el origen del parmetro en cadena de consulta e introducimos el nombre del valor de la cadena de consulta que utilizaremos en el cuadro de texto QueryStringField (SupplierID).

Figura 11. Poblar el valor del parmetro supplierID desde el valor de la cadena de consulta SupplierID Eso es todo lo que hay que hacer! La figura 12 muestra la pgina

ProductsForSupplierDetails.aspx cuando la visitamos haciendo clic en el enlace Comerciantes de Tokio desde la pgina SupplierListMaster.aspx.

Figura 12. Se muestran los productos suministrados por Comerciantes de Tokio Mostrar la informacin del proveedor en ProductsForSupplierDetails.aspx En la figura 12 se observa que la pgina ProductsForSupplierDetails.aspx se limita a enumerar los productos que son suministrados por el SupplierID especificado en la cadena de consulta. Sin embargo, si alguien es enviado directamente a esta pgina no

sabra que los productos que se muestran en la Figura 12 son los productos proporcionados por los Comerciantes de Tokio. Para remediar esto podemos mostrar tambin la informacin del proveedor. Comencemos agregando un FormView encima del GridView de los productos. Cree un nuevo control ObjectDataSource llamado SuppliersDataSource que invoque el mtodo GetSupplierBySupplierID (supplierID) de la clase SuppliersBLL.

Figura 13. Seleccione la clase SuppliersBLL

Figura 14. Haga que el ObjectDataSource invoque el mtodo GetSupplierBySupplierID (supplierID) Al igual que con el ProductsBySupplierDataSource, tome el parmetro supplierID asignado al valor SupplierID de la cadena de consulta.

Figura 15. Rellene el valor del parmetro SupplierID con el valor SupplierID de la cadena de consulta

Cuando enlazamos el FormView al ObjectDataSource en la vista diseo, Visual Studio crea automticamente el ItemTemplate, InsertTemplate y EditItemTemplate con controles Label y controles TextBox para cada uno de los campos devueltos por el ObjectDataSource. Puesto que solo queremos mostrar la informacin del Proveedor no dude en retirar el InsertTemplate y el EditItemTemplate. Luego modifique la propiedad ItemTemplate para mostrar el nombre de la compaa del proveedor en un elemento <h3> y la direccin, ciudad, pas y nmero de telfono debajo del nombre de la empresa. Si lo prefiere puede configurar manualmente el DataSourceID del FormView y crear el marcado del ItemTemplate, como lo hicimos en el tutorial Mostrar datos con el ObjectDataSource. Luego de estas modificaciones, el marcado declarativo del FormView debe ser similar al siguiente:
<asp:FormView ID="FormView1" runat="server" DataKeyNames="SupplierID" DataSourceID="suppliersDataSource" EnableViewState="False"> <ItemTemplate> <h3><%# Eval("CompanyName") %></h3> <p> <asp:Label ID="AddressLabel" runat="server" Text='<%# Bind("Address")%>'> </asp:Label> <br/> <asp:Label ID="CityLabel" runat="server" Text='<%# Bind("City") %>'> </asp:Label>, <asp:Label ID="CountryLabel" runat="server" Text='<%# Bind("Country") %>'></asp:Label><br /> Phone: <asp:Label ID="PhoneLabel" runat="server" Text='<%# Bind("Phone") %>'></asp:Label> </p> </ItemTemplate> </asp:FormView>

La

Figura

16

muestra

una

captura

de

pantalla

de

la

pgina

ProductsForSupplierDetails.aspx despus de que se ha incluido el detallado de la informacin del proveedor ms arriba.

Figura 16. La lista de productos incluye un resumen del Proveedor Aplicacin de toques finales para la interfaz de usuario

ProductsForSuppliersDetails.aspx Para mejorar la experiencia del usuario de este informe, hay un par de adiciones que debemos hacer a la pgina ProductsForSuppliersDetails.aspx. Actualmente la nica manera que un usuario pueda pasar de la pgina ProductsForSuppliersDetails.aspx a la lista de proveedores es hacer clic en el botn atrs de su navegador. Agregaremos un control HiperLink a la pgina ProductsForSuppliersDetails.aspx que enlazara nuevamente a SupplierListMaster.aspx, proporcionando otra manera de que el usuario regrese a la lista principal.

Figura 17. Agregue un control HiperlinkField para que el usuario regrese a la pginaSuplierListMaster.aspx

Si el usuario hace clic en el enlace Ver los Productos de un proveedor que no tiene ningn tipo de productos, el ObjectDataSource ProductsBySupplierDataSource en la pgina ProductsForSuppliersDetails.aspx no devolver ningn resultado. El GridView enlazado al ObjectDataSource no tendr ningn marcado resultando una regin en blanco en la pgina del navegador del usuario. Para comunicarle con mayor claridad al usuario que no hay productos asociados con el proveedor seleccionado, podemos establecer la propiedad EmptyDataText del GridView para que aparezca un mensaje que queramos cuando ocurra una situacin as. En esta propiedad se ha establecido No hay productos ofrecidos por este proveedor. Por defecto todos los proveedores de la base de datos Northwind proporcionan por lo menos un producto. Sin embargo para este tutorial hemos modificado la tabla Products para que el proveedor Escargots Nouveaux ya no est asociado con ningn producto. La Figura 18 muestra la pgina de detalles de Escargots Nouveaux despus de que sea realizado este cambio.

Figura 18. Los usuarios son informados que el proveedor no proporciona ningn producto Resumen Aunque los informes Maestro/Detalle pueden mostrar tanto los registros maestro y detalle en una sola pgina, en la mayora de sitios web se separan en dos pginas web. En este tutorial vimos como implementar un informe Maestro/Detalle teniendo los

proveedores listados en un GridView en la pgina maestro y los productos asociados mostrados en la pgina detalles. Cada fila de proveedores en la pgina web principal contiene un enlace a la pgina de detalles que pasan el valor SupplierID de la fila. Tales vnculos especficos de las filas se pueden agregar fcilmente utilizando HiperLinkField en el GridView. En la pgina detalles la recuperacin de los productos para el proveedor especificado se llevo a cabo mediante la invocacin del mtodo GetProductsBySupplierID (supplierID) de la clase ProductsBLL. El valor del parmetro SupplierID se especifica mediante declaracin utilizando una cadena de consulta como el origen de los parmetros. Tambin nos fijamos en la forma de mostrar los detalles del proveedor en la pgina detalles usando un FormView. Nuestro siguiente tutorial es el ltimo de reportes maestro/detalles. Veremos cmo mostrar una lista de Productos en un GridView donde cada fila tiene un botn de seleccin. Al hacer clic en el botn seleccionar mostrar los detalles de ese producto en un control DetailsView en la misma pgina.

10.

FILTRADO MAESTRO/DETALLE USANDO UN GRIDVIEW MAESTRO

SELECCIONABLE CON DETALLES DETAILVIEW


En este tutorial tendremos un GridView cuyas filas incluyen el nombre y el precio de cada producto, junto con un botn de seleccionar. Al hacer clic en el botn de seleccionar para un determinado producto se mostraran sus detalles en un control DetailsView en la misma pgina. Introduccin En el tutorial anterior vimos como crear un informe maestro/detalle usando dos pginas web: una pgina maestra la cual muestra una lista de los proveedores y una pgina detalle que muestra los productos proporcionados por el proveedor seleccionado. Este formato de reporte de dos pginas puede ser condensado en una pgina. En este tutorial tendremos un GridView cuyas filas incluyen el nombre y el precio de cada producto junto con un botn Select. Dando clic al botn Select de un producto en particular hacemos que sus detalles sean mostrados en un control DetailsView en la misma pgina.

Figura 1. Al dar clic en el botn Select mostramos los detalles del producto Paso 1: Crear un GridView Seleccionable

Recodemos que en el reporte maestro/detalle de dos pginas, cada registro maestro incluye un hipervnculo que cuando se hace clic sobre l, enva al usuario a la pgina de detalles pasando el valor del SupplierID de la fila seleccionada por medio de una cadena de consulta. El hipervnculo fue agregado a cada fila del GridView usando un HiperLinkField. Para un reporte maestro/detalle de una sola pgina, necesitaremos un Button para cada fila del GridView de forma que cuando sea presionado, muestre los detalles. El control GridView puede ser configurado para que incluya un botn Select para cada fila que origine una devolucin de datos y marque la fila para el SelectedRow del GridView. Comencemos agregando un control GridView a la pgina DetailsBySelecting.aspx de la carpeta Filtering, estableciendo su propiedad ID en ProductsGrid. Luego agregamos un nuevo ObjectDataSource llamado AllProductsDataSource que invoque el mtodo GetProducts () de la clase ProductsBLL.

Figura 2. Crear un ObjectDataSource llamado AllProductsDataSource

Figura 3. Utilice la clase ProductsBLL

Figura 4. Configure el ObjectDataSource para que invoque el mtodo GetProducts () Edite los campos del GridView removiendo todos los BoundFields a excepcin del BoundField ProductName y UnitPrice. Tambin sintase libre de personalizar estos BoundFields segn sea necesario, como por ejemplo dar formato al BoundField UnitPrice como moneda y cambiar las propiedades HeaderText de los BoundFields. Estos pasos pueden realizarse grficamente, haciendo clic en la opcin editar columnas en la etiqueta inteligente del GridView, o configurando manualmente la sintaxis declarativa.

Figura 5. Remover todos los BoundFields a excepcin de ProductName y UnitPrice El marcado final declarativo es el siguiente:
<asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="AllProductsDataSource" EnableViewState="False"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Unit Price" HtmlEncode="False" SortExpression="UnitPrice" /> </Columns> </asp:GridView>

Luego es necesario marcar el GridView como seleccionable, por lo cual necesitamos agregar un botn select a cada fila. Para realizar esto, simplemente seleccionamos la casilla Habilitar Seleccin en la etiqueta inteligente del GridView.

Figura 6. Marque como seleccionable las filas del GridView Seleccionando la opcin Habilitar Seleccin agregamos un CommandField al GridView con su propiedad ShowSelectButton establecida en True. Esto resulta en un botn Select para cada fila del GridView como se muestra en la figura 6. De forma predeterminada los botones de seleccin se representan como LinkButtons pero pueden emplearse en su lugar buttons o ImageButtons por medio de la propiedad ButtonType del CommandField.
<asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="AllProductsDataSource" EnableViewState="False"> <Columns> <asp:CommandField ShowSelectButton="True" /> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Unit Price" HtmlEncode="False" SortExpression="UnitPrice" /> </Columns> </asp:GridView>

Cuando se selecciona el botn Select de una fila del GridView se produce una devolucin de datos y la propiedad SelectedRow se actualiza. Adems de la propiedad

SelectedRow,

el

GridView

proporciona

otras

propiedades

como

SelectedIndex,

SelectedValue y SelectedDataKey. La propiedad SelectedIndex devuelve el ndice de la fila seleccionada, mientras que las propiedades SelectedValue y SelectedDataKey devuelven los valores basados en la propiedad DataKeyNames del GridView. La propiedad DataKeyNames se utiliza para asociar uno o ms valores de los campos de datos con cada fila y se utiliza comnmente para el atributo que identifica unvocamente la informacin de los datos subyacentes de cada fila del GridView. La propiedad SelectedValue devuelve el valor del primer campo de datos del DataKeyNames seleccionado, en tanto que la propiedad SelectedDataKey devuelve el objeto DataKey de la fila seleccionada, que contiene todos los valores de los campos de datos claves especificados para esa fila. La propiedad DataKeyNames se establece automticamente en el campo que identifica unvocamente los datos cuando enlazamos un Data Source a un GridView, DetailsView o FormView a travs del diseador. Si bien esta propiedad se ha establecido de forma automtica por nosotros en los tutoriales anteriores, los ejemplos podran haber trabajado sin especificar la propiedad DataKeyNames. Sin embargo, para el GridView seleccionable de este tutorial as como para los futuros tutoriales que examinaremos en insercin, actualizacin y eliminacin, la propiedad DataKeyNames debe establecerse correctamente. Tmese su tiempo para verificar y asegurarse que la propiedad DataKeyNames de su GridView est establecida en ProductID. Veremos nuestro progreso hasta el momento a travs de un navegador. Tenga en cuenta que el control GridView muestra el nombre y el precio de todos los productos, junto con un LinkButton de seleccionar. Al hacer clic en el botn Seleccionar se produce una devolucin de datos. En el paso 2 veremos cmo tener un DetailsView que responda a esta devolucin de datos, mostrando los detalles del producto seleccionado.

Figura 7. Cada fila del producto contiene un LinkButton Seleccionar Resaltando la fila seleccionada El GridView ProductsGrid tiene una propiedad SelectedRowStyle que se puede utilizar para dictar el estilo visual de la fila seleccionada. Usado adecuadamente, este puede mejorar la experiencia del usuario de una forma ms clara mostrando la fila del GridView que actualmente est seleccionada. Para este tutorial tendremos que la fila seleccionada se resaltara con un fondo en amarillo. Al igual que con nuestros tutoriales anteriores, trataremos de mantener la configuracin relacionada con la apariencia definida como clases CSS. Por lo tanto crearemos una nueva clase en CSS Styles.css llamada SelectedRowStyle.
.SelectedRowStyle { background-color: Yellow; }

Para aplicar esta clase CSS a la propiedad SelectedRowStyle de todos los GridView que se muestran en esta serie de tutoriales, editamos la capa GridView.skin en el tema DataWebControls para incluir la configuracin SelectedRowStyle como se muestra a continuacin:
<asp:GridView runat="server" CssClass="DataWebControlStyle"> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> <RowStyle CssClass="RowStyle" /> <HeaderStyle CssClass="HeaderStyle" /> <SelectedRowStyle CssClass="SelectedRowStyle" />

</asp:GridView>

Con esta adicin, la fila seleccionada del GridView se resalta con un fondo de color amarillo.

Figura 8. Personalizar la apariencia de la fila seleccionada usando la propiedad SelectedRowStyle del GridView Paso 2: Mostrar la informacin del producto seleccionado en un DetailsView Con el GridView ProductsGrid completo, todo lo que nos queda es aadir un DetailsView que muestre la informacin en concreto del producto seleccionado. Agregue un control DetailsView por encima del GridView y cree un nuevo ObjectDataSource llamado ProductDetailsDataSource. Dado que queremos que este DetailsView muestre la informacin sobre el producto seleccionado en particular, configure el ProductDetailsDataSource para que utilice el mtodo GetProductByProductID (productID) de la clase ProductsBLL.

Figura 9. Invocamos el mtodo GetProductByProductID (productID) de la clase ProductsBLL Haga que el valor del parmetro productID obtenga su valor de la propiedad SelectedValue del control GridView. Como ya comentamos anteriormente, la propiedad SelectedValue del GridView devuelve el primer valor de datos clave para la fila seleccionada. Por lo tanto es imperativo que la propiedad DataKeyNames del GridView se establezca en ProductID, de modo que el valor ProductID de la fila seleccionada sea devuelto por SelectedValue.

Figura 10. Ajuste el parmetro ProductID a la propiedad SelectedValue del GridView Una vez que el ObjectDataSource ProductDetailsDataSource se ha configurado y enlazado al DetailsView correctamente, este tutorial esta completo. Cuando se visita la pgina por primera vez, ninguna fila esta seleccionada, por lo cual la propiedad SelectedValue del GridView devuelve Nothing. Como no existen productos con un valor de ProductID de Null, no hay registros devueltos por el mtodo GetProductByProductID (productID) lo que significa que el DetailsView no se muestra (Ver Figura 11). Al hacer clic un el botn seleccionar de una fila del GridView, se produce una devolucin de datos y se actualiza el DetailsView. Esta vez la propiedad SelectedValue devuelve el ProductID de la fila seleccionada, el mtodo GetProductByProductID (productID) devuelve un ProductsDataTable con informacin acerca de ese producto en particular, y el DetailsView muestra los detalles (Ver Figura 12).

Figura 11. Cuando visitamos la pgina por primera vez se muestra el GridView

Figura 12. Al seleccionar una fila se muestra la informacin del producto Resumen En este y en los ltimos tres tutoriales hemos visto una serie de tcnicas para la visualizacin de informes maestro/detalle. En este tutorial examinamos un GridView seleccionable como hogar de los registros maestros y un DetailsView para visualizar los detalles del registro maestro seleccionado en la misma pgina. En los tutoriales anteriores vimos como mostrar informes maestro/detalle por medio de DropDownList y mostrar los registros maestros en una pgina y los detalles en otra. En este tutorial concluimos muestro examen de los informes maestro/detalles. Iniciamos en el siguiente tutorial la exploracin del formato personalizado en los controles GridView, FormView y DetailsView. Veremos cmo personalizar el formato de estos controles, basados en los datos enlazados a ellos, as como resumir datos en el pie de pgina de un GridView y cmo utilizar las plantillas para obtener un mayor grado de control sobre el diseo.

FORMATO PERSONALIZADO

11.

FORMATO PERSONALIZADO BASADO EN LOS DATOS

Ajustar el formato de los controles GridView, FormView y DetailsView basado en los datos enlazados a ellos se puede lograr de diferentes formas. En este tutorial veremos cmo realizar el formato de los datos enlazados por medio del uso de los controladores de eventos del RowDataBound y DataBound. Introduccin La apariencia de los controles GridView, FormView y DetailsView puede ser personalizada por medio de un gran nmero de propiedades relacionadas con el estilo. Propiedades como CssClass, Font, BorderWidth, BorderStyle, BorderColor, Width y Height, entre otros dictan el aspecto general del control. Las propiedades HeaderStyle, RowStyle, AlternatingRowStyle permiten establecer las mismas configuraciones de estilos para aplicarlos a secciones particulares. Del mismo modo estos ajustes de estilo pueden aplicarse a nivel de campo. Sin embargo en muchas situaciones los requisitos del formato dependen de los datos que se muestran. Por ejemplo para llamar la atencin sobre los productos fuera de existencia, podramos tener un reporte que muestre la informacin de los productos cuyo color de fondo sea amarillo para aquellos productos cuyos campos UnitsInStock y UnitsOnOrder son iguales a 0. Para destacar los productos ms costosos, es posible que deseemos mostrar los precios de los productos que cuestan ms de $75.00 en negrita. Ajustar el formato de los controles GridView, FormView y DetailsView basados en los datos enlazados a ellos puede realizarse de diferentes maneras. En este tutorial veremos cmo realizar el formato de los datos enlazados a travs del uso de los controladores de eventos del RowDataBound y DataBound. En el siguiente tutorial exploraremos un enfoque alternativo. Uso del controlador de eventos DataBound del control DetailsView Cuando los datos son enlazados a un DetailsView ya sea desde un control de origen de datos o mediante programacin por medio de la asignacin de datos a la propiedad DataSource del control y llamando a su mtodo DataBind (), se produce la siguiente secuencia de pasos:

1. Se desencadena el evento DataBinding del control de datos web 2. Los datos son enlazados al control de datos web 3. Se desencadena el evento DataBound del control de datos web La lgica personalizada puede ser ingresada inmediatamente despus de los pasos 1 y 3 por medio de un controlador de eventos. Al crear un controlador de eventos para el evento DataBound podemos determinar mediante programacin los datos que han sido enlazados al control de datos web y ajustar el formato si es necesario. Para ilustrar esto crearemos un DetailsView que muestre la informacin sobre un producto, pero que muestre el valor de UnitPrice en negrita, si este excede de 75.00 Paso 1: Visualizacin de la informacin del producto en un DetailsView Abra la pgina CustomColors.aspx en la carpeta CustomFormatting, arrastre un control DetailsView desde el cuadro de herramientas al diseador, establezca el valor de su propiedad ID a ExpensiveProductsPriceInBoldItalic y enlcelo a un nuevo ObjectDataSource que invoque el mtodo GetProducts () de la clase ProductsBLL. Los pasos detallados para lograr esto se omiten aqu por brevedad ya que fueron examinados con detalle en los tutoriales anteriores. Una vez que haya enlazado el ObjectDataSource al DetailsView, tmese un momento para modificar la lista de campos. Hemos optado por quitar los BoundFields ProductID, SupplierID, CategoryID, UnitsInStock, UnitsOnOrder, ReorderLevel y Discontinued y cambie el nombre y el formato de los BoundFields restantes. Tambin limpiamos la configuracin Width y Heigth. Como el DetailsView muestra nicamente un solo registro, tenemos que habilitar la paginacin con el fin de permitirle al usuario ver todos los productos. Hgalo seleccionando la casilla Habilitar paginacin en la etiqueta inteligente del DetailsView.

Figura 1. Habilite la casilla Habilitar paginacin en la etiqueta inteligente del DetailsView Despus de estos cambios, el marcado del DetailsView ser el siguiente:
<asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <Fields> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ..ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" ..SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" /> </Fields> </asp:DetailsView>

Tome un momento para verificar esto en su navegador.

Figura 2. El control GridView muestra un producto a la vez Paso 2: Determinar mediante programacin el valor de los datos en el controlador de eventos DataBound Con el fin de mostrar el precio en negrita cursiva para aquellos productos cuyo valor de UnitPrice supera los $75.00, tenemos primero que ser capaz mediante programacin de establecer el valor de UnitPrice. Para el DetailsView, esto se puede lograr por medio del controlador de eventos DataBound. Para crear el controlador de eventos, haga clic en el diseador del DetailsView, luego vaya a la ventana propiedades. Presione F4 para que aparezca si no es visible o vaya al men ver y seleccione la opcin ventana de propiedades del men. En la ventana propiedades haga clic en el icono del rayo para mostrar los eventos del DetailsView. Luego haga doble clic en el evento DataBound o escriba el nombre del controlador de eventos que desea crear.

Figura 3. Crear un controlador de eventos para el evento DataBound Si lo hace crear automticamente el controlador de eventos y te lleva a la parte del cdigo en el que se ha aadido. En este punto veremos:
Protected Sub ExpensiveProductsPriceInBoldItalic_DataBound _ (sender As Object, e As System.EventArgs) _ Handles ExpensiveProductsPriceInBoldItalic.DataBound End Sub

Nota: Tambin puede creer un controlador de eventos desde la porcin de cdigo de la pgina Asp.Net. All encontraras dos listas desplegables en la parte superior de la pgina. Seleccione el objeto en la lista desplegable de la izquierda y el evento en el que desea crear el controlador en la lista desplegable de la derecha y VisualStudio creara automticamente el controlador de eventos adecuado. Podemos acceder a los datos enlazados al DetailsView por medio de la propiedad DataItem. Recordemos que enlazamos nuestros controles a DataTables fuertemente tipiados, los cuales se componen de una coleccin de DataRow fuertemente tipiados. Cuando el DataTable es enlazado al DetailsView, el primer DataRow de la DataTable se asigna a la propiedad DataItem del DetailsView. En concreto la propiedad DataItem es asignada a un objeto DataRowView. Podemos utilizar la propiedad Row del DataRowView para tener acceso al objeto DataRow subyacente, que en realidad es una instancia ProductsRow. Una vez que tenemos esta instancia ProductsRow podemos

tomar nuestra decisin simplemente inspeccionando los valores de la propiedad del objeto. El siguiente cdigo muestra como determinar si el valor de UnitPrice enlazado al control DetailsView es mayor de $75,00:
Protected Sub ExpensiveProductsPriceInBoldItalic_DataBound _ (sender As Object, e As System.EventArgs) _ Handles ExpensiveProductsPriceInBoldItalic.DataBound Dim product As Northwind.ProductsRow = _ CType(CType(ExpensiveProductsPriceInBoldItalic.DataItem, _ System.Data.DataRowView).Row, Northwind.ProductsRow) If Not product.IsUnitPriceNull() AndAlso product.UnitPrice > 75 Then End If End Sub

Nota: Como UnitPrice puede tener valores nulos en la base de datos, primero debemos asegurarnos que no estamos tratando con un valor Null antes de acceder a la propiedad UnitPrice del ProductsRows. Esta verificacin es importante ya que si intentamos acceder a la propiedad UnitPrice cuando tiene un valor Null, el objeto arrojara una excepcin StrongTypingException. Paso 3: Dar formato al valor de UnitPrice en el DetailsView Hasta el momento podemos determinar si el valor UnitPrice enlazado al DetailsView tiene un valor que supera los $75,00, pero todava no podemos ver como se modifica el formato del DetailsView mediante programacin. Para modificar el formato de una fila completa en el DetailsView accedemos mediante programacin a la fila usando DetailsViewID.Rows (index) y para modificar una celda en particular accedemos usando DetailsViewID.Rows (index).Cells (index). Una vez que tenemos referencia a la fila o a la celda podemos ajustar su apariencia mediante la definicin de sus propiedades relacionadas al estilo. Acceder a una fila mediante programacin requiere que usted conozca el ndice de la fila, el cual comienza en 0. El UnitPrice es la quinta fila en el DetailsView dndole un ndice de 4 y accedemos a este mediante programacin usando ExpensiveProductsPriceInBoldItalic.Rows (4). Hasta el momento podramos tener que el

contenido de una fila entera aparezca en negrita y cursiva utilizando el siguiente cdigo:
ExpensiveProductsPriceInBoldItalic.Rows(4).Font.Bold = True ExpensiveProductsPriceInBoldItalic.Rows(4).Font.Italic = True

Sin embargo esto har que tanto la etiqueta (de precios) y el valor aparezcan en cursiva y negrita. Si deseamos que solo el valor aparezca en negrita y cursiva es necesario aplicar este formato a la segunda celda de la fila, lo cual se puede lograr de la siguiente forma:
ExpensiveProductsPriceInBoldItalic.Rows(4).Cells(1).Font.Bold = True ExpensiveProductsPriceInBoldItalic.Rows(4).Cells(1).Font.Italic = True

Hasta el momento en nuestros tutoriales hemos utilizado hojas de estilo para mantener una clara separacin entre lo que representa el marcado y la informacin relacionada con el estilo, en lugar de establecer las propiedades de estilo especificas como se muestra arriba utilizaremos en su lugar una clase CSS. Abra la hoja de estilos CSS Styles.css y agregue una nueva clase denominada ExpensivePriceEmphasis con la siguiente definicin:
.ExpensivePriceEmphasis { font-weight: bold; font-style: italic; }

Luego en el controlador de eventos DataBound, establezca la propiedad de celda CssClass en ExpensivePriceEmphasis. El siguiente cdigo muestro el controlador de eventos DataBound en su totalidad:
Protected Sub ExpensiveProductsPriceInBoldItalic_DataBound _ (sender As Object, e As System.EventArgs) _ Handles ExpensiveProductsPriceInBoldItalic.DataBound Dim product As Northwind.ProductsRow = _ CType(CType(ExpensiveProductsPriceInBoldItalic.DataItem, _ System.Data.DataRowView).Row, Northwind.ProductsRow) If Not product.IsUnitPriceNull() AndAlso product.UnitPrice > 75 Then ExpensiveProductsPriceInBoldItalic.Rows(4).Cells(1).CssClass = _ "ExpensivePriceEmphasis" End If

End Sub

Cuando vemos Chai, el cual tiene un precio menor de $75,00 este se muestra en una fuente normal (Ver Figura 4). Sin embargo cuando vemos Mishi Kobe Niku que tiene un precio de $97,00, el precio se muestra en cursiva y negrita (Ver Figura 5).

Figura 4. Los precios menores de $75,00 se muestran en una fuente normal

Figura 5. Los precios ms costosos se muestran en una fuente en negrita y cursiva Uso del controlador de Eventos DataBound del control FormView Los pasos para determinar los datos subyacentes enlazados al FormView, son idnticos a los del DetailsView, cree un controlador de eventos DataBound, convierta la propiedad DataItem al tipo de objeto apropiado enlazado al control y determine como

proceder. Sin embargo, el FormView y el DetailsView difieren en la forma de la apariencia de su interfaz de usuario cuando es actualizada. El FormView no contiene ningn BoundFields y por lo tanto carece de coleccin Rows. En cambio, un FormView se compone de plantillas, que pueden contener una mezcla de contenido HTML esttico, controles web y sintaxis de enlace de datos. Ajustar el estilo del FormView normalmente implica ajustar el estilo de uno o varios controles web en las plantillas del FormView. Para ilustrar esto, vamos a utilizar un FormView para mostrar los productos del ejemplo anterior, pero esta vez solo vamos a mostrar el nombre del producto y las unidades de existencia, con las unidades de existencia que sean menores de 10 apareciendo en rojo. Paso 4: Visualizacin de la informacin de productos en un FormView Agregue un FormView a la pgina CustomColors.aspx debajo del DetailsView y establezca su propiedad ID en LowStockedProductsInRed. Enlace el FormView al ObjectDataSource creado en el paso anterior. Esto crear un ItemTemplate, EditItemTemplate e InsertItemTemplate para el FormView. Retire el EditItemTemplate y el InsertItemTemplate y simplifique el ItemTemplate para que incluya solamente los valores ProductName y UnitsInStock, cada uno con su respectivo nombre en un control Label. Al igual que con el DetailsView del ejemplo anterior, consulte tambin la casilla Habilitar Paginacin en la etiqueta inteligente del FormView. Despus de estas ediciones el marcado de su FormView debe lucir de la siguiente manera:
<asp:FormView ID="LowStockedProductsInRed" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False"> <ItemTemplate> <b>Product:</b> <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Bind("ProductName") %>'> </asp:Label><br /> <b>Units In Stock:</b> <asp:Label ID="UnitsInStockLabel" runat="server"

Text='<%# Bind("UnitsInStock") %>'> </asp:Label> </ItemTemplate> </asp:FormView>

Tenga en cuenta que el ItemTemplate contiene:

HTML Esttico: el texto Producto y UnitsInStock junto con los elementos </br> y
<b>.

Controles web: Los dos controles label ProductNameLabel y UnitsInStockLabel. Sintaxis de enlace de datos: <%#Bind (ProductName)%> y <%#Bind
(UnitsInStock)%> que asigna los valores de estos campos a las propiedades de los controles Label.

Paso 5: Determinacin mediante programacin de los valores en el controlador de eventos DataBound Con el marcado del FormView completo, el siguiente paso es determinar mediante programacin si el valor de UnitsInStock en menor o igual a 10. Es se logra de la misma forma en el FormView que como se realizo con el DetailsView. Comience por crear un controlador de eventos para el evento DataBound del FormView.

Figura 6. Crear el controlador de eventos DataBound

En el controlador de eventos convertimos la propiedad DataItem en una instancia ProductsRow y determinamos si el valor UnitPrice es tal que tenemos que mostrar su fuente de color rojo.
Protected Sub LowStockedProductsInRed_DataBound _ (sender As Object, e As System.EventArgs) _ Handles LowStockedProductsInRed.DataBound Dim product As Northwind.ProductsRow = _ CType(CType(LowStockedProductsInRed.DataItem,System.Data.DataRowView).Row, _ Northwind.ProductsRow) If Not product.IsUnitsInStockNull() AndAlso product.UnitsInStock <= 10 Then Dim unitsInStock As Label = _ CType(LowStockedProductsInRed.FindControl("UnitsInStockLabel"), Label) If unitsInStock IsNot Nothing Then End If End If End Sub

Paso 6: Dar formato al control Label UnitsInStockLabel del ItemTemplate del FormView El ltimo paso es dar formato al valor que aparece en UnitsInStock en una fuente roja si el valor es menor o igual a 10. Para realizar esto es necesario acceder mediante programacin al control UnitsInStockLabel en el ItemTemplate y establezca sus propiedades de estilo de modo que su texto se muestre en rojo. Para acceder a un control web en una plantilla, utilice el mtodo FindControl (ControlID) de la siguiente forma:
Dim someName As WebControlType = _ CType(FormViewID.FindControl("controlID"), WebControlType)

Para

nuestro

ejemplo

queremos

acceder

un

control

Label

cuyo

ID

es

UnitsInStockLabel, por lo cual tendramos que utilizar:


Dim unitsInStock As Label = _ CType(LowStockedProductsInRed.FindControl("UnitsInStockLabel"), Label)

Una vez que tengamos referenciado el control web mediante programacin, podemos modificar sus propiedades relacionadas con el estilo segn sea necesario. Al igual que con el ejemplo anterior, hemos creado una clase CSS Styles.css llamado LowUnitsInStockEmphasis. Para aplicar este estilo al control web Label, establezca su correspondiente propiedad CssClass.
Protected Sub LowStockedProductsInRed_DataBound _ (sender As Object, e As System.EventArgs) _ Handles LowStockedProductsInRed.DataBound Dim product As Northwind.ProductsRow = _ CType(CType(LowStockedProductsInRed.DataItem, System.Data.DataRowView).Row, _ Northwind.ProductsRow) If Not product.IsUnitsInStockNull() AndAlso product.UnitsInStock <= 10 Then Dim unitsInStock As Label = _ CType(LowStockedProductsInRed.FindControl("UnitsInStockLabel"), Label) If unitsInStock IsNot Nothing Then unitsInStock.CssClass = "LowUnitsInStockEmphasis" End If End If End Sub

Nota: La sintaxis para dar formato a una plantilla mediante programacin accede al control web usando FindControl (controlID) y luego establece sus propiedades relacionadas con el estilo; tambin podemos utilizarlo cuando utilizamos TemplateFields en el control DetailsView o GridView. Examinaremos los TemplateFields en el prximo tutorial. La figura 7 muestra un FormView cuando muestra un producto cuyo UnitsInStock es mayor a 10, mientras que el producto de la figura 8 tiene un valor inferior a 10.

Figura 7. Los productos con suficientes unidades en existencia, no se les aplica el formato personalizado

Figura 8. Las unidades en existencia aparecen rojo para aquellos productos cuyo valor es menor o igual a 10 Dar Formato con el evento RowDataBound del GridView Anteriormente hemos examinado la secuencia de pasos de los controles DetailsView y FormView durante el progreso de enlace de datos. Miraremos una vez estos pasos una vez, muy por encima para recordar. 1. El control de datos web genera el evento DataBinding. 2. Los datos son enlazados al control web 3. El control de datos web desencadena el evento DataBound. Estos tres sencillos pasos son suficientes para el DetailsView y el FormView, ya que muestran un nico registro. Para el control GridView que muestra todos los registros enlazados a l (no solo el primero), el paso 2 es mucho ms complicado.

En el paso 2, el GridView enumera la fuente de datos y para cada registro crea una instancia GridViewRow que se une al registro actual de la misma. Para cada GridViewRow agregado a los controles GridView, se producen dos eventos:

RowCreated, se genera luego que se crea el GridViewRow RowDataBound, se genera despus que el registro actual es enlazado al
GridViewRow

Luego para el control GridView, el enlace de datos se describe ms exactamente por la siguiente secuencia de pasos: 1. El GridView genera el evento DataBinding. 2. Los datos son enlazados al control GridView. Para cada registro del origen de datos: 1. Se crea un objeto GridViewRow 2. Se genera el evento RowCreated 3. Se enlaza el registro a la GridViewRow 4. Se genera el evento RowDataBound 5. El GridView se agrega a la coleccin Rows 3. El GridView desencadena el evento DataBound. Para personalizar el formato de los registros individuales del GridView, tenemos que crear un controlador de evento para el evento RowDataBound. Para ilustrar esto, vamos a aadir un GridView a la pgina CustomColors.aspx que muestre el nombre, categora y precio de cada producto, destacando aquellos productos cuyo precio es inferior a $10,00 con un fondo de color amarillo. Paso 7: Visualizacin de informacin de los productos en un GridView Agregue un GridView por debajo del FormView del ejemplo anterior y establezca su propiedad ID en HighlightCheapProducts. Como ya tenemos un ObjectDataSource que devuelve todos los productos en la pgina, enlazaremos el GridView a l. Por ltimo modificamos los BoundFields del GridView para que incluya solamente los nombres de los productos, categoras y precios. Despus de estas ediciones el marcado del GridView ser similar a:

<asp:GridView ID="HighlightCheapProducts" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False" runat="server"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" /> </Columns> </asp:GridView>

La figura 9 muestra nuestro progreso hasta el momento cuando lo visualizamos a travs de un navegador.

Figura 9. El GridView muestra el nombre, categora y precio para cada producto Paso 8: Determinacin mediante programacin del valor de los datos en el controlador de eventos RowDataBound Cuando el ProductsDataTable es enlazado al GridView sus instancias ProductsRow se enumeran y para cada ProductRow se crea un GridViewRow. La propiedad DataItem del GridViewRow es asignada a un ProductsRow en particular, tras lo cual se produce el controlador de eventos RowDataBound del GridView. Para determinar el valor UnitPrice de cada producto enlazado al GridView es necesario crear un controlador de eventos

para el evento RowDataBound del GridView. Este controlador de eventos puede inspeccionar el valor UnitPrice para el actual GridViewRow y tomar una decisin del formato para esa fila. Este controlador de eventos se puede crear utilizando la misma serie de pasos que con los controles FormView y DetailsView.

Figura 10. Crear un controlador de eventos para el evento RowDataBound del GridView Crear el controlador de evento de esta forma hace que el siguiente cdigo sea agregado a la porcin de cdigo de la pgina Asp.Net:
Protected Sub HighlightCheapProducts_RowDataBound _ (sender As Object, e As System.Web.UI.WebControls.GridViewRowEventArgs) _ Handles HighlightCheapProducts.RowDataBound End Sub

Luego de que se genera el evento RowDataBound, el controlador de eventos pasa como un segundo parmetro un objeto de tipo GridViewRowEventArgs, que tiene una propiedad denominada Row. Esta propiedad devuelve una referencia al GridViewRow que acaba de enlazar los datos. Para acceder a la instancia ProductsRow enlazada al GridViewRow usamos la propiedad DataItem de este modo:

Protected Sub HighlightCheapProducts_RowDataBound _ (sender As Object, e As System.Web.UI.WebControls.GridViewRowEventArgs) _ Handles HighlightCheapProducts.RowDataBound Dim product As Northwind.ProductsRow = _ CType(CType(e.Row.DataItem,System.Data.DataRowView)Row,Northwind.ProductsRow) If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 10 Then End If End Sub

Cuando se trabaja con el controlador de eventos RowDataBound es importante tener en cuenta que el control GridView se compone de diferentes tipos de filas y que este evento se activa para todos los tipos de fila. Un tipo GridViewRow puede ser determinado por su propiedad RowType y puede tener uno de los posibles valores:

DataRow, una fila que esta enlazada a un registro del DataSource del GridView EmptyDataRow, la fila que aparece si el DataSource del GridView esta vacio Footer, el pie de pgina, aparece si la propiedad ShowFooter del GridView se
establece en True

Header, el encabezado de fila, se muestra si la propiedad ShowHeader del


GridView se establece en True (por defecto)

Pager, Implementa la paginacin del GridView, las filas que muestra la interfaz
de paginacin

Separator, no se utilizan para el control GridView, pero usado por las


propiedades RowType de los controles DataList y Repeater, dos controles de datos web que veremos en futuros tutoriales.

Como las filas EmptyDataRow, Footer, Header, y Pager no estn asociadas a un registro del DataSource, siempre tendrn un valor de Nothing para su propiedad DataItem. Por esta razn antes de intentar trabajar con la propiedad DataItem del GridViewRow actual, primero debe asegurarse de que estamos tratando con un DataRow. Este su puede lograr por el control de la propiedad RowType del GridViewRow del siguiente modo:
Protected Sub HighlightCheapProducts_RowDataBound _ (sender As Object, e As System.Web.UI.WebControls.GridViewRowEventArgs) _ Handles HighlightCheapProducts.RowDataBound If e.Row.RowType =DataControlRowType.DataRow Then

Dim product As Northwind.ProductsRow = _ CType(CType(e.Row.DataItem, System.Data.DataRowView).Row, Northwind.ProductsRow) If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 10 Then End If End If End Sub

Paso 9: Destacar la fila de color amarillo cuando el valor de UnitPrice sea menor de $10.00 El ltimo paso es resaltar mediante programacin el GridViewRow entero si el valor UnitPrice de esa fila es menor de $10.00. La sintaxis para acceder a las filas o celdas del GridView es lo mismo que con los DetailsView GridViewID.Rows (index) para acceder a toda la fila, GridViewID.Rows (index).Cells (index) para acceder a una celda en particular. Sin embargo cuando se genera el controlador de eventos RowDataBound, los datos enlazados al GridViewRow aun no se han agregado a la coleccin Rows del GridView. Por lo tanto, no podemos acceder a la instancia GridViewRow actual desde el controlador de eventos RowDataBound utilizando la coleccin Rows. En lugar de GridViewID.Rows (index) puede hacer referencia a la actual instancia GridViewRow en el controlador de eventos RowDataBound utilizando e.Row. Es decir para resaltar la instancia GridViewRow actual desde el controlador de eventos RowDataBound se debe utilizar:
e.Row.BackColor = System.Drawing.Color.Yellow

En lugar de establecer directamente la propiedad BackColor del GridViewRow, continuaremos con el uso de las clases CSS. Hemos creado una clase CSS llamada AffordablePriceEmphasis que establece el color del fondo en amarillo. El controlador de eventos RowDataBound completo es el siguiente:
Protected Sub HighlightCheapProducts_RowDataBound _ (sender As Object, e As System.Web.UI.WebControls.GridViewRowEventArgs) _ Handles HighlightCheapProducts.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then Dim product As Northwind.ProductsRow = _ CType(CType(e.Row.DataItem,System.Data.DataRowView).Row, Northwind.ProductsRow) If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 10 Then

e.Row.CssClass = "AffordablePriceEmphasis" End If End If End Sub

Figura 11. Los productos ms asequibles son resaltados en amarillo Resumen En este tutorial vimos como dar formato a los controles GridView, FormView y DetailsView basados en los datos enlazados al control. Para realizar esto creamos un controlador de eventos para los eventos DataBound y RowDataBound, donde se examinaron los datos subyacentes, junto con un cambio de formato de ser necesario. Para acceder a los datos enlazados a un DetailsView o un FormView, se utiliza la propiedad DataItem en el controlador de eventos DataBound; para un GridView, cada propiedad DataItem de las instancias del GridViewRow contiene los datos enlazados a la fila, los cuales estn disponibles en el controlador de eventos RowDataBound. La sintaxis para ajustar el formato de los controles web mediante programacin depende del control web y como el formato de los datos son mostrados en la pantalla. Para los controles DetailsView y GridView, las filas y las celdas pueden ser accedidas

por un ndice ordinal. Para el FormView que utiliza las plantillas, el mtodo FindControl (controlID) se utiliza comnmente para localizar un control web dentro de la plantilla. En el siguiente tutorial veremos cmo utilizar las plantillas con los controles GridView y DetailsView. Adems veremos otra tcnica para personalizar el formato basado en los datos subyacentes.

12.

USO DE PLANTILLAS DE CAMPO EN EL CONTROL GRIDVIEW

Para ofrecer mayor flexibilidad el control GridView ofrece el TemplateField, el cual se presenta usando una plantilla. Una plantilla puede incluir una combinacin de HTML esttico, controles web y sintaxis de enlace de datos. En este tutorial examinaremos como utilizar el TemplateField para lograr un mayor grado de personalizacin con el control GridView. Introduccin El GridView est compuesto de un conjunto de campos que indican que propiedades desde el origen de datos deben ser incluidas en la salida presentada junto con como los datos son mostrados. El tipo de campo ms sencillo es el BoundField, el cual muestra un valor de datos como texto. Otros tipos de campos muestran los datos usando elementos HTML alternativos. Por ejemplo el CheckBoxField se presenta como un CheckBox cuyo estado de verificacin depende del valor especificado por el campo de datos. El ImageField presenta una imagen cuyo origen de imagen est basado en un especfico campo de datos. Los HiperLink y Buttons cuyos estados dependen del valor de un campo subyacente, pueden ser presentados usando los tipos de campos HiperLinkField y ButtonField. Aunque los tipos de campo CheckBoxField, ImageField, HiperLinkField y ButtonField permiten una vista alternativa de los datos, se encuentran bastante limitados con respecto a su formato. Un CheckBoxField nicamente puede mostrar un solo CheckBoxField, al igual que un ImageField puede mostrar nicamente una sola imagen. Pero que sucede si un campo particular necesita mostrar algn texto, un CheckBox y una imagen, todos basados en diferentes valores de campos de datos? O que sucede si deseamos mostrar los datos usando un control Web diferente al CheckBox, Image, HiperLink o Button? Desafortunadamente, el BoundField limita su muestra a un solo campo de datos. Que sucede si deseamos mostrar dos o ms valores de campos de datos en una sola columna del GridView? Para acomodar este nivel de flexibilidad, el GridView ofrece el TemplateField que se representa usando una plantilla. Una plantilla puede incluir una mezcla de HTML esttico, controles Web y sintaxis de enlace a datos. Sin embargo, el TemplateField tiene una variedad de plantillas que pueden ser usadas para personalizar la presentacin de diferentes situaciones. Por ejemplo, el ItemTemplate es usado por

defecto para presentar las celdas de cada fila, pero el EditItemTemplate puede ser usado para personalizar la interfaz cuando editemos datos. En este tutorial examinaremos como usar un TemplateField para alcanzar un mayor grado de personalizacin con el control GridView. En el tutorial anterior vimos como personalizar el formato basado en los datos subyacentes, empleando los controladores de eventos DataBound y RowDataBound. Otra forma de personalizar el formato basados en los datos subyacentes es llamando mtodos de formateo dentro de una plantilla. Veremos esta tcnica en este tutorial. Para este tutorial usaremos los TemplateFields para personalizar la apariencia de una lista de empleados. Especficamente mostraremos todos los empleados, pero mostraremos los nombres y apellidos de los empleados en una sola columna, su fecha de contratacin en un control Calendar y una columna status que indica cuantos das ha sido empleado de la compaa.

Figura 1. Se utilizaran 3 TemplateFields para personalizar la visualizacin. Paso 1. Enlazar los datos al GridView En los escenarios de presentacin de informes es donde necesitamos usar TemplateFields para personalizar su apariencia. Para ms facilidad empezaremos creando un control GridView que inicialmente contendr solamente BoundFields y luego agregaremos nuevos TemplateFields o convertiremos los BoundFields existentes a

TemplateFields de ser necesario. Por lo tanto comenzaremos este tutorial agregando un GridView a la pgina por medio del Diseador y lo enlazaremos a un ObjectDataSource que devuelva una lista de todos los empleados. Estos pasos crearan un GridView con BoundFields para cada uno de los campos de los empleados. Abra la pgina GridViewTemplateField.aspx y arrastre un GridView desde la caja de herramientas hasta el diseador. Desde la etiqueta inteligente del GridView, seleccione agregar un Nuevo ObjectDataSource que invoque el mtodo GetEmployees () de la clase EmployeesBLL.

Figura 2. Agregar un nuevo control ObjectDataSource que invoque el mtodo GetEmployees () Enlazando el GridView de esta forma, agregamos automticamente un BoundField para cada una de las propiedades de los empleados: EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo y Country. Para este informe no nos tomaremos la molestia de visualizar las propiedades EmployeID, ReportsTo o Country. Para remover estos BoundFields podemos: Utilizar el cuadro de dialogo campos, haga clic en el enlace Editar Columnas en la etiqueta inteligente del control GridView para que aparezca este cuadro de dialogo. Luego, seleccione los BoundFields de la lista inferior izquierda y haga clic en el botn rojo X para eliminar el BoundField.

Editar manualmente la sintaxis declarativa del GridView, desde la vista fuente, borre el elemento <asp: BoundField> del BoundField que desea eliminar.

Luego que elimine los BoundFields EmployeeID, ReportsTo y Country, el marcado de su GridView lucir como este:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="EmployeeID" DataSourceID="ObjectDataSource1"> <Columns> <asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" /> <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" /> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:BoundField DataField="HireDate" HeaderText="HireDate" SortExpression="HireDate" /> </Columns> </asp:GridView>

Tmese un momento para ver nuestro progreso en un navegador. Hasta este momento deberamos ver una tabla con un registro por cada empleado y cuatro columnas: una para el apellido del empleado, una para su nombre, una para el cargo y una para su fecha de contratacin.

Figura 3. Se muestran los campos LastName, FirstName, Title y HireDate para cada empleado Paso 2. Mostrar el nombre y el apellido en una sola columna En la actualidad, los nombres y apellidos de cada empleado se muestran en columnas separadas. En su lugar puede resultar agradable combinarlos en una sola columna. Para realizar esto, tenemos que utilizar un TemplateField. Podemos tambin agregar un nuevo TemplateField, agregar el marcado y la sintaxis de enlace de datos necesarios y luego eliminar los BoundFields LastName y FirstName; o podemos convertir el BoundField FirstName en un TemplateField, editar el TemplateField para agregar el valor del LastName y luego eliminar el BoundField LastName. Ambos enfoques tienen el mismo resultado, pero personalmente prefiero convertir los BoundFields en TemplateField cuando sea posible, ya que la conversin agrega automticamente un Itemtemplate y un EditItemTemplate con controles web y la sintaxis de enlace a datos para imitar la apariencia y funcionalidad del BoundField. La ventaja es que tendremos que hacer menos trabajo con el TemplateField, ya que el proceso de conversin realiza parte del trabajo por nosotros. Para convertir un BoundField existente en un TemplateField, haga clic en el enlace editar columnas de la etiqueta inteligente del GridView, para que aparezca el cuadro de dialogo campos. Seleccione el BoundField a convertir de la lista inferior en la esquina izquierda y luego haga clic en el enlace Convertir este BoundField en un TemplateField en la esquina inferior derecha.

Figura 4. Convertir un BoundField en un TemplateField desde el cuadro de dilogo Campos Contine y convierta el BoundField FirstName en un TemplateField. Despus de este cambio no hay una diferencia perceptiva en el diseador. Esto se debe a que la conversin de un BoundField en un TemplateField mantiene la apariencia de un BoundField. A pesar que hasta el momento no existe ninguna diferencia visual en el diseador, este proceso de conversin ha remplazado la sintaxis declarativa del BoundField <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName"/> con la siguiente sintaxis declarativa del TemplateField:
<asp:TemplateField HeaderText="FirstName" SortExpression="FirstName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("FirstName") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("FirstName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

Como puede observar, el TemplateField consta de dos plantillas, un ItemTemplate que contiene un Label cuya propiedad Text se establece con el valor del campo de datos FirstName y un EditItemTemplate con un control TextBox cuya propiedad Text tambin est definida con el campo FirstName. La sintaxis de enlace de datos <%#Bind("fieldName") %> indica que el campo de datos FieldName esta enlazado a la propiedad especifica del control web. Para agregar el valor del campo de datos LastName a este TemplateField, necesitamos agregar otro control web Label en el ItemTemplate y enlazar su propiedad Text a LastName. Podemos realizar esto a travs del diseador o manualmente. Para realizar esto manualmente, simplemente agregue la siguiente sintaxis declarativa apropiada al ItemTemplate:
<asp:TemplateField HeaderText="FirstName" SortExpression="FirstName"> <EditItemTemplate>

<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("FirstName") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("FirstName") %>'></asp:Label> <asp:Label ID="Label2" runat="server" Text='<%# Bind("LastName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

Para agregar esto por medio del diseador, haga clic en el enlace editar Templates en la etiqueta inteligente del GridView. Esto mostrara la interfaz de edicin de plantillas del GridView. En la etiqueta inteligente de esta interfaz, hay una lista de los Templates del GridView. Como hasta el momento solo tenemos un TemplateField, el nico Template mostrado en la lista desplegable son los Templates para el TemplateField FirstName junto con el EmptyDataTemplate y PagerTemplate. Si se especifica el Template EmptyDataTemplate se utiliza para presentar la salida del GridView cuando no hay resultados en el enlace de datos del GridView; si se especifica el PagerTemplate se utiliza para presentar la interface de paginacin para un GridView que soporta paginacin.

Figura 5. Las Template del GridView pueden ser editadas por medio de un diseador Para mostrar tambin los TemplateField LastName y FirstName, arrastre un control Label desde la caja de herramientas al ItemTemplate del TemplateField FirstName en la interfaz de edicin de plantillas del GridView.

Figura 6. Agregar un control web Label al ItemTemplate del TemplateField del FisrtName Hasta el momento el control web Label agregado al TemplateField tiene su propiedad Text establecida en Label. Necesitamos cambiar esto para que en su lugar, esta propiedad sea enlazada al valor del campo de datos LastName. Para realizar esto, haga clic en la etiqueta inteligente del control Label y seleccione la opcin Editar enlace de datos.

Figura 7. Elija la opcin Editar Enlace de datos en la etiqueta inteligente del Label

Esto abrir el cuadro de dialogo DataBindings. Desde aqu puede seleccionar la propiedad que participa en el enlace de datos desde la lista de la izquierda, y seleccionar el campo para enlazar los datos desde la lista desplegable de la derecha. Seleccione la propiedad Text en la izquierda y el campo LastName de la derecha y haga clic en OK.

Figura 8. Enlace la propiedad Text al campo de datos LastName Nota: El cuadro de dialogo DataBindings le permite indicar si se debe realizar un enlace de datos de dos vas. Si se deja esta casilla sin marcar, se utiliza la sintaxis de enlace de datos <%#Eval (LastName)%> en lugar de usar <%#Bind(LastName)%>. Cualquiera de estos enfoques est bien para este tutorial. El enlace de datos bidireccional es muy importante cuando insertamos o modificamos datos. Sin embargo para simplemente mostrar datos, ambos enfoques trabajaran exactamente de la misma manera. Discutiremos el enlace de datos bidireccional en los futuros tutoriales. Tmese un momento para ver esta pgina a travs de un navegador. Como puede ver, El GridView aun incluye cuatro columnas, sin embargo la columna FirstName ahora muestra los valores de los campos de datos FirstName y LastName.

Figura 9. Los valores FirstName y LastName son mostrados en una sola columna Para completar este primer paso, quite el BoundField LastName y renombre la propiedad HeaderText del TemplateField FirstName a Nombre. Despus de estos cambios, el marcado declarativo del GridView debera tener el siguiente aspecto:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="EmployeeID" DataSourceID="ObjectDataSource1"> <Columns> <asp:TemplateField HeaderText="Name" SortExpression="FirstName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("FirstName") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("FirstName") %>'></asp:Label> <asp:Label ID="Label2" runat="server" Text='<%# Eval("LastName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:BoundField DataField="HireDate" HeaderText= "HireDate" SortExpression="HireDate" /> </Columns>

</asp:GridView>

Figura 10. El LastName y el FirstName de cada empleado son mostrados en una sola columna Paso 3. Mostrar un control Calendar para mostrar la fecha de contratacin Mostrar un valor de un campo de datos como un texto en un GridView, es tan sencillo como usar un BoundField. Sin embargo, para ciertos escenarios los datos se pueden expresar mejor usando un control web en particular, en lugar de solo texto. Esta personalizacin de la visualizacin de datos es posible con TemplateFields. Por ejemplo, en lugar de mostrar la fecha de contratacin de los empleados como un texto, podramos mostrar un calendario (usando el control Calendar) con su fecha de contratacin resaltada. Para realizar esto comience convirtiendo el BoundField HireDate en un TemplateField. Sencillamente vaya a la etiqueta inteligente del GridView y haga clic en el enlace Editar Columnas, con lo cual aparece el cuadro de dialogo Campos. Seleccione el BoundField HiredDate y haga clic en Convertir este BoundField en un TemplateField.

Figura 11. Convierta el BoundField HiredDate en un TemplateField Como vimos en el paso 2, esto remplaza el BoundField con un TemplateField que contiene un ItemTemplate y un EditItemTemplate con un Label y un TextBox cuyas propiedades Text estn enlazadas al valor HiredDate usando la sintaxis de enlace de datos <%#Bind(HiredDate)%>. Para remplazar el texto con un control Calendar, editamos el Template eliminando el Label y agregando un control Calendar. Desde el diseador seleccione Editar Templates en la etiqueta inteligente del GridView y seleccione de la lista desplegable el Itemtemplate del TemplateField HiredDate. Luego borre el control Label y arrastre un control Calendar desde la caja de herramientas hasta la interfaz de edicin de plantillas.

Figura 12. Agregue un control Calendar al ItemTemplate del TemplateField del HiredDate Hasta el momento cada fila en el GridView contendr un control Calendar en su TemplateField HiredDate. Sin embargo el valor de la fecha de contratacin del empleado actual no est establecido en ninguno de los controles Calendar, ocasionando que cada control Calendar muestre por defecto la fecha y mes en curso. Para solucionar esto, tenemos que asignar las propiedades SelectedDate y Visible Date de los controles Calendar al HiredDate de cada empleado. Desde la etiqueta inteligente del control Calendar, seleccione Editar DataBindings. Luego enlace las propiedades SelectedDate y Visible Date al campo de datos HiredDate.

Figura 13. Enlace las propiedades SelectedDate y Visible Date al campo de datos HiredDate Nota: La fecha seleccionada del control Calendar no necesariamente ser visible. Por ejemplo un Calendar podra tener como fecha seleccionada Agosto 1, 1999, pero estar mostrando el mes y aos actuales. La fecha seleccionada y la fecha visible son especificadas por las propiedades SelectedDate y Visible Date del control Calendar. Como deseamos seleccionar la fecha de contratacin del empleado, asegrese que esta sea mostrada cuando enlazamos estas propiedades al campo de datos HiredDate. Cuando visualizamos la pgina en un navegador, el calendar ahora muestra el mes de la fecha de contratacin del empleado y seleccionara esa fecha en particular.

Figura 14. El HiredDate del empleado es mostrado en el control Calendar Nota: A diferencia de todos los ejemplos que hemos visto hasta ahora, para este tutorial no establecemos la propiedad EnableViewState=False para este GridView. La razn de esta decisin es porque dando clic a las fechas del control Calendar provocamos una devolucin de datos, estableciendo la fecha seleccionada del Calendar a la fecha que acabamos de dar clic. Sin embargo, si el ViewState del GridView esta deshabilitado, en cada devolucin, los datos del GridView son enlazados nuevamente a su origen de datos, lo que ocasiona que la fecha seleccionada en el Calendar se

establezca nuevamente en la fecha de contratacin del empleado, sobrescribiendo la fecha escogida por el usuario. Para este tutorial se trata de una discusin irrelevante ya que el usuario no debe actualizar la fecha de contratacin del empleado. Esta seria posiblemente la mejor configuracin para el control Calendar para que sus fechas no sean seleccionables. En cualquier caso, este tutorial muestra que en algunas circunstancias el ViewState debe ser deshabilitado con el fin de proporcionar cierta funcionalidad. Paso 4. Mostrar el nmero de das que el empleado ha trabajado con la compaa Hasta ahora hemos visto dos aplicaciones del TemplateFields: Combinar dos o ms valores de campos de datos en una columna, y Expresar el valor de un campo de datos usando un control web en lugar de texto

Un tercer uso del TemplateField est en la visualizacin de los metadatos relacionados a los datos subyacentes del GridView. Adems de mostrar las fechas de contratacin de los empleados, por ejemplo podramos mostrar cuantos das ha estado en su trabajo. Sin embargo, otro uso de los TemplateFields surge en los escenarios en que los datos subyacentes, deben ser mostrados de una forma diferente al formato con el que esta almacenado en la base de datos en una pgina web de informe. Imaginen que la tabla Empleados, tiene un campo Sexo que almacena los caracteres M o F indicando el sexo del empleado. Cuando mostramos esta informacin en una pgina web, podramos desear mostrar el sexo como Femenino y Masculino en lugar de M y F. Los dos escenarios pueden ser manejados con la creacin de un mtodo de formato en la clase del cdigo subyacente de la pgina Asp.Net (o en una biblioteca de clases separadas, implementada como un mtodo Share) que sea invocada desde el Template. Un mtodo de formato es invocado desde la Template usando la misma sintaxis de enlace de datos vista anteriormente. El mtodo de formato puede tomar cualquier nmero de parmetros, pero debe devolver un string. El string devuelto es el HTML que se ingresa en la plantilla. Para ilustrar este concepto, vamos a ampliar nuestro tutorial para mostrar una columna que muestre el total del nmero de das que cada empleado ha trabajado. El mtodo de

formato tomara un objeto Northwind.EmployeesRow y devuelve el nmero de das que un empleado ha trabajado como una cadena. Este mtodo puede ser agregado a la clase del cdigo subyacente de la pgina ASP.Net, pero debe ser marcado como Protected o Public con el fin de que pueda accederse a l desde el Template.
Protected Function DisplayDaysOnJob(ByVal employee As Northwind.EmployeesRow) As String ' Make sure HiredDate is not NULL... if so, return "Unknown" If employee.IsHireDateNull() Then Return "Unknown" Else ' Returns the number of days between the current ' date/time and HireDate Dim ts As TimeSpan = DateTime.Now.Subtract(employee.HireDate) Return ts.Days.ToString("#,##0") End If End Function

Como el campo HiredDate puede contener valores de base de datos Nulls, primero debemos asegurarnos que el valor no es Null antes de proceder con el clculo. Si el valor HiredDate es Null, simplemente devolvemos la cadena Desconocido, si no es Null calculamos la diferencia entre la fecha actual y el valor de la fecha de contratacin y regresamos el nmero de das. Para utilizar este mtodo necesitamos invocarlo desde un TemplateField en el GridView utilizando la sintaxis de enlace de datos. Empiece agregando un nuevo TemplateField al GridView, haga clic en el enlace editar columnas en la etiqueta inteligente del control GridView y agregue un nuevo TemplateField.

Figura 15. Agregue un nuevo TemplateField en el GridView Establezca la propiedad Header de este nuevo TemplateField en Das en el trabajo y su propiedad HorizontalAlign del ItemStyle en center. Para llamar al mtodo DisplayDaysOnJob desde el Template, agregamos un ItemTemplate y usamos la siguiente sintaxis de enlace de datos:
<%# DisplayDaysOnJob(CType(CType(Container.DataItem,System.Data.DataRowView).Row, Northwind.EmployeesRow)) %>

Container.DataItem devuelve un objeto DataRowView que corresponde al registro del DataSource enlazado al GridViewRow. Su propiedad el cual es Row pasado devuelve al el Northwind.EmployeesRow fuertemente tipiado, mtodo

DisplayDaysOnJob. Esta sintaxis de enlace de datos puede aparecer directamente en el ItemTemplate (como se muestra en la sintaxis declarativa de abajo) o puede ser asignada a la propiedad Text de un control web Label. Nota: De forma alternativa, en lugar de pasar una instancia EmployeesRow, podramos solamente pasar el valor de HiredDate usando <%#DisplayDaysOnJob (Eval(HireDate))%>. Sin embargo, el mtodo Eval devuelve un object, por lo cual tendremos que cambiar nuestra asignacin del mtodo DisplayDaysOnJob para que acepte en su lugar un parmetro de entrada de tipo object. Estamos a ciegas y no podemos llamar Eval(HiredDate) a un DateTime porque la columna HiredDate en la tabla Employees puede contener valores Nulls. Por lo tanto, necesitamos aceptar un object como un parmetro de entrada para el mtodo DisplayDaysOnJob, verificar para

ver si hay valores de base de datos Null (lo cual puede hacerse usando Convert.IsDBNull (objectToCheck)) y luego proceder acordemente. Debido a estas sutilezas, he optado por pasar toda la instancia EmployeesRow. En el siguiente tutorial veremos un ejemplo ms apropiado para usar la sintaxis Eval(ColumnName) pasando un parmetro de entrada en un mtodo de formato. A continuacin mostramos la sintaxis declarativa para nuestro GridView luego que ha sido agregado el TemplateField y el mtodo DisplayDaysOnJob es llamado desde el ItemTemplate:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="EmployeeID" DataSourceID="ObjectDataSource1"> <Columns> <asp:TemplateField Header Text="Name" SortExpression="FirstName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("FirstName") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("FirstName") %>'></asp:Label> <asp:Label ID="Label2" runat="server" Text='<%# Eval("LastName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:TemplateField HeaderText="HireDate" SortExpression= "HireDate"> <EditItemTemplate> <asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("HireDate") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Calendar ID="Calendar1" runat="server" SelectedDate='<%# Bind("HireDate") %>' VisibleDate='<%# Eval("HireDate") %>'>

</asp:Calendar> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Days On The Job"> <ItemTemplate> <%# System.Data.DataRowView).Row, Northwind.EmployeesRow)) %> </ItemTemplate> <ItemStyle HorizontalAlign="Center" /> </asp:TemplateField> </Columns> </asp:GridView> DisplayDaysOnJob(CType(CType(Container.DataItem,

La figura 16, muestra el tutorial completo visto a travs de un navegador.

Figura 16. Se muestran el nmero de das que el empleado a trabajo en la empresa Resumen El TemplateField en el control GridView permite un alto grado de flexibilidad en la presentacin de los datos que est disponible con los otros controles de campo. Los TemplateFields son ideales para situaciones donde:

Mltiples campos de datos necesitan ser mostrados en una sola columna en el GridView Los datos son expresados mejor usando un control web en lugar de un texto plano Las salidas dependen de los datos subyacentes, como al mostrar los metadatos o en reformatear los datos.

Adicionalmente para personalizar la visualizacin de datos, los TemplateFields tambin son usados para personalizar las interfaces de usuario usadas para editar o insertar datos, como veremos en futuros tutoriales. Los siguientes dos tutoriales continan explorando los Templates, partiendo de una mirada del uso de TemplateFields en un DetailsView. Luego pasaremos al FormView que utiliza plantillas en lugar de campos para proporcionar una mayor flexibilidad en el diseo y estructura de los datos.

13.

USANDO PLANTILLAS DE CAMPO EN EL CONTROL DETAILSVIEW

Las mismas capacidades de TemplateFields disponibles con el GridView tambin estn disponibles en el control DetailsView. En este tutorial vamos a mostrar un producto a la vez usando un DetailsView que contiene TemplateFields. Introduccin Los TemplateField ofrecen un mayor grado de flexibilidad en la reproduccin de los datos que los BoundField, CheckBoxField, HiperLinkField y los otros controles de campos de datos. En el tutorial anterior vimos como usar un TemplateField en un GridView para: Mostrar varios valores de campos de datos en una columna. Especficamente combinamos los campos FirstName y LastName en una sola columna del GridView. Usar un control web alternativo para mostrar el valor de un campo de datos. Vimos como mostrar el valor de la fecha de contratacin usando un control Calendar. Mostrar el estado de la informacin basados en los datos subyacentes. Aunque la tabla Employees no contiene una columna que devuelva los das que un empleado ha estado en el trabajo, fuimos capaces de mostrar esta informacin en el GridView del ejemplo del tutorial anterior usando un TemplateField y un mtodo de formato. Las mismas capacidades disponibles en los TemplateFields validas en un GridView tambin estn disponibles con el control DetailsView. En este tutorial mostraremos un producto a la vez usando un DetailsView que contiene dos TemplateFields. El primer TemplateField combinara los campos de datos UnitPrice, UnitsInStock y UnitsOnOrder en una sola fila del DetailsView. El segundo TemplateField mostrara el valor del campo Discontinued, el cual usara un mtodo de formato para mostrar Si, si el Discontinued es True y No si Discontinued es False.

Figura 1. Se utilizaran dos TemplateFields para personalizar la visualizacin Paso 1. Enlazar los datos al DetailsView: Como discutimos en el tutorial anterior, cuando trabajamos con TemplateFields a menudo es fcil comenzar creando un control DetailsView que contiene solamente BoundFields y luego agregar TemplateFields o convertir los BoundFields existentes a TemplateFields si es necesario. Por lo tanto comenzamos este tutorial agregando un DetailsView a la pgina por medio del diseador y enlazarlo a un ObjectDataSource que devuelva la lista de los productos. Estos pasos crearan un DetailsView con BoundFields para cada uno de los valores de campo no booleanos de los productos y un CheckBoxField para cada uno de los valores de campos booleanos (Discontinued). Abra la pgina DetailsViewtemplateField.aspx y arrastre un DetailsView desde la caja de herramientas hasta el diseador. Desde la etiqueta inteligente del DetailsView seleccione agregar un nuevo ObjectDataSource que invoque el mtodo GetProducts () de la clase ProductsBLL.

Figura 2. Agregue un nuevo control ObjectDataSource que invoque el mtodo GetProducts () Para este informe removemos los BoundFields ProductID, SupplierID, CategoryID y ReorderLevel. Luego reordene los BoundFields para que los BoundFields CategoryName y SupplierName aparezca inmediatamente despus del BoundField ProductName. Sintase libre de ajustar las propiedades HeaderText y dar formato a las propiedades para que los BoundFields aparezcan mejor. Como con el GridView, estas ediciones a nivel de BoundField pueden realizarse por medio del cuadro de dialogo campos (accedemos dando clic en el enlace editar campos en la etiqueta inteligente del DetailsView) o por medio de la sintaxis declarativa. Por ltimo limpie los valores de las propiedades Heigth y Width del DetailsView con el fin de permitir que el DetailsView se expanda basado en los datos mostrados y habilite la casilla de verificacin Habilitar Paginacin en la etiqueta inteligente. Despus de estos cambios, el marcado declarativo de su DetailsView lucir similar al siguiente:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False"> <Fields> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" />

<asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" SortExpression="UnitsOnOrder" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Fields> </asp:DetailsView>

Tmese un momento para ver la pgina a travs de un navegador. Hasta el momento veremos un solo producto (Chai) con filas que muestran el nombre del producto, categora, proveedor, precio, unidades en existencia, unidades ordenadas y su estado de descontinuado.

Figura 3. Los detalles del producto son mostrados usando una serie de BoundFields

Paso 2. Combinar el precio, unidades ordenadas y unidades de existencia en una sola fila El DetailsView tiene una fila para los campos UnitPrice, UnitsInStock y UnitsOnOrder. Podemos combinar estos campos de datos en una sola fila con un TemplateField, ya sea agregando un nuevo TemplateField o convirtiendo uno de los BoundFields UnitPrice, UnitsInStock y UnitsOnOrder existentes en un TemplateField. Aunque personalmente prefiero convertir un BoundField existente en un TemplateField, practicaremos agregando un Nuevo TemplateField. Comencemos haciendo clic en el enlace Editar campos en la etiqueta inteligente del DetailsView para que aparezca el cuadro de dialogo campos. Luego agregue un nuevo TemplateField y establezca su propiedad HeaderText en Precio e Inventario y mueva el nuevo TemplateField para que se posicione encima del BoundField UnitPrice.

Figura 4. Agregue un nuevo TemplateField al control DetailsView Como este nuevo TemplateField contendr los valores actualmente mostrados en los BoundFields UnitPrice, UnitsInStock y UnitsOnOrder, vamos removerlos. La ltima tarea de este paso es la de definir el marcado del ItemTemplate para el TemplateField Precio e Inventario, que se puede realizar por medio de la interfaz de edicin de Template del DetailsView en el diseador o manualmente por medio de la sintaxis declarativa del control. Al igual que con el GridView, se puede acceder a la interface de edicin de las plantillas del DetailsView dando clic en el enlace editar Templates en la etiqueta inteligente. Desde aqu podemos seleccionar el Template a

editar desde la lista desplegable y luego agregar cualquier control web desde la caja de herramientas. Para este tutorial comenzamos agregando un control Label al ItemTemplate del TemplateField Precio e Inventario. Luego haga clic en el enlace editar DataBindings en la etiqueta inteligente del control Label y enlace la propiedad Text al campo UnitPrice.

Figura 5. Enlace la propiedad Text del Label al campo de datos UnitPrice Dar formato al precio como una moneda Con esta edicin, el control web Label del TemplateField Precio e Inventario muestra solamente el precio del producto seleccionado. La figura 6 muestra una captura de pantalla de nuestro progreso hasta el momento, vista a travs de un navegador.

Figura 6. El TemplateField Precio e Inventario muestra el Precio

Tenga en cuenta que el precio del producto no est formateado como moneda. Con un BoundField dar formato es posible estableciendo su propiedad HtmlEncode a False y la propiedad DataFormatString en {0:formatSpecifier}. Sin embargo para un TemplateField cualquier instruccin de formato debe ser especificado en la sintaxis de enlace de datos o por medio de un mtodo de formato definido en algn lugar dentro del cdigo de la aplicacin (como en la clase del cdigo subyacente de la pgina Asp.Net). Para especificar le formato de la sintaxis de enlace de datos usada en el control Label, regresamos al cuadro de dialogo DataBindings dando clic en el enlace editar DataBindings en la etiqueta inteligente del Label. Usted puede escribir las instrucciones de formato directamente en la lista desplegable formato, o seleccionar una de las cadenas de formato definidas. Al igual que con la propiedad DataFormatString de los BoundFields, el formato es especificado usando {0:formatSpecifier}. Para el campo UnitPrice usamos el formato moneda especificado seleccionando el valor apropiado de la lista desplegable o escribiendo manualmente {0:c}.

Figura 7. Formatear el precio como moneda Mediante declaracin, la especificacin del formato es indicado como un segundo parmetro en los mtodos Bind o Eval. Las modificaciones que acabamos de hacer por medio del Diseador originan la siguiente expresin de enlace de datos en el marcado declarativo:

<asp:Label ID="Label1" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'/>

Agregar los campos de datos restantes al TemplateField Hasta este momento hemos mostrado y formateado el campo de datos UnitPrice en el TemplateField Precio e Inventario, pero aun necesitamos mostrar los campos UnitsInStock y UnitsOnOrder. Mostraremos estos en una linea debajo del precio y en parntesis. Desde la interface de edicin de Templates en el diseador, el marcado puede ser agregado posicionando su cursor dentro del Template y escribiendo el texto que ser mostrado. Por otra parte este marcado puede ser agregado directamente en la sintaxis declarativa. Agregue el marcado esttico, los controles web Label y la sintaxis de enlace de datos para que el TemplateField Precio e Inventario muestre la informacin del precio e inventario de la siguiente manera:
UnitPrice
(In Stock / On Order: UnitsInStock / UnitsOnOrder)

Despus de realizar esta tarea, el marcado declarativo del DetailsView lucir similar al siguiente:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False"> <Fields> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" /> <asp:TemplateField HeaderText="Price and Inventory"> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>

<br /> <strong> (In Stock / On Order: </strong> <asp:Label ID="Label2" runat="server" Text='<%# Eval("UnitsInStock") %>'></asp:Label> <strong>/</strong> <asp:Label ID="Label3" runat="server" Text='<%# Eval("UnitsOnOrder") %>'> </asp:Label><strong>)</strong> </ItemTemplate> </asp:TemplateField> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Fields> </asp:DetailsView>

Con estos cambios, consolidamos la informacin de precio e inventario en un solo DetailsView row

Figura 8. La informacin del Precio e Inventario es mostrada en una sola fila Paso 3. Personalizar la informacin del campo Discontinued La columna Discontinued de la tabla Products es un valor bit que indica si el producto ha sido descontinuado. Cuando enlazamos un DetailsView (o GridView) a un control de origen de datos, los valores de campos booleanos como Discontinued son implementados como CheckBoxFields, mientras que los valores de campos no booleanos como ProductID, ProductName, entre otros son implementados como

BoundFields. Los CheckBoxFields se presentan como una casilla de verificacin deshabilitada, que es habilitada si el valor del campo de datos es True y deshabilitada en caso contrario. En lugar de mostrar el CheckBoxField podramos desear mostrar en su lugar un texto que indique si el producto est o no descontinuado. Para realizar esto podemos remover el CheckBoxField del DetailsView y luego agregar un BoundField cuya propiedad DataField sea establecida en Discontinued. Tmese un momento para hacer esto. Luego de estos cambios el DetailsView muestra el texto True para los productos descontinuados y False para los productos que aun estn activos.

Figura 9. Se utilizan las cadenas True y False para mostrar el estado de Discontinued Imagine que en lugar de usar las cadenas True y False deseamos usar Si y No. Estas personalizaciones pueden realizarse con la ayuda de un TemplateField y un mtodo de formato. Un mtodo de formato puede tomar cualquier numero de parmetros de entrada, pero debe devolver el HTML (como una cadena) para ingresar en el Template. Agregue un mtodo de formato a la clase de cdigo subyacente de la pgina DetailsViewTemplateField.aspx llamado DisplayDiscontinuedAsYESorNO que acepte un objeto Northwind.ProductsRow como un parmetro de entrada y devuelva una cadena. Como discutimos en el tutorial anterior, este mtodo debe ser marcado como Public o Protected con el fin que pueda ser accedido desde el Template.

Protected Function DisplayDiscontinuedAsYESorNO(discontinued As Boolean) As String If discontinued Then Return "YES" Else Return "NO" End If End Function

Este mtodo examina el parmetro de entrada (discontinued) y devuelve SI si es True, NO en caso contrario. Nota: En el mtodo de formato examinado en el tutorial anterior aclaramos que el campo de datos pasado podra contener valores Nulls y por lo tanto era necesario verificar si la propiedad HiredDate de los empleados tiene un valor de base de datos Null antes de acceder a la propiedad HiredDate de EmployeesRow. Aqu no es necesaria una verificacin ya que la columna Discontinued nunca podr tener valores de base de datos Nulls asignados. Por otra parte esta es la razn por la cual el mtodo puede aceptar un parmetro de entrada booleano en lugar de tener que aceptar una instancia ProductsRow o un parmetro de tipo object. Con este mtodo de formato completo, lo restante es llamarlo desde el ItemTemplate del TemplateField. Para crear el TemplateField, remueva el BoundField Discontinued y agregue un nuevo TemplateField o convierta el BoundField Discontinued en un TemplateField. Luego desde la vista del marcado declarativo, edite el TemplateField para que contenga solamente un ItemTemplate que invoque el mtodo DsiplayDiscontinuedYESorNO, pasando el valor actual de la propiedad Discontinued de la instancia ProductsRow actual. Este puede ser accedido a travs del mtodo Eval. Especficamente el marcado declarativo del TemplateField lucir as:
<asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued"> <ItemTemplate> <%# DisplayDiscontinuedAsYESorNO(Convert.ToBoolean(Eval("Discontinued")) %> </ItemTemplate> </asp:TemplateField>

Esto har que el mtodo DisplayDiscontinuedAsYESorNO sea invocado cuando presentamos el DetailsView, pasando el valor Discontinued de la instancia ProductRow.

Como el mtodo Eval devuelve un valor de un tipo object, pero el mtodo DisplayDiscontinuedAsYESorNO espera un parmetro de entrada de tipo Boolean, nosotros moldeamos los mtodos Eval para devolver un valor Boolean. Entonces el mtodo DisplayDiscontinuedAsYESorNO devolver SI o NO dependiendo del valor que recibe. El valor devuelto es el que se muestra en esta fila del DetailsView (Ver Figura 10).

Figura 10. Ahora se muestran los valores YES o NO en la fila Discontinued Resumen Los TemplateField en el control DetailsView permiten un alto grado de flexibilidad en la visualizacin de datos que est disponible con los otros controles de campos y son ideales para situaciones donde: Es necesario mostrar mltiples campos de datos en una sola columna del GridView. Es mejor expresar los datos usando un control web en lugar de un texto sin formato. La salida depende de los datos subyacentes, tal como la presentacin de los metadatos o en reformatear los datos. Mientras los TemplateFields permiten un mayor grado de flexibilidad en la presentacin de los datos subyacentes del DetailsView, las salidas del DetailsView todava se siente un poco cuadriculada, ya que cada fila se presenta como una fila en un archivo HTML <table>.

El control FormView ofrece un mayor grado de flexibilidad en la configuracin de la presentacin de sus salidas. El FormView no contiene campos en su lugar tiene una serie de plantillas (ItemTemplate, EditItemTemplate, HeaderTemplate y as sucesivamente). En nuestro prximo tutorial veremos cmo utilizar el FormView para lograr un mayor control en la capa de presentacin.

14.

USO DE LAS PLANTILLAS EN LOS FORMVIEWS

A diferencia del DetailsView, el FormView no se compone de campos. En cambio el FormView se representa mediante el uso de plantillas. En este tutorial examinaremos el control FormView para presentar un diseo menos rgido de los datos. Introduccin En los ltimos dos tutoriales vimos como personalizar las salidas de los controles GridView y DetailsView usando TemplateFields. Los Templatefields permiten personalizar altamente los contenidos para un campo especfico, pero tanto para los controles GridView y DetailsView tienen una apariencia de aspecto cuadriculado. Para muchos escenarios un diseo cuadriculado es lo ideal, pero a veces se necesita una pantalla ms fluida y menos rgida. Cuando se muestra un registro nico con un diseo fluido podemos utilizar un control FormView. A diferencia del DetailsView. El FormView no est compuesto de campos. Usted no puede agregar un BoundField o un TemplateField al FormView. En su lugar el FormView es presentado usando plantillas. Piense en el FormView como un control DetailsView que contiene un solo TemplateField. El FormView soporta las siguientes plantillas: ItemTemplate usado para presentar un registro especifico mostrado en el FormView HeaderTemplate usado para especificar una fila de encabezado opcional FooterTemplate usado para especificar opcionalmente una fila de pie de pagina EmptyDataTemplate cuando el DataSource del FormView carece de registros, el EmptyDataTemplate se utiliza en lugar del ItemTemplate para presentar el marcado del control. PagerTemplate se puede utilizar para personalizar la interfaz de paginacin del FormView que permite la paginacin. EditItemTemplate/InsertItemTemplate utilizada para personalizar la interfaz de edicin o la interfaz de insercin de los FormView que soportan esta funcionalidad. En este tutorial examinaremos como usar el control FormView para presentar una pantalla de productos menos rgida. En lugar de tener campos para el nombre, categora, proveedor y as sucesivamente. El ItemTemplate del FormView muestra estos

valores usando una combinacin de un elemento de encabezado y una <table> (Ver Figura 1).

Figura 1. El FormView rompe con el diseo de cuadriculado visto en el DetailsView Paso 1: Enlazar los datos al FormView Abra la pgina FormView.aspx y arrastre un FormView desde la barra de herramientas hasta el diseador. Cuando agregamos el FormView este aparece como una caja gris, indicndonos que es necesario un ItemTemplate.

Figura 2. El FormView no puede presentarse hasta que se proporcione el ItemTemplate El ItemTemplate puede ser creado manualmente (a travs de la sintaxis declarativa) o puede ser creado automticamente enlazando el FormView a un control de origen de datos por medio del diseador. El ItemTemplate creado automticamente contiene

HTML que muestra los nombres de cada campo y un control Label cuyas propiedades Text son enlazadas a los valores de los campos. Este enfoque tambin crea automticamente un InsertTemplate y EditTemplate, los cuales son poblados con controles de entrada para cada uno de los campos de datos devueltos por los controles de origen de datos. Si usted desea crear automticamente la plantilla, desde la etiqueta inteligente del FormView agregue un nuevo control ObjectDataSource que invoque el mtodo GetProducts () de la clase ProductsBLL. Esto creara un FormView con un ItemTemplate, InsertItemTemplate y un EditTemplate. Desde la vista Cdigo, remueva el ItemTemplate y el EditItemTemplate ya que no estamos interesados aun en crear un FormView que soporte la edicin o la insercin. Luego limpiemos el marcado del ItemTemplate para que podamos tener un borrn y cuenta nueva para trabajar. Si prefiere construir el ItemTemplate manualmente, podemos agregar y configurar el ObjectDataSource arrastrndolo desde el cuadro de herramientas al diseador. Sin embargo no establezca el origen de datos desde el diseador. En su lugar vaya a la vista cdigo fuente y configure manualmente la propiedad DataSourceID del FormView al valor ID del ObjectDataSource. Luego agregue manualmente el ItemTemplate. Independientemente del enfoque que desee utilizar, hasta este punto el marcado declarativo del FormView debe ser similar a:
<asp:FormView ID="FormView1" runat="server" DataSourceID="ObjectDataSource1"> <ItemTemplate> </ItemTemplate> </asp:FormView>

Tome un momento para seleccionar la casilla de verificacin Habilitar Paginacin en la etiqueta inteligente del FormView, lo cual agregara el atributo AllowPaging=True a la sintaxis declarativa del FormView. As mismo defina la propiedad EnableViewState en Falso. Paso 2: Defina el marcado del ItemTemplate Con el FormView enlazado al control ObjectDataSource y configurado para que admita la paginacin, estamos listos para especificar el contenido del ItemTemplate. Para este

tutorial vamos a tener el nombre del producto mostrado en un encabezado <h3>. Luego usaremos un HTML <table> para mostrar las propiedades de los productos restantes en una tabla de cuatro columnas, en la primera y tercer columna mostramos los nombres de la propiedad y en la segunda y cuarta mostramos sus valores. Este marcado se puede introducir a travs de la interfaz de edicin de plantillas del FormView en el diseador o introducirlo manualmente a travs de la sintaxis declarativa. Cuando se trabaja con plantillas normalmente resulta ms rpido trabajar directamente con la sintaxis declarativa, pero no dude en utilizar la tcnica con la que se sienta ms cmodo. El siguiente marcado muestra el marcado declarativo del FormView luego que la estructura ItemTemplate se ha completado:
<asp:FormView ID="FormView1" runat="server" DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False"> <ItemTemplate> <hr /> <h3><%# Eval("ProductName") %></h3> <table border="0"> <tr> <td class="ProductPropertyLabel">Category:</td> <td class="ProductPropertyValue"> <%# Eval("CategoryName") %></td> <td class="ProductPropertyLabel">Supplier:</td> <td class="ProductPropertyValue"> <%# Eval("SupplierName")%></td> </tr> <tr> <td class="ProductPropertyLabel">Price:</td> <td class="ProductPropertyValue"><%# Eval("UnitPrice", "{0:C}") %></td> <td class="ProductPropertyLabel">Units In Stock:</td> <td class="ProductPropertyValue"> <%# Eval("UnitsInStock")%></td> </tr> <tr> <td class="ProductPropertyLabel">Units On Order:</td> <td class="ProductPropertyValue">

<%# Eval("UnitsOnOrder") %></td> <td class="ProductPropertyLabel">Reorder Level:</td> <td class="ProductPropertyValue"> <%# Eval("ReorderLevel")%></td> </tr> <tr> <td class="ProductPropertyLabel">Qty/Unit</td> <td class="ProductPropertyValue"> <%# Eval("QuantityPerUnit") %></td> <td class="ProductPropertyLabel">Discontinued:</td> <td class="ProductPropertyValue"> <asp:CheckBox runat="server" Enabled="false" Checked='<%# Eval("Discontinued") %>' /> </td> </tr> </table> <hr /> </ItemTemplate> </asp:FormView>

Por ejemplo observe que la sintaxis de enlace de datos - <%# Eval(ProductName)%>, puede ser ingresada directamente en la salida de la plantilla. Es decir no es necesario asignar la propiedad Text a un control Label. Por ejemplo nosotros tenemos el valor ProductName <h3>Chai</h3i>. Las clases CSS ProductPropertyLabel y ProductPropertyValue se utilizan para especificar el estilo de las propiedades del nombre y valor de los productos en la <table>. Estas clases CSS se definen en Styles.css y ocasionan que las propiedades nombres sean en negrita y alineados a la derecha y agrega un relleno a la derecha de la propiedad valores. Dado que no existen CheckBoxFields disponibles en el FormView, para mostrar una casilla de verificacin, es necesario que agreguemos nuestro propio control CheckBox. La propiedad Enable se establece en False, lo que lo convierte en solo lectura y la propiedad Checked del CheckBox esta enlazada al valor del campo de datos Discontinued. mostrado en un elemento <h3> utilizando <h3><%#Eval(ProductName)%></h3>, que para el producto Chai ser algo como

Con el ItemTemplate completo, la informacin del producto se muestra de una forma mucho ms fluida. Compare la salida del DetailsView de los ltimos tutoriales (Figura 3) con el resultado generado por el FormView de este tutorial (Figura 4).

Figura 3. Salida rgida del DetailsView

Figura 4. Salida Fluida del DetailsView Resumen Aunque los controles GridView y DetailsView pueden personalizar sus salidas usando TemplateFields, ambos presentan sus datos en una forma cuadrada. Para ocasiones

cuando es necesario mostrar un solo registro usando un diseo menos rgido, el FormView es una opcin ideal. Al igual que el DetailsView, el FormView muestra un nico registro de su DataSource, pero a diferencia del DetailsView este solo se compone de plantillas y no es compatible con los campos. Como vimos en este tutorial, el FormView presenta un diseo ms flexible al mostrar un nico registro. En futuros tutoriales examinaremos los controles DataList y Repeater, que proporcionan el mismo nivel de flexibilidad que los FormViews, pero son capaces de mostrara varios registros (como el GridView).

15.

MOSTRAR INFORMACIN RESUMIDA EN EL PIE DE PGINA DEL

GRIDVIEW
El resumen de la informacin a menudo es mostrado en la parte inferior del informe en una fila de resumen. El control GridView puede incluir una fila de pie de pgina, en cuyas celdas se puede ingresar mediante programacin los datos agregados. En este tutorial veremos cmo mostrar los datos agregados en esta fila de pie de pgina. Introduccin Adems de ver cada uno de los precios de los productos, unidades de existencia, unidades pedidas y niveles de pedidos, el usuario podra estar interesado en una informacin adicional, como un promedio del precio, el nmero total de unidades en existencia y as sucesivamente. Por lo tanto el resumen de la informacin es mostrado a menudo en una fila de resumen en la parte inferior del informe. El control GridView puede incluir una fila de pie de pgina en cuyas celdas se puede ingresar mediante programacin los datos agregados. Esta tarea nos plantea tres desafos: Configurar el GridView para que muestre la fila de pie de pagina Determinar el resumen de los datos, es decir cmo se calcula el precio promedio, el total de las unidades en existencia? Ingresar el resumen de los datos en las celdas apropiadas de la fila de pie de pgina. En este tutorial veremos cmo superar estos desafos. Especficamente crearemos una pgina que muestre todas las categoras en una lista desplegable, con los productos de la categora seleccionada mostrados en un control GridView. El GridView incluir una fila de pie de pgina que muestra el precio promedio y el nmero total de unidades en existencia y ordenadas para los productos de esa categora.

Figura 1. El resumen de la informacin se muestra en el pie de pgina del GridView Este tutorial con su interface Maestro/Detalle Categoras y Productos, construida bajo los conceptos tratados en el tutorial anterior Filtrado Maestro/Detalle con un DropDownList. Si todava no ha trabajado con el tutorial anterior, por favor revselo antes de continuar con este. Paso 1. Agregar el DropDownList Categories y el GridView Products Antes de preocuparnos por agregar la informacin resumida en el pie de pgina del GridView, primero construiremos el informe Maestro/Detalle. Una vez que hayamos completado este primer paso, veremos la forma de incluir el resumen de los datos. Comience abriendo la pgina SummaryDataInFooter.aspx de la carpeta

CustomFormatting. Agregue un control DropDownList y establezca su ID en Categories. Luego haga clic en el enlace Elegir Origen de datos en la etiqueta inteligente del DropDownList y seleccione la opcin agregar un nuevo ObjectDataSource llamado CategoriesDataSource CategoriesBLL. que invoque el mtodo GetCategories() de la clase

Figura 2. Agregar un nuevo ObjectDataSource llamado CategoriesDataSource

Figura 3. Hacer que el ObjectDataSource invoque el mtodo GetCategories() de la clase CategoriesBLL Despus de configurar el ObjectDataSource, el asistente nos devuelve al asistente de configuracin del origen de datos del DropDownList, en el cual necesitamos especificar cul es el valor del campo de datos que debemos mostrar y cual corresponde al valor del LisItems del DropDownList. Haga de CategoryName el campo a mostrar y utilice el CategoryID como valor.

Figura 4. Utilice los campos CategoryName y CategoryID como el Text y el Value del ListItem respectivamente Hasta el momento tenemos un DropDownList (Categories) que muestre las categoras en el sistema. Ahora necesitamos agregar un GridView que muestre los productos que pertenecen a la categora seleccionada. Sin embargo antes de hacerlo nos tomaremos un momento para revisar la casilla de verificacin Habilitar AutoPostBack en la etiqueta inteligente del DropDownList. Como se discuti en el tutorial Filtrado Maestro/Detalle con un DropDownList establezca la propiedad AutoPostBack del DropDownList en True, la pgina se actualizara cada vez que se cambie el valor del DropDownList. Esto hace que el GridView se actualice, mostrando los productos de la categora que acaba de seleccionar. Si la propiedad AutoPostBack est establecida en False (por defecto), cambiar la categora no origina ninguna devolucin de datos y por lo tanto no se actualizaran los productos de la fila.

Figura 5. Marque la opcin Habilitar AutoPostBack en la etiqueta inteligente del DropDownList Agregue un control GridView a la pgina con el fin de mostrar los productos para la categora seleccionada. Ajuste el ID del GridView a ProductsInCategory y enlacelo a un nuevo ObjectDataSource llamado ProductsInCategoryDataSource.

Figura 6. Agregar un nuevo ObjectDataSource llamado ProductsInCategoryDataSource Configure el ObjectDataSource para que invoque el mtodo

GetProductsByCategoryID(categoryID) de la clase ProductsBLL

Figura

7.

Haga

que

el

ObjectDataSource

invoque

el

mtodo

GetProductsByCategoryID(categoryID) Como el mtodo GetProductsByCategoryID(categoryID) toma un parmetro de entrada, el paso final del asistente nos pide especificar el origen del valor del parmetro. Con el fin de mostrar los productos de la categora seleccionada, tomamos el parmetro desde el DropDownList Categories.

Figura 8. Obtener el valor del parmetro categoryID de la categora seleccionada en el DropDownList

Despus de completar el asistente el GridView tendr un BoundField para cada una de las propiedades del producto. Limpiaremos los BoundField de modo que solo se muestren los BoundFields ProductName, UnitPrice, UnitsInStock y UnitsOnOrder. Sintase libre de agregar cualquier configuracin a nivel de campo a los BoundFields restantes (por ejemplo, formatear el UnitPrice como moneda). Despus de realizar estos cambios el marcado declarativo debe ser similar al siguiente:
<asp:GridView ID="ProductsInCategory" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ProductsInCategoryDataSource" EnableViewState="False"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice"> <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" SortExpression="UnitsInStock"> <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" SortExpression="UnitsOnOrder"> <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> </Columns> </asp:GridView>

Hasta el momento tenemos un Informe Maestro/Detalle funcionando plenamente que muestra el nombre, precio unitario, unidades en existencia y unidades ordenadas para los productos que pertenecen a la categora seleccionada.

Figura 9. Obtener el valor del parmetro categoryID de la categora seleccionada en el DropDownList Paso 2. Visualizacion de un pie de pgina en el GridView En GridView puede mostrar una fila de encabezado y un pie de pgina. Estas filas se muestran dependiendo de los valores de sus propiedades ShowHeader y ShowFooter, respectivamente con ShowFooter por defecto en True y ShowFooter en False. Para incluir un pie de pgina en el GridView establezca su propiedad ShowFooter en True.

Figura 10. Ajuste la propiedad ShowFooter del GridView en True

La fila de pie de pgina tiene una celda para cada uno de los campos definidos en el GridView, sin embargo por defecto estas celdas estn vacas. Tmese un momento para ver nuestro progreso en un navegador. Con la propiedad ShowFooter establecida en True, el GridView ahora incluye una fila de pie de pgina vaca.

Figura 11. El GridView ahora incluye una fila de pie de pgina La fila de pie de pgina en la figura 11 no se destaca, ya que tiene un fondo blanco. Crearemos una clase CSS FooterStyle en Style.css que especifique un color de fondo rojo oscuro y luego configure el archivo skin GridView.skin en el tema DataWebControls para que asigne esta clase CSS a la propiedad CssClass del FooterStyle del GridView. Si necesita recordar sobre skins y temas refirase de nuevo al tutorial Visualizacion de datos con el ObjectDataSource Comencemos agregando la siguiente clase CSS a Styles.css:
.FooterStyle { background-color: #a33; color: White; text-align: right; }

La clase Css FooterStyle es similar en estilo a la clase HeaderStyle, aunque el color de fondo del HeaderStyle es ms oscuro y su texto est en negrita. Adems el texto del pie de pgina est alineado a la derecha mientras que el del encabezado est centrado. Luego asocie esta clase CSS con el pie de pgina del GridView, abra el archivo GridView.skin en el tema DataWebControls y establezca la propiedad Css del FooterStyle. Despus de esta adicin el marcado del archivo debe ser similar a:
<asp:GridView runat="server" CssClass="DataWebControlStyle"> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> <RowStyle CssClass="RowStyle" /> <HeaderStyle CssClass="HeaderStyle" /> <FooterStyle CssClass="FooterStyle" /> <SelectedRowStyle CssClass="SelectedRowStyle" /> </asp:GridView>

Como muestra la captura de pantalla de la figura 12, estos cambios hacen que el pie de pgina se destaca con mayor claridad.

Figura 12. La fila de pie de pgina del GridView ahora tiene un fondo de color rojizo Paso 3. Calculo del resumen de datos Con la visualizacin del pie de pgina del GridView, el siguiente reto al que nos enfrentamos es como calcular el resumen de los datos. Existen dos formas de calcular esta informacin a agregar:

1. Por medio de una consulta SQL que podra emitir una consulta adicional a la base de datos para que calcule el resumen de los datos para una determinada categora. SQL Server incluye un numero de funciones agregadas junto con una clausula GROUP BY para especificar los datos sobre los datos que deben ser sumarizados. La siguiente consulta recuperara la informacin necesaria:
SELECT CategoryID, AVG(UnitPrice), SUM(UnitsInStock), SUM(UnitsOnOrder) FROM Products WHERE CategoryID = categoryID GROUP BY CategoryID

Por supuesto no deseamos emitir esta consulta directamente a la pgina SummaryDataInFooter.aspx, en su lugar creamos un mtodo en el ProductsTableAdapter y el ProductsBLL. 2. Calcula esta informacin ya que est siendo agregada al GridView como discutimos en el tutorial Formato Personalizado basado en los datos, el controlador de evento RowDataBound del GridView se genera para cada fila agregada al GridView luego que este ha sido enlazado. Creando un controlador de evento para este evento podemos mantener un total actualizado de los valores que deseamos agregar. Despus que la ltima fila de datos ha sido enlazada al GridView tenemos los totales y la informacin necesaria para calcular el promedio. Normalmente empleamos el segundo enfoque, ya que ahorra un viaje a la base de datos y el esfuerzo necesario para implementar la funcionalidad de resumen en la capa de acceso a datos y en la capa lgica de negocios, pero cualquier enfoque es suficiente. Para este tutorial utilizaremos la segunda opcin y realizaremos un seguimiento del total actualizado con el controlador de eventos RowDataBound. Crea un controlador del evento RowDataBound para el GridView, seleccionando el GridView en el diseador, haciendo clic en el icono del rayo en la ventana propiedades y doble clic en el evento RowDataBound. De forma alternativa puede seleccionar el GridView y su evento RowDataBound desde las listas despegables en la parte superior del archivo de la clase de cdigo subyacente de la pgina ASP.Net. Esto creara un nuevo

controlador de evento llamado ProductsInCategory_RowDataBound en la clase de cdigo subyacente de la pgina SummaryDataInFooter.aspx


Protected Sub ProductsInCategory_RowDataBound _ (sender As Object, e As GridViewRowEventArgs) _ Handles ProductsInCategory.RowDataBound End Sub

Con el fin de mantener un total actualizado necesitamos definir las variables fuera del alcance del controlador de eventos. Cree las siguientes cuatro variables a nivel de pgina: _totalUnitPrice de tipo Decimal _totalNonNullUnitPrice de tipo Integer _totalUnitsInStock de tipo Integer _totalUnitsOnOrder de tipo Integer

Luego escriba el cdigo para incrementar estas tres variables para cada fila de datos encontrada en el controlador de evento RowDataBound.
Dim _totalUnitPrice As Decimal = 0 Dim _totalNonNullUnitPriceCount As Integer = 0 Dim _totalUnitsInStock As Integer = 0 Dim _totalUnitsOnOrder As Integer = 0 Protected Sub ProductsInCategory_RowDataBound _ (sender As Object, e As GridViewRowEventArgs) _ Handles ProductsInCategory.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then Dim product As Northwind.ProductsRow = _ CType(CType(e.Row.DataItem, DataRowView).Row, Northwind.ProductsRow) If Not product.IsUnitPriceNull() Then _totalUnitPrice += product.UnitPrice _totalNonNullUnitPriceCount += 1 End If If Not product.IsUnitsInStockNull() Then _totalUnitsInStock += product.UnitsInStock End If If Not product.IsUnitsOnOrderNull() Then

_totalUnitsOnOrder += product.UnitsOnOrder End If ElseIf e.Row.RowType = DataControlRowType.Footer Then Dim avgUnitPrice As Decimal = _ _totalUnitPrice / CType(_totalNonNullUnitPriceCount, Decimal) e.Row.Cells(1).Text = "Avg.: " & avgUnitPrice.ToString("c") e.Row.Cells(2).Text = "Total: " & _totalUnitsInStock.ToString() e.Row.Cells(3).Text = "Total: " & _totalUnitsOnOrder.ToString() End If End Sub

El controlador del evento RowDataBound inicia asegurndose que est tratando con un DataRow. Una vez que se ha establecido, la instancia Northwind.ProductsRow que fue enlazada al objeto GridViewRow en e.Row es almacenada en la variable product. Luego las variables totales actualizadas son incrementadas por los valores correspondientes a los productos actuales (asumiendo que no contienen valores de base de datos Null). Hacemos seguimiento del total de UnitPrice actualizado y el nmero de registros noNull UnitPrice porque el precio promedio es el cociente de estos dos nmeros. Paso 4. Mostrar el resumen de datos en el pie de pgina Con el resumen de datos totalizado, el ltimo paso es mostrarlo en la fila de pie de pgina del GridView. Esta tarea tambin puede ser realizada mediante programacin a travs del controlador del evento RowDataBound. Recordemos que el evento RowDataBound se genera para todas las filas que son enlazadas al GridView, incluyendo la fila de pie de pgina. Por lo tanto podemos ampliar nuestro controlador de evento para que muestre los datos en la fila de pie de pgina usando el siguiente cdigo:
Protected Sub ProductsInCategory_RowDataBound _ (sender As Object, e As GridViewRowEventArgs) _ Handles ProductsInCategory.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then ... Increment the running totals ... ElseIf e.Row.RowType = DataControlRowType.Footer ... Display the summary data in the footer ... End If End Sub

Como la fila de pie de pgina es agregada al GridView despus que todas las filas de datos han sido agregadas, podemos estar seguros que en el momento en que vayamos a mostrar el resumen de datos en el pie de pgina, la actualizacin del clculo de los totales ya se ha completado. El ltimo paso es establecer estos valores en las celdas del pie de pgina. Para mostrar el texto en una celda en particular del pie de pgina, usamos e.Row.Cells(index).Text=value , donde la indexacin de las celdas comienza en 0. El siguiente cdigo calcula el precio promedio (el precio total divido entre el numero de productos) y los muestra junto con el nmero total de unidades en stock y las unidades ordenadas en las celdas apropiadas del pie de pgina del control GridView.
Protected Sub ProductsInCategory_RowDataBound _ (sender As Object, e As GridViewRowEventArgs) _ Handles ProductsInCategory.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then ... <i>Increment the running totals</i> ... ElseIf e.Row.RowType = DataControlRowType.Footer Dim avgUnitPrice As Decimal = _ _totalUnitPrice / CType(_totalNonNullUnitPriceCount, Decimal) e.Row.Cells(1).Text = "Avg.: " & avgUnitPrice.ToString("c") e.Row.Cells(2).Text = "Total: " & _totalUnitsInStock.ToString() e.Row.Cells(3).Text = "Total: " & _totalUnitsOnOrder.ToString() End If End Sub

La figura 13 muestra el informe luego que se ha agregado el cdigo. Note como el ToString(c) hace que el resumen de la informacin sobre el precio promedio sea formateado como una moneda.

Figura 13. Resumen Visualizar el resumen de los datos es requisito comn en los informes y el control GridView hace que sea fcil incluir esta informacin en su fila de pie de pgina. La fila de pie de pgina se muestra cuando la propiedad ShowFooter del GridView se establece en True y puede tener el texto en sus celdas establecindolo mediante programacin a travs del controlador de eventos del RowDataBound. El clculo del resumen de datos puede hacerse consultando de nuevo a la base de datos o mediante el uso de la clase de cdigo subyacente de la pgina ASP.Net para calcular mediante programacin el resumen de los datos. Con este tutorial concluimos nuestro examen del Formato personalizado con los controles GridView, DetailsView y FormView. En nuestro siguiente tutorial iniciaremos la exploracin de cmo insertar, actualizar y eliminar datos utilizando estos mismos controles.

EDICIN, INSERCIN Y ELIMINACIN DE DATOS

16.

UN RESUMEN DE LA EDICIN, INSERCIN Y ELIMINACIN DE DATOS

En este tutorial veremos cmo asignar los mtodos Insert(), Update() y Delete() del ObjectDataSource a los mtodos de las clases BLL, as como la forma de configurar los controles GridView, FormView y DetailsView para proporcionar la capacidad de modificacin de datos. Introduccin Durante varios de los tutoriales anteriores hemos estudiado la forma de mostrar los datos en una pgina web ASP.Net por medio de los controles GridView, DetailsView y FormView. Estos controles solo trabajan con los datos que se les suministran. Comnmente estos controles acceden a los datos por medio de un control de origen de datos, como el ObjectDataSource. Hemos visto que el ObjectDataSource acta como un proxy entre la pgina ASP.Net y los datos subyacentes. Cuando un GridView necesita mostrar datos, invoca el mtodo Select() de su ObjectDataSource, el cual a su vez invoca un mtodo de nuestra capa lgica de negocios (BLL), el cual llama un mtodo del TableAdapter apropiado en la capa de acceso a datos DAL; la cual a su vez enva una consulta SELECT a la base de datos Northwind. Recordemos que cuando creamos los TableAdapters de la DAL en nuestro primer tutorial, Visual Studio agreg automticamente mtodos para insertar, actualizar y eliminar datos desde la tabla de la base de datos subyacente. Por otra parte, en la creacin de la capa lgica de negocios diseamos mtodos en la BLL que llaman a estos mtodos de modificacin de datos de la DAL. Adems de su mtodo Select(), el ObjectDataSource tiene mtodos Insert(), Delete() y Update(). Al igual que el mtodo Select, estos tres mtodos se pueden asignar a los mtodos de un objeto subyacente. Cuando se configuran los controles GridView, FormView y DetailsView para insertar, agregar o actualizar datos, estos proporcionan una interfaz de usuario para modificar los datos subyacentes. Esta interfaz de usuario llama a los mtodos Insert (), Update() y Delete() del ObjectDataSource, que luego invoca a los mtodos asociados del objeto subyacente (Ver Figura 1).

Figura 1. Los mtodos Insert (), Update () y Delete () del ObjectDataSource sirven como proxy en la BLL En este tutorial veremos cmo asignar los mtodos Insert(), Update() y Delete() del ObjectDataSource a los mtodos de las clases en la BLL, as como la forma de configurar los controles GridView, FormView y DetailsView para proporcionar la capacidad de modificacin de datos. Paso 1: Creacin de pginas Web de tutoriales de Insercin, Actualizacin y Eliminacin. Antes de empezar a explorar la forma de insertar, actualizar y eliminar datos, primero tommonos un momento para crear las pginas Asp.Net que utilizaremos en este y en los prximos tutoriales, para el sitio web de nuestro proyecto. Comencemos agregando una carpeta llamada EditInsertDelete. Luego agregue las siguientes pginas Asp.Net a la carpeta, asegurndose de asociar cada pgina con la pagina principal Site.Master. Default.aspx Basics.aspx DataModificationEvents.aspx ErrorHandling.aspx UIValidation.aspx CustomizedUI.aspx OptimisticConcurrency.aspx ConfirmationOnDelete.aspx

UserLevelAcess.aspx

Figura 2. Agregar las pginas Asp.Net para los tutoriales relacionados con la modificacin de datos Al igual que en las otras carpetas, Default.aspx en la carpeta EditInsertDelete mostrar una lista de los tutoriales de su seccin. Recordemos que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Por lo tanto aadimos este control de usuario a Default.aspx, arrastrndolo desde el explorador de soluciones a la vista de diseo de la pgina.

Figura 3. Agregue el control de usuario SectionLevelTutorialListing.ascx a Default.aspx Por ltimo, agregue las pginas como entradas al archivo Web.Sitemap. En concreto agregue el siguiente marcado despus del nodo <SiteMap> de Formato Personalizado:
<siteMapNode title="Editing, Inserting, and Deleting" url="~/EditInsertDelete/Default.aspx" description="Samples of Reports that Provide Editing, Inserting, and Deleting Capabilities"> <siteMapNode url="~/EditInsertDelete/Basics.aspx" title="Basics" description="Examines the basics of data modification with the GridView, DetailsView, and FormView controls." /> <siteMapNode url="~/EditInsertDelete/DataModificationEvents.aspx" title="Data Modification Events" description="Explores the events raised by the ObjectDataSource pertinent to data modification." /> <siteMapNode url="~/EditInsertDelete/ErrorHandling.aspx" title="Error Handling" description="Learn how to gracefully handle exceptions raised during the data modification workflow." /> <siteMapNode url="~/EditInsertDelete/UIValidation.aspx" title="Adding Data Entry Validation" description="Help prevent data entry errors by providing validation." /> <siteMapNode url="~/EditInsertDelete/CustomizedUI.aspx" title="Customize the User Interface" description="Customize the editing and inserting user interfaces." /> <siteMapNode url="~/EditInsertDelete/OptimisticConcurrency.aspx"

title="Optimistic Concurrency" description="Learn how to help prevent simultaneous users from overwritting one another s changes." /> <siteMapNode url="~/EditInsertDelete/ConfirmationOnDelete.aspx" title="Confirm On Delete" description="Prompt a user for confirmation when deleting a record." /> <siteMapNode url="~/EditInsertDelete/UserLevelAccess.aspx" title="Limit Capabilities Based on User" description="Learn how to limit the data modification functionality based on the user role or permissions." /> </siteMapNode>

Despus de actualizar el Web.SiteMap tmese un tiempo para visualizar el sitio web de tutoriales a travs de un navegador. El men de la izquierda ahora incluye los artculos de los tutoriales de edicin, actualizacin y eliminacin.

Figura 4. El SiteMap ahora incluye entradas a los tutoriales de Edicin, Insercin y Eliminacin Paso2: Agregar y configurar el ObjectDataSource Dado que el GridView, DetailsView y el FormView difieren en su capacidad de modificacin y diseo, examinaremos cada uno de ellos de forma individual. Sin embargo, en lugar de tener cada control usando su propio ObjectDataSource, crearemos un nico ObjectDataSource que los tres controles puedan compartir.

Abra la pgina Basics.aspx, arrastre un ObjectDataSource desde el cuadro de herramientas hasta el diseador y haga clic en el enlace Configurar su origen de datos desde su etiqueta inteligente. Como la clase ProductsBLL es la nica clase que utiliza mtodos de edicin, insercin y eliminacin, entonces configuramos el ObjectDataSource para que utilice esta clase.

Figura 5. Configure el ObjectDataSource para utilizar la clase ProductsBLL En la siguiente pantalla, podemos especificar qu mtodos de la clase ProductsBLL se asignan a los mtodos Select(), Update(), Delete() e Insert() del ObjectDataSource, seleccionando la pestaa adecuada y escogiendo el mtodo de la lista desplegable. A estas alturas, la figura 6 debera resultar familiar, asigne el mtodo Select() del ObjectDataSource al mtodo GetProducts() de la clase ProductsBLL. Los mtodos Insert(), Update() y Delete() pueden ser configurados seleccionando la pestaa apropiada de la lista superior.

Figura 6. Haga que el ObjectDataSource devuelva todos los productos Las figuras 7, 8 y 9 muestran las pestaas INSERT, UPDATE y DELETE del ObjectDataSource. Configure estas pestaas para que los mtodos Insert(), Update() y Delete() invoquen los mtodos AddProduct, UpdateProduct y DeleteProduct de la clase ProductsBLL respectivamente.

Figura 7. Asignacin del mtodo Update() del ObjectDataSource al mtodo UpdateProduct de la clase ProductsBLL

Figura 8. Asignacin del mtodo Insert() del ObjectDataSource al mtodo AddProduct de la clase ProductsBLL

Figura 9. Asignacin del mtodo Delete() del ObjectDataSource al mtodo DeleteProduct de la clase ProductsBLL Usted puede haber notado que las listas desplegables de las pestaas UPDATE, INSERT y DELETE ya tenan seleccionados estos mtodos. Esto es gracias a nuestro uso del DataObjectMethodAttribute que decora los mtodos de ProductsBLL. Por ejemplo, el mtodo DeleteProduct tiene la siguiente firma:

<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteProduct(ByVal productID As Integer) As Boolean End Function

El atributo DataObjectMethodAttribute indica el propsito de cada mtodo, ya sea para seleccionar, insertar, actualizar o eliminar y si este es o no el valor por defecto. Si se omiten estos atributos cuando se crean las clases BLL, tendr que seleccionar manualmente los mtodos desde las pestaas INSERT, UPDATE y DELETE. Asegrese que se asignen los mtodos apropiados de la clase ProductsBLL a los mtodos Insert (), Update () y Delete () del ObjectDataSource y luego haga clic en Finalizar para completar el asistente. Examine el marcado del ObjectDataSource Despus de configurar el ObjectDataSource por medio del asistente, vaya a la vista cdigo fuente para examinar el marcado declarativo generado. La etiqueta <asp:ObjectDataSource> especifica el objeto y los mtodos subyacentes a invocar. Adems hay DeleteParameters, UpdateParameters e InsertParemeters que se asignan a los parmetros de entrada para los mtodos AddProduct, UpdateProduct y DeleteProduct de la clase ProductsBLL
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct" InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL" UpdateMethod="UpdateProduct"> <DeleteParameters> <asp:Parameter Name="productID" Type="Int32" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" />

<asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> </InsertParameters> </asp:ObjectDataSource>

El ObjectDataSource incluye un parmetro para cada uno de los parmetros de entrada de sus mtodos asociados; al igual que una lista de SelectParameter est presente cuando el ObjectDataSource es configurado para llamar a un mtodo de seleccin que espera un parmetro de entrada (como GetProductsByCategoryId (categoryID)). Como veremos prontamente los valores de DeleteParameters, UpdateParameters e InsertParameters se establecen automticamente para que el GridView, FormView y DetailsView invoque los mtodos Update(), Insert() y Delete() del ObjectDataSource. Estos valores tambin pueden establecerse mediante programacin si es necesario, como veremos en un futuro tutorial. Uno de los efectos secundarios de usar el asistente para configurar el ObjectDataSource es que Visual Studio establece la propiedad OldValuesParameterFormatString a original {0}. Este valor de la propiedad es usado para incluir los valores originales de los datos que se estn editando y es til en dos situaciones: Si al editar un registro, el usuario es capaz de cambiar la llave principal. En este caso, tanto el nuevo valor de la llave principal y el valor original de la misma, deben ser proporcionados de manera que se pueda encontrar el registro con el valor original de la llave principal y en consecuencia actualizar su valor.

Cuando usamos la concurrencia optimista. La concurrencia optimista es una tcnica para asegurar que dos usuarios simultneos no sobrescriban los cambios del otro, este es un tema que veremos en un tutorial futuro.

La propiedad OldValuesParameterFormatString indica el nombre de los parmetros de entrada para los valores originales en los mtodos Update y Delete del objeto subyacente. Hablaremos con mayor detalle de esta propiedad y de su propsito ms adelante cuando exploremos la concurrencia optimista. Sin embargo, lo traigo ahora, debido a que nuestros mtodos BLL no esperan los valores originales y por lo tanto es importante que eliminemos esta propiedad. en del Si un dejamos valor la propiedad al valor el OldValuesParameterFormatString invocar los mtodos Update() establecida o Delete() diferente ya

predeterminado ({0}), se producir un error cuando el control de datos web intente ObjectDataSource que ObjectDataSource intentar pasar tanto los UpdateParameters o DeleteParameters establecidos, junto con el valor original de los parmetros. Si no est muy claro en este momento, no se preocupe, examinaremos esta propiedad y su funcionalidad en un futuro tutorial. Por ahora solo tenga la certeza de borrar por completo esta declaracin de propiedad de la sintaxis declarativa o establecerla en el valor predeterminado ({0}). Nota: Si solo limpia el valor de la propiedad OldvaluesParameterFormatString desde la ventana propiedades en la vista diseo, la propiedad seguir existiendo en la sintaxis declarativa, pero estableciendo una cadena vaca. Esto por desgracia se traducir en el mismo problema mencionado anteriormente. Por lo tanto quite la propiedad totalmente de la sintaxis declarativa o desde la ventana propiedades establezca el valor predeterminado {0}. Paso 3: Agregue un control de datos Web y configrelo para la modificacin de datos Una vez que aada el ObjectDataSource a la pgina y lo haya configurado, estamos listos para agregar los controles de datos web a la pgina para mostrar los datos y proporcionar un medio al usuario para que pueda modificarlos. Veremos el GridView, FormView y DetailsView separadamente, ya que estos controles de datos web difieren en su capacidad de modificacin de datos y su configuracin.

Como veremos en el resto de este articulo, agregar un soporte bsico de edicin, insercin y eliminacin por medio de los controles GridView, FormView y DetailsView es realmente tan simple como agregar un par de casillas de verificacin. Hay muchas sutilezas y filos en los casos del mundo real que hacen que proporcionar dicha funcionalidad sea ms complicado que simplemente apuntar y hacer clic. Sin embargo, en este tutorial nos centraremos nicamente en demostrar una capacidad sencilla de modificacin de datos. En los tutoriales futuros, estudiaremos los problemas que muy seguramente surgirn en un ambiente del mundo real. Eliminacin de datos desde el GridView Comience arrastrando un GridView desde el cuadro de herramientas hasta el diseador. Luego enlace el ObjectDataSource al GridView seleccionando este en la lista desplegable de la etiqueta inteligente del GridView. Hasta este momento el marcado declarativo del GridView ser el siguiente:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="ObjectDataSource1"> <Columns> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" DataKeyNames="ProductID"

SortExpression="Discontinued" /> <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True" SortExpression="SupplierName" /> </Columns> </asp:GridView>

Enlazar el ObjectDataSource por medio de su etiqueta inteligente tiene dos ventajas: Los BoundFields y los CheckBoxFields se crean automticamente para cada uno de los campos devueltos por el ObjectDataSource. Por otro lado las propiedades de los BoundFields y los CheckBoxFields son establecidas sobre la base de metadatos del campo subyacente. Por ejemplo los campos ProductID, CategoryName y SupplierName estn marcados como de solo lectura en el ProductsDataTable y por lo tanto no deben ser actualizados durante la edicin. Para adaptarse a esto, se establece la propiedad ReadOnly de estos BoundFields en Verdadero. La propiedad DataKeyNames se asigna al campo(s) de la llave primaria del objeto subyacente. Esto es esencial cuando se utiliza el control GridView para editar o eliminar datos, ya que esta propiedad indica el campo (o conjunto de campos) que identifican de forma nica a cada registro. Para obtener ms informacin sobre la propiedad DataKeyNames, refirase de nuevo al tutorial Reporte Maestro/Detalle usando un GridView maestro seleccionable con un detalle DetailsView. Aunque el GridView puede ser enlazado al ObjectDataSource por medio de la ventana propiedades o por medio de la sintaxis declarativa, hacer ello requiere agregar manualmente los apropiados BoundFields y el marcado del DataKeyNames. El control GridView proporciona un soporte integrado para la edicin y eliminacin a nivel de fila. Configurando un GridView para que soporte la eliminacin, agregamos una columna de botones de eliminacin. Cuando el usuario final, hace clic en el botn eliminar para una fila en particular, se genera una devolucin de datos y el GridView sigue los siguientes pasos:

1. Son asignados los valores DeleteParameters del ObjectDataSource. 2. Es invocado el mtodo Delete() del ObjectDataSource, eliminando el registro especificado. 3. El control GridView se vuelve a enlazar al ObjectDataSource invocando su mtodo Select(). Los valores asignados al DeleteParameters son los valores de los campos

DataKeyNames de la fila en la que se ha dado clic al botn Eliminar. Por lo tanto es vital que la propiedad DataKeyNames del GridView se establezca correctamente. Si esta es perdida, se asignar el valor de Nothing al DeleteParameters en el paso 1, lo que originar que ningn registro sea eliminado en el paso 2. Nota: La coleccin DataKeyNames es almacenada en el control de estado del GridView, lo que significa que los valores DataKeys sern recordados por medio de la devolucin de datos incluso si el ViewState del GridView se ha deshabilitado. Sin embargo es muy importante que el ViewState quede habilitado para los GridViews que soporten eliminacin o actualizacin (el comportamiento por defecto). Si establece la propiedad EnableViewState del GridView en Falso, el comportamiento de edicin y eliminacin funcionan bien para un solo usuario, pero si existen varios usuarios al tiempo eliminando datos, existe la posibilidad que accidentalmente borren o editen los registros de los otros usuarios. Esta misma advertencia se aplica tambin para los DetailsView y los FormViews. Para agregar capacidades de eliminacin en un GridView, simplemente vaya a su etiqueta inteligente y seleccione la opcin Habilitar Eliminacin.

Figura 10. Seleccione la casilla Habilitar Eliminacin Habilitando la opcin Habilitar Eliminacin desde la etiqueta inteligente, se aade un CommandField al GridView. El CommandField presenta una columna con botones en el GridView para realizar una o varias de las siguientes tareas: la seleccin de un registro, la edicin de un registro y la eliminacin de un registro. Anteriormente vimos el CommandField en accin con la seleccin de registros en el tutorial Registros Maestro/Detalles DetailsView. El CommandField contiene una serie de propiedades ShowXButton que indica que serie de botones son mostrados en el CommandField. Al marcar la casilla de verificacin Habilitar Eliminacin, se agrega a la coleccin de columnas del GridView un CommandField cuya propiedad ShowDeleteButton es True.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" /> ... BoundFields removed for brevity ... </Columns> </asp:GridView>

utilizando

un

GridView

Maestro

Seleccionable

un

detalle

Hasta este punto lo creas o no, hemos terminado con la adicin del soporte de eliminacin del GridView! En la figura 11 se muestra que cuando visitamos esta pgina desde un navegador existe una columna de botones Eliminar.

Figura 11. El CommandField agrega una columna de botones Eliminar Si usted ha estado construyendo este tutorial desde el principio por su cuenta, al probar esta pgina haciendo clic en el botn eliminar se producir una excepcin. Contine leyendo porque se presentan estas excepciones y como solucionarlas. Nota: Si usted est siguiendo este tutorial a travs de la descarga que los acompaa, estos problemas ya han sido solucionados. Sin embargo, le animo a leer los detalles que se indican a continuacin para identificar los problemas que pueden surgir y las soluciones adecuadas. Si al intentar eliminar un producto, se obtiene una excepcin cuyo mensaje es similar a ObjectDataSource, ObjectDatSource1 no puede encontrar un mtodo no genrico DeleteProduct que tenga parmetros: ProductID, Original_ProductID, es probable que haya olvidado quitar intentar la propiedad pasar los OldValuesParameterFormatString parmetros de entrada ProductID del y ObjectDataSource. Con la propiedad OldValuesParameterFormatString definida, el ObjectDataSource Original_ProductID al mtodo DeleteProduct. Sin embargo, DeleteProduct solo acepta un nico parmetro de entrada, por lo cual se genera la excepcin. Extrayendo la propiedad OldValuesParameterFormatString (o si se establece en {0}) indicamos al ObjectDataSource que no trate de pasar el valor original al parmetro de entrada.

Figura 12. Asegrese que la propiedad OldValuesParameterFormatString ha sido removida Incluso si ha quitado la propiedad OldValuesParameterFormatString, usted seguir recibiendo una excepcin cuando trate de eliminar un producto, con el mensaje: Conflicto con la sentencia DELETE con la restriccin REFERENCE FK_Order_Details_Products. La base de datos Northwind contiene una restriccin de llave fornea entre la tabla Products y la tabla OrderDetails, lo que significa que un producto no puede ser eliminado del sistema si hay uno o ms registros relacionados con l en la tabla OrderDetails. Puesto que cada producto en la base de datos Northwind tiene al menos un registro en la tabla OrderDetails, no se puede eliminar ninguno de los productos hasta que se eliminen primero los productos asociados con los registros de detalle de orden.

Figura 13. Una restriccin de llave externa prohbe la eliminacin de Productos Para nuestro tutorial eliminaremos todos los registros de la tabla OrderDetails. En una aplicacin del mundo real necesitaramos: Tener otra pantalla para administrar la informacin de los detalles de pedido. Aumentar el mtodo DeleteProduct para incluir la lgica para eliminar los detalles de orden del producto especificado. Modificar la consulta T-SQL utilizada por el TableAdapter para incluir la eliminacin de los detalles de pedidos del producto especificado. Vamos a examinar todos los registros de la tabla OrderDetails para eludir la restriccin de la llave fornea. Vaya al explorador de servidores en Visual Studio, haga clic en el nodo Northwind.mdf y seleccione nueva consulta. Luego en la ventana de consulta, ejecute la instruccin SQL: DELETE FROM [OrderDetails]

Figura 14. Eliminar todos los registros de la tabla OrderDetails Despus de limpiar la tabla OrderDetails al hacer clic en el botn Eliminar, se borrar el producto sin ningn error. Si al hacer clic en el botn eliminar no se elimina el producto, asegrese de que la propiedad DataKeysNames se establece en el campo de la llave primaria (productID). Nota: Cuando hacemos clic en el botn Eliminar se produce una devolucin de datos y el registro se elimina. Esto puede ser peligroso ya que es fcil hacer clic accidentalmente en el botn Eliminar de la fila equivocada. En un futuro tutorial veremos cmo agregar una confirmacin del lado del cliente al eliminar un registro. Edicin de datos con el GridView Junto con la eliminacin, el GridView proporciona un soporte integrado de edicin a nivel de fila, configurando un GridView para que soporte la edicin agregamos una columna de botones de edicin. Desde la perspectiva del usuario final, al hacer clic en el botn editar de una fila, hacemos que la fila sea editable, convirtiendo las celdas en TextBox que contienen los valores existentes y remplazando el botn editar por los botones actualizar y cancelar. Despus de realizar los cambios deseados, el usuario puede hacer clic en actualizar para confirmar los cambios o en el botn cancelar para descartarlos. En cualquiera de los casos, despus de hacer clic en los botones actualizar o cancelar, el GridView regresa a su estado anterior a la edicin.

Desde nuestra perspectiva como desarrolladores de pginas, cuando el usuario hace clic en el botn editar de una fila determinada, se produce una devolucin de datos y el GridView realiza los siguientes pasos: 1. La propiedad EditItemIndex del GridView se asigna al ndice de la fila en la que se hace clic en el botn editar. 2. El control GridView vuelve a enlazarse con el ObjectDataSource invocando su mtodo Select (). 3. Se presenta en modo de edicin la fila cuyo ndice coincida con el valor de EditItemIndex. En este modo, el botn editar es sustituido por los botones actualizar y cancelar, adems los BoundFields cuyas propiedades ReadOnly son falsas (por defecto), se presentan como controles TextBox con sus propiedades Text asignadas a los valores de los datos de los campos. Hasta el momento el marcado es devuelto al navegador, permitindole al usuario final hacer los cambios a los datos de la fila. Cuando el usuario hace clic en el botn actualizar, se produce una devolucin de datos y el GridView realiza los siguientes pasos: 1. Los valores UpdateParameters del ObjectDataSource se asignan a los valores finales ingresados por el usuario en la interfaz de edicin del GridView. 2. Se invoca el mtodo Update() del ObjectDataSource y se realiza la actualizacin del registro especificado. 3. El control GridView se vuelve a enlazar con el ObjectDataSource invocando su mtodo Select(). Los valores de la llave principal asignados al UpdateParameters en el paso 1, proceden de los valores especificados en la propiedad DataKeysNames, mientras que los valores de las llaves no primarias provienen del texto en los controles TextBox de la fila editada. En la eliminacin es vital que la propiedad DataKeysNames del GridView se establezca correctamente, ya que si esta es perdida, se asignara un valor de Nothing al valor de la llave principal en el UpdateParameters en el paso 1, lo que dar lugar a que ningn registro sea actualizado en el paso 2. La funcin de edicin se puede activar sencillamente seleccionando la casilla Habilitar edicin en la etiqueta inteligente del GridView.

Figura 15. Seleccin de la casilla Habilitar Edicin Seleccionando la casilla Habilitar Edicin, se agregara un CommandField (si es necesario) y se establece su propiedad ShowEditButton en True. Como vimos anteriormente, el CommandField contiene una serie de propiedades ShowXButton que indican la serie de botones que son mostrados en el CommandField. Seleccionando la casilla Habilitar Edicin agregamos la propiedad ShowEditButton al CommandField actual:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" /> ... BoundFields removed for brevity ... </Columns> </asp:GridView>

Esto es todo lo que hay que hacer para agregar un soporte de edicin rudimentario. Como muestra la figura 16, la interfaz de edicin es bastante simple, cada Boundfield cuya propiedad ReadOnly se establezca en True (por defecto), se representa como un cuadro de texto. Esto incluye campos como CategoryID y SupplierID, que son claves para otras tablas.

Figura 16. Haciendo clic en el botn editar de Chao mostramos la fila en modo de edicin Adems de solicitar a los usuarios que editen directamente los valores de las llaves forneas, la interfaz de edicin tiene falencias en las siguientes formas: 1. Si es usuario ingresa un CategoryID o un SupplierID que no existe en la base de datos, el UPDATE violar una restriccin de llave externa, generando una emisin de excepcin. 2. La interfaz de edicin no incluye ningn tipo de validacin. Si usted no proporciona un valor requerido, por ejemplo ProductName o escribe un valor de cadena donde se espera un valor numrico, por ejemplo al introducir Demasiado en el cuadro de texto UnitPrice, se emitir una excepcin. En un tutorial futuro examinaremos como agregar controles de validacin a la interfaz de edicin del usuario. 3. En la actualidad, todos los campos que no son de lectura, deben ser incluidos en el GridView. Si tuviramos que quitar un campo del GridView, por ejemplo UnitPrice, al actualizar los datos del GridView no se establecera el valor de UnitPrice en el UpdateParameters, lo cual cambiaria el valor UnitPrice del registro de la base de datos a Null. Del mismo modo, si un campo obligatorio como ProductName se quita del GridView, la actualizacin fallara por la excepcin Columna ProductName no permite valores nulos mencionada anteriormente. 4. El formato de la interfaz de edicin deja mucho que desear. El UnitPrice se muestra con cuatro decimales. Lo ideal sera que los valores de CategoryID y SupplierID tengan dos DropDownLists que muestren una lista de categoras y proveedores del sistema.

Todas estas deficiencias son con las que tendremos que vivir por ahora, pero se abordaran en tutoriales futuros. Insertar, Editar y Eliminar datos con el DetailsView Como hemos visto en los tutoriales anteriores, el control DetailsView muestra un registro a la vez y al igual que el GridView permite la edicin y eliminacin del registro que muestra. Tanto la experiencia del usuario final con los elementos de edicin y eliminacin del DetailsView, como el flujo de trabajo desde el lado de Asp.Net es idntica a la del GridView. Donde el DetailsView difiere del GridView es en el soporte predefinido de edicin proporcionado. Para mostrar las capacidades de modificacin de datos del DetailsView, comencemos agregando un DetailsView a la pgina Basic.aspx encima del GridView existente y enlcelo con el ObjectDataSource vigente por medio de las etiquetas inteligentes del DetailsView. Luego limpie las propiedades Height y Width del DetailsView y seleccione la opcin Habilitar paginacin en la etiqueta inteligente. Para habilitar el soporte de edicin, actualizacin y eliminacin, simplemente seleccione las opciones Habilitar Edicin, Habilitar insercin y Habilitar eliminacin en la etiqueta inteligente.

Figura 17. Configurar el soporte de edicin, actualizacin y eliminacin del DetailsView Al igual que con el GridView, agregando el soporte de edicin, actualizacin y eliminacin; agregamos un CommandField al DetailsView como se muestra en la siguiente sintaxis declarativa:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"

DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <Fields> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True" SortExpression="SupplierName" /> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowInsertButton="True" /> </Fields> </asp:DetailsView>

Tenga en cuenta que para el DetailsView, el CommandField aparece de forma predeterminada al final de la coleccin Columns. Como los campos del DetailsView se representan como filas, el CommandField aparece como una fila con los botones insertar, actualizar y eliminar en la parte inferior del mismo.

Figura 18. Configuracin del soporte de edicin, insercin y actualizacin del DetailsView Dando clic en el botn Delete comienza la misma secuencia de eventos que con el GridView, una devolucin de datos seguido por el DetailsView llenando el DeleteParameters del ObjectDataSource basado en los valores del DataKeysNames y finaliza con una llamada al mtodo Delete() del ObjectDataSource, que en realidad elimina el producto de la base de datos. La edicin en el DetailsView tambin funciona de manera idntica que en el GridView. Para la insercin, la interfaz de usuario presenta un botn New que cuando se hace clic, hace que el DetailsView este en modo de edicin. Con el modo de insercin, el botn New, se remplaza por los botones insertar y cancelar y solo se muestran los BoundFields cuya propiedad InsertVisible se establezca en verdadero (por defecto). Los campos identificados como campos de valores incrementados automticamente, tales como ProductID, tienen su propiedad InsertVisible establecida en Falso cuando se enlaza el origen de datos al DetailsView por medio de su etiqueta inteligente. Al enlazar un origen de datos al DetailsView por medio de la etiqueta inteligente, Visual Studio establece la propiedad InsertVisible en falso solo para los campos de incremento automtico. Solo los campos de lectura como CategoryName, SupplierName se mostraran en modo de insercin en la interfaz del usuario a menos que su propiedad

InsertVisible se establezca explcitamente en Falso. Tmese un momento para establecer la propiedad InsertVisible de estos dos campos en Falso, ya sea a travs de la sintaxis declarativa del DetailsView o a travs del enlace de edicin de campos en la etiqueta inteligente. La figura 19 muestra como establecer las propiedades InsertVisible a Falso, haciendo clic en el enlace editar campos.

Figura 19. Establecer las propiedades InsertVisible a falso a travs de la etiqueta inteligente Despus de ajustar las propiedades InsertVisible, vea la pgina Basics.aspx en el navegador y haga clic en el botn nuevo. La figura 20 muestra el DetailsView al agregar una nueva bebida, Acme T, a nuestra lnea de productos.

Figura 20. Northwind Traders ahora ofrece T Acme.

Despus de ingresar los detalles para T Acme y hacer clic en el botn Insertar, se produce una devolucin de datos y se aade el nuevo registro a la tabla Products de la base de datos. Como el DetailsView muestra los productos en el orden en que existen en la tabla de la base de datos, debemos ir a la ltima pgina para ver el ltimo producto ordenado con el fin de ver el nuevo producto.

Figura 21. Detalles de Acme T Nota: La propiedad CurrentMode del DetailsView indica que interfaz est siendo mostrada y puede ser de uno de los siguientes valores: Edit, Insert o ReadOnly. La propiedad DefaultMode indica el modo al que los DetailsView regresan despus que se realiza una edicin o insercin y es til para mostrar que un DetailsView est permanentemente en modo de edicin o insercin. Hasta este punto las capacidades de insercin y edicin del DetailsView sufren las mismas limitaciones que el control GridView: el usuario debe introducir los valores de CategoryID y SupplierID a travs de su cuadro de texto, la interfaz no tiene ninguna lgica de validacin, todos los campos de los productos que no permiten valores Null o no tienen un valor predeterminado especificado a nivel de base de datos deben ser incluidos en la interface de edicin, entre otros.

Las tcnicas que examinaremos para la ampliacin y mejoramiento de la interfaz de edicin del GridView en los artculos futuros, pueden aplicarse tambin a las interfaces de edicin e insercin del control DetailsView. Uso del FormView para una interfaz de usuario de modificacin de datos ms flexible El FormView ofrece soporte para insercin, edicin y eliminacin de datos, pero debido a que usa plantillas en lugar de campos, no hay lugar para agregar los BoundFields o el CommandField usados por los controles GridView y DetailsView para proporcionar una interface de modificacin de datos. En su lugar los controles Web de esta interface recogen las entradas del usuario cuando agregamos un nuevo elemento o editamos uno ya existente, junto con los botones Nuevo, Editar, Borrar, Insertar, Actualizar y Cancelar que deben agregarse manualmente a las plantillas correspondientes. Afortunadamente Visual Studio crear automticamente la interfaz necesaria cuando enlacemos el FormView a una fuente de datos a travs de la lista desplegable de su etiqueta inteligente. Para ilustrar estas tcnicas, comience agregando un FormView a la pgina Basic.aspx, desde la etiqueta inteligente que ya del FormView, Esto enlazamos generar un este FormView al ObjectDataSource creamos. EditItemTemplate,

InsertItemTemplate y un ItemTemplate con los controles web TextBox para recoger la entrada del usuario y los controles Web Button para Nuevo, Editar, Borrar, Insertar Actualizar, Actualizar y Cancelar. Adems la propiedad DataKeysNames se establece en el campo de la llave primaria (ProductID) del objeto devuelto por el ObjectDataSource. Por ltimo compruebe la opcin Habilitar paginacin de la etiqueta inteligente del FormView. A continuacin se muestra el marcado declarativo para el ItemTemplate del FormView luego que el FormView se ha enlazado al ObjectDataSource. Por defecto, cada valor de un campo no booleano esta enlazado a la propiedad Text del control web Label mientras que cada valor de un campo booleano (Discontinued) esta enlazado a la propiedad Checked de un control web CheckBox deshabilitado. Para que los botones Nuevo, Editar y Eliminar activen ciertos comportamientos del FormView cuando hacemos clic sobre ellos, es imprescindible que sus valores CommandName se establezcan en New, Update y Delete, respectivamente.
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID"

DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ... </EditItemTemplate> <InsertItemTemplate> ... </InsertItemTemplate> <ItemTemplate> ProductID: <asp:Label ID="ProductIDLabel" runat="server" Text='<%# Eval("ProductID") %>'></asp:Label><br /> ProductName: <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Bind("ProductName") %>'> </asp:Label><br /> SupplierID: <asp:Label ID="SupplierIDLabel" runat="server" Text='<%# Bind("SupplierID") %>'> </asp:Label><br /> CategoryID: <asp:Label ID="CategoryIDLabel" runat="server" Text='<%# Bind("CategoryID") %>'> </asp:Label><br /> QuantityPerUnit: <asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Bind("QuantityPerUnit") %>'> </asp:Label><br /> UnitPrice: <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Bind("UnitPrice") %>'></asp:Label><br /> UnitsInStock: <asp:Label ID="UnitsInStockLabel" runat="server" Text='<%# Bind("UnitsInStock") %>'> </asp:Label><br /> UnitsOnOrder: <asp:Label ID="UnitsOnOrderLabel" runat="server" Text='<%# Bind("UnitsOnOrder") %>'> </asp:Label><br /> ReorderLevel: <asp:Label ID="ReorderLevelLabel" runat="server"

Text='<%# Bind("ReorderLevel") %>'> </asp:Label><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>' Enabled="false" /><br /> CategoryName: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Bind("CategoryName") %>'> </asp:Label><br /> SupplierName: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Bind("SupplierName") %>'> </asp:Label><br /> <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit" Text="Edit"> </asp:LinkButton> <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete"> </asp:LinkButton> <asp:LinkButton ID="NewButton" runat="server" CausesValidation="False" CommandName="New" Text="New"> </asp:LinkButton> </ItemTemplate> </asp:FormView>

La Figura 22 muestra el ItemTemplate del FormView cuando lo visualizamos a travs de un navegador. Cada campo del producto es listado con los botones New, Edit y Delete en la parte inferior.

Figura 22. El ItemTemplate del FormView por defecto muestra cada campo del Producto, junto con los botones Nuevo, Editar y Eliminar. Al igual que los controles GridView y DetailsView, al hacer clic en el botn eliminar o en cualquier otro botn, LinkButton o ImageButton cuya propiedad CommandName este establecida en Eliminar, se produce una devolucin de datos, haciendo que se pobl el DeleteParameters del ObjectDataSource segn los valores del DataKeyNames en el FormView y se invoca el mtodo Delete() del ObjectDataSource. Cuando se hace clic en el botn editar, se produce una devolucin de datos y los datos son devueltos nuevamente al EditItemTemplate, que es el encargado de la presentacin de la interfaz de edicin. Esta interfaz incluye los controles web de edicin de datos, junto con los botones actualizar y cancelar. El EditItemTemplate generado por defecto por Visual Studio contiene un Label para cualquier campo de incremento automtico (productID), un TextBox para cada campo de valor no booleano y CheckBox para cada campo de valor booleano. Este comportamiento es muy similar a la de los BoundFields de los controles GridView y DetailsView generados automticamente. Nota: Un pequeo problema con el EditItemTemplate de los FormView generados automticamente es que hace que los controles web TextBox de los campos como CategoryName y SupplierName sean solo de lectura. Vamos a darnos cuenta de esto en breve.

Los controles TextBox en el EditItemTemplate tienen su propiedad Text enlazada con el valor de su correspondiente campo de datos usando el enlace de datos de dos vas. El enlace de dos vas denotada <%#Bind(DataField)%> lleva a cabo tanto el enlace de datos cuando enlazamos los datos a la plantilla y luego llena los parmetros del ObjectDataSource para insertar o modificar los registros. Es decir cuando el usuario hace clic en el botn editar del ItemTemplate, el mtodo Bind() devuelve el valor de los datos del campo especificado. Luego que el usuario hace sus cambios y da clic en el botn actualizar los valores de los datos devueltos que corresponden a los campos especificados con Bind() son aplicados al UpdateParameters del ObjectDataSource. Por otra parte los enlaces de datos de una va, denotados por <%Eval(DataField)%>, solo recuperan los valores de los datos del campo cuando enlazamos los datos a la plantilla y no devuelve los valores de los datos de los parmetros del origen ingresados por el usuario en una devolucin de datos. El siguiente marcado declarativo muestra el EditItemTemplate del FormView. Tenga en cuenta que el mtodo Bind() es utilizado en la sintaxis de enlace y que los controles de datos web Cancelar y actualizar tienen sus propiedades CommandName establecidas correspondientemente.
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ProductID: <asp:Label ID="ProductIDLabel1" runat="server" Text="<%# Eval("ProductID")%>"> </asp:Label><br /> ProductName: <asp:TextBox ID="ProductNameTextBox" runat="server" Text="<%# Bind("ProductName") %>"> </asp:TextBox><br /> SupplierID: <asp:TextBox ID="SupplierIDTextBox" runat="server" Text="<%# Bind("SupplierID") %>"> </asp:TextBox><br /> CategoryID: <asp:TextBox ID="CategoryIDTextBox" runat="server" Text="<%# Bind("CategoryID") %>"> </asp:TextBox><br /> QuantityPerUnit: <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text="<%# Bind("QuantityPerUnit") %>"> </asp:TextBox><br /> UnitPrice: <asp:TextBox ID="UnitPriceTextBox" runat="server" Text="<%# Bind("UnitPrice") %>"> </asp:TextBox><br /> UnitsInStock: <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text="<%# Bind("UnitsInStock") %>"> </asp:TextBox><br />

UnitsOnOrder: <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text="<%# Bind("UnitsOnOrder") %>"> </asp:TextBox><br /> ReorderLevel: <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text="<%# Bind("ReorderLevel") %>"> </asp:TextBox><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked="<%# Bind("Discontinued") %>" /><br /> CategoryName: <asp:TextBox ID="CategoryNameTextBox" runat="server" Text="<%# Bind("CategoryName") %>"> </asp:TextBox><br /> SupplierName: <asp:TextBox ID="SupplierNameTextBox" runat="server" Text="<%# Bind("SupplierName") %>"> </asp:TextBox><br /> <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update" Text="Update"> </asp:LinkButton> <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"> </asp:LinkButton> </EditItemTemplate> <InsertItemTemplate> ... </InsertItemTemplate> <ItemTemplate> ... </ItemTemplate> </asp:FormView>

Hasta este punto nuestro EditItemTemplate producir una excepcin si tratamos de utilizarlo. El problema es que los campos CategoryName y SupplierName son representados como controles web TextBox en el EditItemTemplate. Nosotros necesitamos cambiar estos cuadros de texto a etiquetas o eliminarlos totalmente. Simplemente los eliminaremos por completo del EditItemTemplate. La figura 23 muestra el FormView en un navegador luego que se ha hecho clic en el botn editar para Chai. Tenga en cuenta que los campos SupplierName y CategoryName que se muestran en el ItemTemplate ya no estn presentes, ya que acabamos de eliminarlos desde el EditItemTemplate. Al hacer clic en el botn de actualizacin del FormView se procede a travs de la misma secuencia de pasos como en los controles GridView y DetailsView.

Figura 23. Por defecto el EditItemTemplate muestra cada campo editable del producto como un TextBox o un CheckBox Cuando se hace clic en el botn Insert, el ItemTemplate del FormView produce una devolucin de datos. Sin embargo, no hay datos enlazados al FormView porque se est agregando un nuevo registro. La interfaz InsertItemTemplate incluye los controles web para agregar un nuevo registro junto con los botones insertar y cancelar. El InsertItemTemplate generado por defecto por Visual Studio contiene un TextBox para cada campo de valor no booleano y un CheckBox para cada campo booleano, de forma similar a la interface del EditItemTemplate generada automticamente. Los controles TextBox tienen su propiedad Text enlazada al valor del campo de datos correspondiente utilizando el enlace de datos de dos vas. El siguiente marcado declarativo muestra el InsertItemTemplate del FormView. Tenga en cuenta que el mtodo Bind() es usado en esta sintaxis de enlace de datos y que los controles web Button Insertar y Cancelar, tienen sus propiedades CommandName establecidas correctamente.
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ... </EditItemTemplate> <InsertItemTemplate>

ProductName: <asp:TextBox ID="ProductNameTextBox" runat="server" Text="<%# Bind("ProductName") %>"> </asp:TextBox><br /> SupplierID: <asp:TextBox ID="SupplierIDTextBox" runat="server" Text="<%# Bind("SupplierID") %>"> </asp:TextBox><br /> CategoryID: <asp:TextBox ID="CategoryIDTextBox" runat="server" Text="<%# Bind("CategoryID") %>"> </asp:TextBox><br /> QuantityPerUnit: <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text="<%# Bind("QuantityPerUnit") %>"> </asp:TextBox><br /> UnitPrice: <asp:TextBox ID="UnitPriceTextBox" runat="server" Text="<%# Bind("UnitPrice") %>"> </asp:TextBox><br /> UnitsInStock: <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text="<%# Bind("UnitsInStock") %>"> </asp:TextBox><br /> UnitsOnOrder: <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text="<%# Bind("UnitsOnOrder") %>"> </asp:TextBox><br /> ReorderLevel: <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text="<%# Bind("ReorderLevel") %>"> </asp:TextBox><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked="<%# Bind("Discontinued") %>" /><br /> CategoryName: <asp:TextBox ID="CategoryNameTextBox" runat="server" Text="<%# Bind("CategoryName") %>"> </asp:TextBox><br /> SupplierName:

<asp:TextBox ID="SupplierNameTextBox" runat="server" Text="<%# Bind("SupplierName") %>"> </asp:TextBox><br /> <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" Text="Insert"> </asp:LinkButton> <asp:LinkButton ID="InsertCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"> </asp:LinkButton> </InsertItemTemplate> <ItemTemplate> ... </ItemTemplate> </asp:FormView>

Hay

una

sutileza

con

el

InsertItemTemplate

de

los

FormView

generado

automticamente. En concreto los controles web TextBox son creados incluso para aquellos campos que son de solo lectura, como CategoryName y SupplierName. Al igual que con el EditItemTemplate tenemos que eliminar estos cuadros de texto para la InsertItemTemplate. La figura 24 muestra el FormView en un navegador cuando agregamos un nuevo producto, Acme Coffe. Tenga en cuenta que los campos SupplierName y CategoryName que se muestran en el ItemTemplate ya no estn presentes pues acabamos de removerlos. Cuando damos clic en el botn insertar, el FormView procede a travs de la misma secuencia de pasos como el control DetailsView, agregando un nuevo registro a la tabla Products. La Figura 25 muestra los detalles del producto Acme Coffe en el FormView despus de que ha sido insertado.

Figura 24. El InsertItemTemplate dicta la interfaz de insercin del FormView

Figura 25. Los datos del nuevo producto Acme Coffe son mostrados en el FormView Separando las interfaces de solo lectura, edicin e insercin en tres plantillas diferentes, el FormView permite un grado de control ms fino sobre estas interfaces que el GridView y el DetailsView.

Nota: Al igual que el DetailsView, la propiedad CurrentMode del FormView indica la interfaz que est siendo mostrada y su propiedad DefaultMode indica el modo en que el FormView es devuelto luego que una insercin o edicin se han completado. Resumen En este tutorial examinamos los fundamentos bsicos de insercin, edicin y eliminacin de datos utilizando controles GridView, DetailsView y FormView. Todos los tres controles proporcionan algn nivel predeterminado de capacidad de modificacin de datos que puede ser utilizado sin necesidad de escribir una sola lnea de cdigo en la pgina Asp.Net, gracias a los controles de datos web y al ObjectDataSource. Sin embargo, simplemente apuntar y las tcnicas de clic hacen bastante frgil la interfaz de modificacin de datos del usuario. Para proporcionar la validacin, agregar valores mediante programacin, controlar el manejo de excepciones, personalizar la interfaz de usuario y as sucesivamente, tendremos que confiar en un grupo de tcnicas que se discutirn en los prximos tutoriales.

17.

EXAMINANDO LOS EVENTOS ASOCIADOS CON LA INSERCIN,

ACTUALIZACIN Y ELIMINACIN.
En este tutorial examinaremos los eventos que ocurren antes, durante y despus de una operacin de insercin, actualizacin y eliminacin en un control de datos web de Asp.Net. Tambin veremos cmo personalizar la interfaz de edicin para actualizar solo un subconjunto de los campos de los productos. Introduccin Cuando se utilizan las funciones predefinidas de insercin, edicin y eliminacin de los controles GridView, DetailsView o FormView, se generan una serie de pasos una vez que el usuario completa finalmente el proceso de agregar un nuevo registro o de actualizar o eliminar uno existente. Como ya comentamos en el tutorial anterior, cuando se edita una fila en el control GridView, el botn editar es sustituido por los botones actualizar y cancelar y los BoundFields se convierten en TextBox. Luego el usuario final actualiza los datos y da clic en el botn actualizar, los siguientes pasos se realizan en la devolucin de datos: 1. El GridView poblar el UpdateParameters de su ObjectDataSource con los campos que identifican de forma nica los registros (a travs de la propiedad DataKeyNames) junto con los valores introducidos por el usuario. 2. El GridView invoca el mtodo Update() del ObjectDataSource, que a su vez invoca el mtodo apropiado en el objeto subyacente (ProductsDAL.UpdateProduct en nuestro tutorial anterior) 3. Los datos subyacentes, que ahora incluyen los cambios actualizados, son devueltos al GridView Durante esta secuencia de pasos, se disparan una serie de eventos, permitindonos crear controladores de eventos para agregar una lgica personalizada de ser necesario. Por ejemplo antes del paso 1, el GridView desencadena el evento RowUpdating. En este punto nosotros podemos cancelar la solicitud de actualizacin si hay algn error de validacin. Cuando se invoca el mtodo Update(), el ObjectDataSource activa el evento Updating, proporcionando una oportunidad para aadir o modificar cualquiera de los valores de UpdateParameters. Luego que el mtodo del objeto subyacente del ObjectDataSource ha terminado de ejecutarse, se produce el evento Updated del ObjectDataSource. Un controlador de eventos para el evento Updated puede examinar

los detalles relacionados con la operacin de actualizacin, tales como el numero de filas que se han afectado y si se produjo o no una excepcin. Por ltimo despus del paso 2, los GridView desencadenan el evento RowUpdated, un controlador de eventos para este evento puede examinar la informacin adicional acerca de la operacin de actualizacin que se acaba de realizar. La figura 1 describe la serie de eventos y pasos en la actualizacin de un control GridView. El modelo de eventos de la figura 1 no es aplicable nicamente a la actualizacin de un GridView. La insercin, actualizacin y eliminacin de datos de los controles GridView, DetailsView y FormView siguen la misma secuencia de eventos previos y posteriores, tanto para los datos del control web como para los del ObjectDataSource. En este tutorial examinaremos el uso estos eventos para ampliar las capacidades predefinidas de insercin, actualizacin y eliminacin de los controles de datos web de Asp.Net. Tambin veremos cmo personalizar la interfaz de edicin para actualizar solo un subconjunto de los campos de los productos.

Figura 1. Una serie de eventos previos y posteriores son disparados cuando actualizamos los datos de un GridView Paso 1: Actualizar los campos ProductName y UnitPrice de un Producto En las interfaces de edicin de los ltimos tutoriales, todos los campos de los productos que no son de solo lectura tenan que ser incluidos. Si tuviramos que remover un campo del GridView digamos QuantityPerUnit cuando actualizamos los datos, el control de datos web no establecera el valor QuantityPerUnit del

UpdateParameters del ObjectDataSource. Despus el ObjectDataSource pasara un valor de Nothing al mtodo UpdateProduct en la capa lgica de negocios (BLL) lo cual cambiaria el valor de la columna QuantityPerUnit del registro editado por un valor Null. Del mismo modo si un campo obligatorio como ProductName se quita de la interfaz de edicin, la actualizacin fallara con una excepcin de columna ProductName no permite valores nulos. La razn de este comportamiento se debe a que el ObjectDataSource fue configurado para llamar al mtodo UpdateProduct de la clase ProductsBLL, el cual espera un parmetro de entrada para cada uno campos de los campos del producto. Por lo tanto la coleccin UpdateParameters del ObjectDataSource contiene un parmetro para cada uno de los parmetros de entrada del mtodo. Si deseamos proporcionar un control de datos web que permita al usuario final, actualizar solamente un subconjunto de campos, entonces necesitamos establecer mediante programacin los valores perdidos del UpdateParameters en el controlador del evento Updating del ObjectDataSource o crear y llamar un mtodo BLL que espere solo un subconjunto de los campos. Exploraremos este ltimo enfoque. En concreto vamos a crear una pgina que muestre solamente los campos ProductName y UnitPrice en un GridView editable. Esta interface de edicin del GridView solo le permitir al usuario actualizar los dos campos que se muestran, ProductName y UnitPrice. Como esta interfaz de edicin solo proporciona un subconjunto de los campos de los productos, necesitamos crear un ObjectDataSource que utilice el mtodo UpdateProduct existente del ProductsBLL y tenga los valores de los campos de los productos perdidos establecidos mediante programacin en su controlador de evento Updating o necesitamos crear un nuevo mtodo BLL que espere solo un subconjunto de campos definidos en el GridView. Para este tutorial vamos a utilizar la ltima opcin y crearemos una recarga al mtodo UpdateProduct, que tomar solo tres parmetros de entrada: ProductName, UnitPrice y ProductID.
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, False)> _ Public Function UpdateProduct(productName As String, _ unitPrice As Nullable(Of Decimal), productID As Integer) _ As Boolean Dim products As Northwind.ProductsDataTable = _ Adapter.GetProductByProductID(productID) If products.Count = 0 Then

Return False End If Dim product As Northwind.ProductsRow = products(0) product.ProductName = productName If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If Dim rowsAffected As Integer = Adapter.Update(product) Return rowsAffected = 1 End Function

Al igual que el mtodo UpdateProduct original, esta sobrecarga inicia comprobando si existe un producto en la base de datos con el ProductID especificado. En caso contrario devuelve falso indicando que la solicitud de actualizacin de la informacin del producto especificado ha fallado. En caso contrario este actualiza los campos existente ProductName y UnitPrice del registro del producto y realiza la actualizacin llamando al mtodo Update() del TableAdapter y de paso a la instancia ProductsRow. Con esta adicin a nuestra clase ProductsBLL, estamos listos para crear la interfaz simplificada del GridView. Abra la pgina DataModificationEvents.aspx de la carpeta EditInsertDelete y agregue un GridView a la pgina. Cree un nuevo ObjectDataSource y configrelo para que utilice la clase ProductsBLL, con su mtodo Select() asignado a GetProducts y su mtodo Update() asignado a la recarga UpdateProduct que toma como parmetros de entrada solamente el ProductName, UnitPrice y ProductID. La figura 2 muestra el asistente de configuracin de origen de datos cuando asignamos el mtodo Update() del ObjectDataSource al nuevo mtodo de recarga UpdateProduct de la clase ProductsBLL.

Figura 2.Asignacin del Mtodo Update a la nueva recarga UpdateProduct Como nuestro ejemplo inicialmente solo necesita la posibilidad de editar datos, pero no inserta ni elimina registros, tommonos un momento para indicar explcitamente que los mtodos Insert() y Delete() del ObjectDataSource no deben ser asignados a ninguno de los mtodos de la clase ProductsBLL, seleccionando la opcin Ninguno en la lista desplegable de las pestaas INSERT y DELETE.

Figura 3. Seleccione la opcin ninguno para las pestaas INSERT y DELETE

Despus de completar este asistente, seleccione la opcin Habilitar edicin en la etiqueta inteligente del GridView. Con la finalizacin del asistente de configuracin de origen de datos y el enlace al GridView, Visual Studio ha creado la sintaxis declarativa para ambos controles. Vaya a la vista Fuente, para inspeccionar que el marcado declarativo del ObjectDataSource, el cual luce como se muestra a continuacin:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL" UpdateMethod="UpdateProduct"> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> </asp:ObjectDataSource>

Puesto que los mtodos Insert() y Delete() del ObjectDataSource no estn asignados, no existen las secciones InsertParameters o DeleteParameters. Por otra parte el mtodo Update() est asignado al mtodo de recarga UpdateProduct que solo acepta tres parmetros de entrada, por lo cual la seccin UpdateParameters tiene solo tres instancias Parameter. Tenga en cuenta que la propiedad OldValuesParameterFormatString se establece en Original_{0}. Esta propiedad es establecida automticamente por Visual Studio cuando se utiliza el asistente para la configuracin de origen de datos. Sin embargo, como nuestro mtodo BLL no espera que se pase el valor original del ProductID, debemos descartar totalmente la asignacin de esta propiedad en la sintaxis declarativa del ObjectDataSource. Nota: Si solamente borra el valor de la propiedad OldValuesParameterFormatString en la ventana propiedades en la vista diseo, la propiedad seguir existiendo en la sintaxis declarativa, solamente que se establece una cadena vaca. Remueva totalmente la propiedad desde la sintaxis declarativa, o desde la ventana propiedades establezca su valor en el valor predeterminado, {0}.

Mientras que el ObjectDataSource solamente tiene un UpdateParameters para el nombre, precio e ID del producto, Visual Studio ha agregado un BoundField o un CheckBoxField en el GridView para cada uno de los campos del producto.

Figura 4. El GridView contiene BoundFields y CheckBoxFields para cada campo de los productos Cuando el usuario edita un producto y hace clic en el botn Actualizar, el GridView enumera los campos que no son de solo-lectura. Luego establece el valor del parmetro correspondiente en la coleccin UpdateParameters del ObjectDataSource con el valor especificado por el usuario. Si no hay un parmetro correspondiente, el GridView agrega uno a la coleccin. Por lo tanto si nuestro GridView contiene BoundFields y CheckBoxFields para todos los campos de los productos, el ObjectDataSource terminar invocando la recarga de UpdateProduct que toma todos estos productos, a pesar que el marcado declarativo del ObjectDataSource especifica solo tres parmetros de entrada (Ver Figura 5). Del mismo modo si hay una combinacin de campos del producto que no son de solo lectura en el GridView y que no corresponden con los parmetros de entrada para la recarga UpdateProduct, se producir una excepcin cuando intentemos actualizarlo.

Figura 5. El GridView agregar parmetros a la coleccin UpdateParameters del ObjectDataSource Para asegurar que el ObjectDataSource invoque la recarga UpdateProduct de modo que toma solamente el nombre, precio y ID del producto, tenemos que restringir el GridView para que tenga nicamente los campos editables ProductName y UnitPrice. Esto se puede lograr eliminando los otros BoundFields y CheckBoxFields, o estableciendo la propiedad ReadOnly de los otros campos en True, o combinando ambas cosas. Para este tutorial simplemente vamos quitar todos los campos del GridView, excepto los BoundFields ProductName y UnitPrice, luego de realizar esto el marcado declarativo del GridView se ver as:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowEditButton="True" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> </Columns> </asp:GridView>

A pesar de que la recarga UpdateProduct espera solo tres parmetros de entrada, solo tenemos dos BoundFields en nuestro GridView. Esto se debe a que el parmetro productID es un valor de llave principal y se pasa a travs de la propiedad DataKeyNames de la fila editada.

Nuestro GridView, junto con la recarga UpdateProduct, permiten editar al usuario el nombre y precio de un producto sin perder ninguno de los otros campos del producto.

Figura 6. La interfaz permite la edicin solamente del nombre y precio del producto. Nota: Como se explico en el tutorial anterior, es de vital importancia que el View State del GridView este habilitado (comportamiento por defecto). Si establece la propiedad EnableViewState a falso, se corre el riesgo de tener usuarios concurrentes que borren o modifiquen registros sin querer. Mejorar el formato UnitPrice Aunque el ejemplo del GridView de la figura 6 trabaja correctamente, el campo UnitPrice no tiene formato alguno, originando una exhibicin de precios que carece de smbolos y cuenta con cuatro cifras decimales. Para aplicar un formato de moneda para las filas no editables, basta con establecer la propiedad DataFormatString del BoundField UnitPrice en {0:c} y su propiedad HtmlEncode en Falso.

Figura 7. Establezca las propiedades HtmlEncode y DataFormatString del UnitPrice Con este cambio, se da formato al precio de las filas no editables como monedas, sin embargo la fila editada seguir mostrando el valor sin el smbolo de moneda y con cuatro cifras decimales.

Figura 8. Las filas no editables son formateadas como valores de moneda Las instrucciones de formato especificadas en la propiedad DataFormatString se puede aplicar a la interfaz de edicin estableciendo la propiedad ApplyFormatInEditMode del

BoundField en True (el valor predeterminado es False). Tmese un momento para establecer esta propiedad en True.

Figura 9. Ajuste la propiedad ApplyFormatInEditMode del BoundField UnitPrice en True Con este cambio, el valor de UnitPrice tambin aparece en la fila que se edite en un formato de moneda.

Figura 10. El valor UnitPrice de la fila editada esta formateada como moneda Sin embargo, actualizar el cuadro de texto de un producto con el smbolo de moneda, tal como $19.00 genera un FormatException. Cuando el GridView intenta asignar los

valores

suministrados

por

el

usuario

la

coleccin

UpdateParameters

del

ObjectDataSource, este no es capaz de convertir la cadena UnitPrice $19.00 en el decimal requerido por el parmetro (Ver Figura 11). Para solucionar esto podemos crear un controlador de eventos para el evento RowUpdating del GridView y analizar el UnitPrice suministrado por el usuario como una moneda con formato Decimal. El evento RowUpdating del GridView acepta como segundo parmetro un objeto de tipo GridViewUpdateEventArgs, que incluye un diccionario NewValues como una de sus propiedades que almacena los valores suministrados por el usuario, listos para ser asignados a la coleccin UpdateParameters del ObjectDataSource. Podemos sobrescribir el valor existente UnitPrice en la coleccin NewValues con un valor decimal analizado usando el formato de moneda con las siguientes lneas de cdigo en el controlador de eventos RowUpdating:
Protected Sub GridView1_RowUpdating(sender As Object, e As GridViewUpdateEventArgs) _ Handles GridView1.RowUpdating If e.NewValues("UnitPrice") IsNot Nothing Then e.NewValues("UnitPrice") = _ Decimal.Parse(e.NewValues("UnitPrice").ToString(), _ System.Globalization.NumberStyles.Currency) End If End Sub

Si el usuario ha proporcionado un valor UnitPrice (como $19.00), este valor se sobrescribe con un valor decimal calculado por Decimal.Parse, analizando el valor como una moneda. Este analiza correctamente el decimal en el caso de cualquier smbolo de moneda, comas, puntos decimales y as sucesivamente, y utiliza la enumeracin NumberStyles en el espacio de nombres System.Globalization. La figura 11 muestra tanto el problema causado por los smbolos de moneda en el UnitPrice suministrado por el usuario, junto con la forma en que se puede utilizar el controlador de eventos RowUpdating para analizar correctamente estas entradas.

Figura 11. El valor UnitPrice de la fila editada est ahora en formato moneda Paso 2: Prohibir UnitPrices Nulls Si bien la base de datos est configurada para permitir valores nulos en la columna UnitPrice de la tabla Products, es posible que deseemos evitar que los usuarios que ingresen a esta pgina en particular, especifiquen valores UnitPrice nulos. Es decir si un usuario no ingresa un valor UnitPrice al editar una fila, en lugar de guardar los resultados en la base de datos, deseamos mostrar un mensaje informando al usuario que a travs de esta pgina los productos que sean modificados deben tener un precio determinado. El objeto GridViewUpdateEventArgs que se pasa al controlador de eventos RowUpdating del GridView contiene una propiedad Cancel, que si se pone en True, termina el proceso de actualizacin. Vamos a ampliar el controlador de eventos RowUpdating para establecer e.Cancel en True y mostrar un mensaje explicando su motivo, si el valor UnitPrice de la coleccin NewValues tiene un valor de Nothing. Comience agregando un control web Label a la pgina llamado

MustProvidePriceMessage.aspx. Este control Label se mostrara si el usuario no especifica un valor UnitPrice cuando se actualiza un producto. Establezca la propiedad Text del control Label en Debe suministrar un precio para el producto. Tambin

hemos creado una nueva clase CSS en Styles.css llamado Warning con la siguiente definicin:
.Warning { color: Red; font-style: italic; font-weight: bold; font-size: x-large; }

Finalmente establezca la propiedad CssClass del Label en Warning. Hasta este momento el diseador debe mostrar el mensaje de advertencia en rojo, negrita, cursiva y tamao de letra extra grande por encima del GridView, como se muestra en la Figura 12.

Figura 12. Un Label se ha agregado por encima del GridView De forma predeterminada, este Label debe estar oculto, por lo tanto establezca su propiedad Visible a False en el controlador de eventos Page_Load:
Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load MustProvideUnitPriceMessage.Visible = False End Sub

Si el usuario intenta modificar un producto sin especificar el UnitPrice, queremos cancelar la actualizacin y mostrar la etiqueta de advertencia. Ampliemos el controlador de eventos RowUpdating de la siguiente manera:
Protected Sub GridView1_RowUpdating(sender As Object, e As GridViewUpdateEventArgs) _ Handles GridView1.RowUpdating If e.NewValues("UnitPrice") IsNot Nothing Then e.NewValues("UnitPrice") = _ Decimal.Parse(e.NewValues("UnitPrice").ToString(), _ System.Globalization.NumberStyles.Currency) Else MustProvideUnitPriceMessage.Visible = True e.Cancel = True End If End Sub

Si un usuario intenta guardar un producto sin especificar su precio, la actualizacin se cancela y se muestra un mensaje. Si bien la base de datos (y la lgica de negocios) permite UnitPrice Nulos, esta pgina Asp.Net en particular no lo permite.

Figura 13. El usuario no puede dejar en blanco el UnitPrice Hasta el momento hemos visto como utilizar el evento RowUpdating del GridView para establecer mediante programacin los valores asignados a la coleccin UpdateParameters del ObjectDataSource, as como la forma de cancelar el proceso de

actualizacin por completo. Estos conceptos tambin se amplan a los controles DetailsView y FormView y se aplican a la insercin y eliminacin. Estas tareas tambin se pueden hacer a nivel de ObjectDataSource empleando controladores de eventos para los eventos Inserting, Updating y Deleting. Estos eventos se generan antes de invocar el mtodo asociado del objeto subyacente y ofrecen una ltima oportunidad para modificar la coleccin de parmetros de entrada o cancelar la operacin de forma directa. Los controladores de eventos para estos tres eventos pasan un objeto de tipo ObjectDataSourceMethodEventArgs que tiene dos propiedades de inters:

Cancelar, si se pone en True cancela operacin. Inputparameters, que es la coleccin InsertParameters, UpdateParameters o
DeleteParameters, dependiendo si el controlador de eventos es para el evento Inserting, Updating o Deleting.

Para mostrar el trabajo con los valores de los parmetros a nivel de ObjectDataSource, vamos a incluir un DetailsView en nuestra pgina que permita a los usuarios agregar un nuevo producto. Este DetailsView ser usado para proporcionar una interfaz que agregue rpidamente un nuevo producto a la base de datos. Para mantener una interfaz de usuario consistente con la insercin de un nuevo producto, permitiremos que el usuario introduzca solamente los valores de los campos ProductName y UnitPrice. De forma predeterminada los valores que no son ofrecidos en la interfaz de insercin del DetailsView se establecen con un valor Null en la base de datos. Sin embargo, podemos usar el evento Inserting del ObjectDataSource para ingresar valores predeterminados diferentes, como veremos en breve. Paso 3: Proporcionar una interfaz para aadir nuevos productos Arrastre un DetailsView desde el cuadro de herramientas al diseador por encima del GridView, borre las propiedades Height y Width, y enlcelo al ObjectDataSource presente. Esto agregar un BoundField o un CheckBoxField para cada uno de los campos del producto. Como queremos usar este DetailsView para agregar nuevos productos, tenemos que seleccionar la opcin Habilitar insercin en la etiqueta inteligente. Sin embargo, esta opcin no existe debido a que el mtodo Insert() del ObjectDataSource no est asignado a ningn mtodo de la clase ProductsBLL (Recuerde que definimos Ninguno al configurar el origen de datos, Vea la figura 3).

Para configurar el ObjectDataSource, seleccione el enlace Configurar el origen de datos en su etiqueta inteligente, poniendo en marcha el asistente. La primera pantalla le permite cambiar el objeto subyacente enlazado al ObjectDataSource, djelo en ProductsBLL. La siguiente pantalla muestra las asignaciones de los mtodos del ObjectDataSource a los del objeto subyacente. A pesar que expresamente se indica que los mtodos Insert() y Delete() no deben ser asignados a ningn mtodo, si vamos a las pestaas INSERT y DELETE veremos que hay una asignacin. Esto se debe a que los mtodos Addproduct y DeleteProduct del ProductsBLL utilizan el atributo DataObjectMethodAttribute para indicar que son los mtodos por defecto para Insert() y Delete() respectivamente. Por tal razn el asistente del ObjectDataSource selecciona estos cada vez que se ejecuta el asistente, a menos que haya otro valor especificado explcitamente. Deje el mtodo Insert () apuntando al mtodo AddProduct, pero una vez ms establezca en la pestaa DELETE en Ninguno.

Figura 14. Establezca el mtodo AddProduct de la lista desplegable de la pestaa INSERT

Figura 15. Establezca en Ninguno la lista desplegable de la pestaa DELETE Despus de realizar estos cambios, la sintaxis declarativa del ObjectDataSource se amplia para incluir la coleccin InputParameters como se muestra a continuacin:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProducts" TypeName="ProductsBLL" UpdateMethod="UpdateProduct" OnUpdating="ObjectDataSource1_Updating" InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}"> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> </InsertParameters> </asp:ObjectDataSource>

Al

regresar

al

asistente,

este

configura

de

nuevo

la

propiedad

OldValuesParameterFormatString. Tmese un momento para limpiar esta propiedad establecindola al valor por defecto ({0}) o eliminndola por completo de la sintaxis declarativa. Como el ObjectDataSource proporciona capacidades de insercin, la etiqueta inteligente del DetailsView ahora debe incluir una casilla de verificacin de Habilitar Insercin, regrese al diseador y seleccione esta opcin. Luego remueva los BoundFields del DetailsView de tal forma que solo queden dos BoundFields ProductName y UnitPrice y el CommandField. Hasta este momento el marcado declarativo del DetailsView lucir as:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Fields> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:CommandField ShowInsertButton="True" /> </Fields> </asp:DetailsView>

La figura 16 muestra esta pgina vista a travs de un navegador. Como puede ver el DetailsView muestra el nombre y el precio del primer producto (Chai). Sin embargo, lo que queremos es una interfaz de edicin que proporcione al usuario un medio para que pueda agregar un nuevo producto a la base de datos.

Figura 16. Actualmente el DetailsView es presentado en modo de solo lectura Con el fin de mostrar el DetailsView en modo de insercin, establezca la propiedad DefaultMode en Inserting. Esto hace que la primera vez que visite el DetailsView este en modo de insercin, y se mantenga as despus de agregar un nuevo registro. En la figura 17, vemos como el DetailsView proporciona una interfaz rpida de ingreso de nuevos productos.

Figura 17. El DetailsView proporciona una interfaz rpida de ingreso de nuevos productos Cuando el usuario introduce un nombre de producto y el precio (como con Acme Coffe y 1.99, como en la Figura 17) y hace clic en Insertar , se produce una devolucin de datos y empieza el flujo de trabajo de insercin, que culmina en un registro nuevo agregado a la base de datos. El DetailsView mantiene su interfaz de insercin y el

control GridView es automticamente es enlazado de nuevo a su origen de datos con el fin de incluir un nuevo producto, como se muestra en la figura 18.

Figura 18. El producto Agua Acme se ha agregado a la base de datos Aunque el GridView de la figura 18 no lo muestra, los campos de los productos que carecen de interfaz en el DetailsView, CategoryID, SupplierID, QuantityPerUnit y as sucesivamente, se les asignan valores Nulos en la base de datos. Usted puede ver esto por medio de los siguientes pasos: 1. 2. 3. 4. Vaya al explorador de soluciones en Visual Studio Ampli el nodo Northwind.mdf Haga clic en el nodo de la tabla Products de la base de datos Seleccione Mostrar datos

Esto mostrar una lista de todos los registros de la tabla Products. En la Figura 19 vemos como todas las columnas de los productos nuevos, a excepcin del ProductID, ProductName y UnitPrice tienen valores Nulos.

Figura 19. Los campos de los productos no mostrados en el DetailsView se asignan como Nulos Es posible que desee proporcionar por defecto un valor que no sea Nulo para uno o ms valores de las columnas, ya sea porque Nulo no es la mejor opcin por defecto o porque la columna de la base de datos en s, no permite valores Nulos. Para realizar esto podemos establecer mediante programacin los valores de los parmetros de la coleccin InputParameters del DetailsView. Esta asignacin se puede hacer ya sea en un controlador de eventos para el evento ItemInserting del DetailsView o en el evento Inserting del ObjectDataSource. Como ya hemos visto y usado los eventos previos y posteriores a nivel de control de datos web, en este momento exploraremos los eventos del ObjectDataSource. Paso 4: Asignacin de valores de los parmetros CategoryID y SupplierID Para este tutorial vamos a imaginarnos que para nuestra aplicacin cuando agregamos un nuevo producto por medio de esta interfaz, debemos asignar el valor de CategoryID y SupplierID en 1. Como se menciono anteriormente, el ObjectDataSource tiene un par de eventos previos y posteriores que son generados durante el proceso de modificacin de datos. Cuando se invoca el mtodo Insert(), el ObjectDataSource primero genera su evento Inserting, luego llama al mtodo que ha sido asignado a su mtodo Insert() y finalmente genera el evento Inserted. El controlador de evento Inserting nos da una ltima oportunidad para modificar los parmetros de entrada o cancelar la operacin de forma directa. Nota: En una aplicacin del mundo real lo ms probable es que quieras que el usuario especifique la categora y el proveedor, o que escoja el valor para ellos basndose en

algn criterio o lgica de negocio (en vez de establecer ciegamente la seleccin de un ID de 1). En cualquier caso, el ejemplo muestra como establecer mediante programacin el valor de un parmetro de entrada desde el evento previo del ObjectDataSource. Tmese un momento para crear un controlador de eventos para el evento Inserting del ObjectDataSource. Tenga en cuenta que el segundo parmetro de entrada del controlador del evento es un objeto de tipo ObjectDataSourceMethodEventsArgs, que tiene una propiedad para acceder a la coleccin de parmetros (InputParameters) y una propiedad para cancelar la operacin (Cancel).
Protected Sub ObjectDataSource1_Inserting _ (sender As Object, e As ObjectDataSourceMethodEventArgs) _ Handles ObjectDataSource1.Inserting End Sub

Hasta

el

momento,

la

propiedad

InputParameters

contiene

la

coleccin

InsertParameters del ObjectDataSource con los valores asignados desde el DetailsView. Para cambiar el valor de uno de estos parmetros, basta con utilizar: e.InputParameters (paramName)=Value. Por lo tanto para establecer los valores de CategoryID y SupplierID en 1, modificamos el controlador de eventos para que tenga el siguiente aspecto:
Protected Sub ObjectDataSource1_Inserting _ (sender As Object, e As ObjectDataSourceMethodEventArgs) _ Handles ObjectDataSource1.Inserting e.InputParameters("CategoryID") = 1 e.InputParameters("SupplierID") = 1 End Sub

Esta vez, cuando agregamos un nuevo producto (como soda Acme) las columnas CategoryID y SupplierID para el nuevo producto se establecen en 1(Ver Figura 20).

Figura 20. Los nuevos productos ahora tienen establecidos los valores CategoryID y SupplierID en 1 Resumen Durante el proceso de edicin, insercin y eliminacin, tanto el control de datos web como el ObjectDataSource prosiguen a travs de una serie de eventos previos y posteriores. En este tutorial examinamos los eventos previos y vimos como utilizarlos para personalizar los parmetros de entrada o cancelar la operacin de actualizacin de un conjunto de datos, tanto en los controles de datos web como en el ObjectDataSource. En el siguiente tutorial veremos cmo crear y utilizar controladores de eventos posteriores.

18.

MANEJO DE EXCEPCIONES EN UNA PGINA ASP.NET A NIVEL DE DAL

Y BLL
En este tutorial veremos cmo mostrar un mensaje de informacin de error amistoso, producido por una excepcin durante una operacin de insercin, actualizacin o eliminacin de datos en los controles web Asp.Net Introduccin Trabajar con datos en una aplicacin web Asp.Net, empleando una arquitectura de aplicacin por niveles, abarca los siguientes tres pasos generales: 1. Determinar que mtodo de la capa lgica de negocios debe ser invocado y los valores de los parmetros que se deben pasar. Los valores de los parmetros pueden ser codificados, asignados mediante programacin o entradas ingresadas por el usuario. 2. Invocar el mtodo 3. Procesar los resultados. Cuando llamamos un mtodo BLL que devuelve datos, estos deben enlazar los datos a un control de datos web. Para los mtodos BLL que modifican datos, estos pueden desarrollar algunas acciones basadas en algn valor devuelto o manejar cualquier excepcin generada en el paso 2. Como vimos en el tutorial anterior, tanto el ObjectDataSource y los controles de datos web proporcionan una extensin para los pasos 1 y 3. Por ejemplo, el GridView dispara su evento RowUpdating antes de asignar los valores de sus campos a la coleccin UpdateParameters del ObjectDataSource, su evento RowUpdated se produce despus que el ObjectDataSource ha completado la operacin. Ya hemos visto los eventos generados en el paso 1 y hemos visto como utilizarlos para personalizar los parmetros de entrada o cancelar la operacin. En este tutorial vamos a dirigir nuestra atencin a los eventos generados despus que se completa la operacin. Con estos controladores de eventos posteriores, podemos entre otras cosas determinar si se produjo una excepcin durante la operacin y as manejarlo adecuadamente, mostrando un mensaje amistoso que informe el error en la pantalla en lugar de mostrar la pagina de excepcin estndar de Asp.Net.

Para realizar el trabajo con eventos posteriores, crearemos una pgina que muestre los productos en un GridView editable. Cuando actualizamos un producto, si se genera una excepcin nuestra pgina ASP.Net mostrar un mensaje corto encima del GridView, explicando que se ha producido un problema. Paso 1: Creacin de un GridView de Productos editable En el tutorial anterior hemos creado un GridView editable con tan solo dos campos, ProductName y UnitPrice. Esto requiri la creacin de una recarga para el mtodo UpdateProduct de la clase ProductsBLL, que solo aceptara tres parmetros de entrada (ProductName, UnitPrice y ID) en lugar de tener un parmetro para cada campo. Para este tutorial vamos a practicar esta tcnica de nuevo, crearemos un GridView que muestre el nombre del producto, la cantidad por unidad, el precio unitario y las unidades de existencia; pero solo permitir que sean editados el nombre, precio unitario y unidades de existencia del producto. Para acostumbrarse a este escenario, necesitaremos otra recarga para el mtodo UpdateProduct, que acepte cuatro parmetros: el nombre del producto, precio, unidades de existencia e ID. Agreguemos el siguiente mtodo a la clase ProductsBLL:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, UpdateProduct _ (ByVal productName As String, ByVal unitPrice As Nullable(Of Decimal), _ByVal unitsInStock As Nullable(Of Short), ByVal productID As Integer) As Boolean Dim products As Northwind.ProductsDataTable = _ Adapter.GetProductByProductID(productID) If products.Count = 0 Then Return False End If Dim product As Northwind.ProductsRow = products(0) product.ProductName = productName If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() True)> _Public Function

Else product.UnitsInStock = unitsInStock.Value End If Dim rowsAffected As Integer = Adapter.Update(product) Return rowsAffected = 1 End Function

Con este mtodo completo, estamos listos para crear la pgina ASP.Net que permita la edicin de estos cuatro campos particulares del producto. Abra la pgina ErrorHandling.aspx de la carpeta EditInsertDelete y agregue un GridView a la pgina a travs del diseador. Enlace el GridView a un nuevo ObjectDataSource, asignando el mtodo Select() al mtodo GetProducts() de la clase ProductsBLL y el mtodo Update() a la recarga UpdateProduct que acabamos de crear.

Figura 1. Utilice la recarga del mtodo UpdateProduct que acepta cuatro parmetros de entrada Esto creara un ObjectDataSource con una coleccin UpdateParameters con cuatro parmetros y un GridView con un campo para cada uno de los campos del producto. El marcado declarativo del ObjectDataSource asigna a la propiedad OldValuesParameterFormatString el valor de Original_{0}, lo que provocara una excepcin ya que nuestra clase BLL no espera que sea pasado un parmetro de entrada llamado Original_ProductID. No olvide borrar por completo esta configuracin de la sintaxis declarativa (o establecerla en el valor predeterminado {0}).

Luego modifique el GridView para incluir solo los BoundFields ProductName, QuantityPerUnit, UnitPrice y UnitsInStock. Tambin sintase libre de aplicar cualquier formato a nivel de campo que considere necesario aplicar (tal como el cambio de las propiedades HeaderText) En el tutorial anterior vimos como dar formato de moneda al BoundField UnitPrice, tanto en el modo de solo lectura, como en el modo de edicin. Haremos lo mismo aqu. Recuerde que este ajuste requiere que la propiedad DataFormatString del BoundField, sea {0:c}, su propiedad HtmlEncode sea False y su ApplyFormatInEditMode este en True, como se muestra en la Figura 2.

Figura 2. Configure el BoundField UnitPrice para mostrarlo como moneda Aplicar el formato de moneda al UnitPrice en la interfaz de edicin, requiere la creacin de un controlador de eventos para el evento RowUpdating del GridView, que analice la cadena con formato de moneda en un valor decimal. Recordemos que el control de eventos RowUpdating de los ltimos tutoriales tambin realiza la inspeccin para garantizar que el usuario ha suministrado un valor para UnitPrice. Sin embargo para este tutorial permitiremos que el usuario omita el precio.
Protected Sub GridView1_RowUpdating(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) _ Handles GridView1.RowUpdating If e.NewValues("UnitPrice") IsNot Nothing Then e.NewValues("UnitPrice") = _

Decimal.Parse(e.NewValues("UnitPrice").ToString(), _ System.Globalization.NumberStyles.Currency) End If

Nuestro GridView incluye un BoundField QuantityPerUnit, pero este BoundField es para fines de presentacin y no debe ser modificado por el usuario. Para realizar esto, basta con establecer la propiedad ReadOnly del BoundField en True.

Figura 3. Haga que el BoundField QuantityPerUnit sea de solo lectura Por ltimo seleccione la casilla Habilitar Edicin desde la etiqueta inteligente del GridView. Una vez completados estos pasos, el diseador de la pgina ErrorHandling.aspx debe ser similar a la figura 4.

Figura 4. Remover todos los Boundfields innecesarios y seleccionar la opcin Habilitar Edicin Hasta el momento tenemos una lista de los campos ProductName, QuantityPerUnit, UnitPrice y UnitsInStock de todos los productos, sin embargo solo los campos ProductName, UnitPrice y UnitsInStock pueden ser editados.

Figura 5. Ahora los usuarios pueden editar fcilmente los valores de los campos ProductName, Price y UnitsInStock Paso 2: Manejar con gracia las excepciones a nivel de DAL Aunque nuestro GridView editable funciona de maravilla cuando los usuarios ingresan valores legales para el nombre, precio y unidades de existencia del producto editado; ingresar valores ilegales generara una excepcin. Por ejemplo omitiendo el valor ProductName hace que se genere una NoNullAllowedException ya que la propiedad

ProductName en la clase ProductsRow tiene su propiedad AllowDBNull establecida en False, si la base de datos est apagada, una SqlException ser generada por el TableAdapter cuando intente conectarse a la base de datos. Si no tomamos ninguna accin, estas excepciones irn desde la capa de acceso a datos a la capa lgica de negocios, luego a la pgina ASP.Net y finalmente al tiempo de ejecucin de ASP.Net. Dependiendo de cmo se configura la aplicacin web y si usted est visitando la aplicacin desde un localhost, una excepcin no controlada puede resultar en una pgina de error de servidor genrico, un informe detallado del error o una pgina web amigable al usuario. Vea Manejo y control de errores de aplicaciones Asp.Net y el elemento CustomErrors para obtener ms informacin relacionada con como el tiempo de ejecucin Asp.Net responde a una excepcin no capturada. La Figura 6 muestra la pantalla encontrada, cuando intentamos actualizar un producto sin especificar el valor del ProductName. Este es el informe de errores detallado que aparece por defecto al ingresar a travs del Localhost.

Figura 6. Omitir los nombres de los productos mostrara Detalles de la excepcin Aunque los detalles de la excepcin son tiles cuando probamos una aplicacin, presentar al usuario final una pantalla con la excepcin no es lo ideal. El usuario final muy probablemente no sepa que es un NoNullAllowedException o por que se produjo. Un mejor enfoque es presentar al usuario un mensaje ms amigable explicando que hubo problemas tratando de actualizar el producto.

Si se produce una excepcin cuando se realiza una operacin, los eventos posteriores tanto en el ObjectDataSource como en el control de datos web proporcionan un medio para detectar y cancelar la excepcin generada en el tiempo de ejecucin de ASP.Net. Para nuestro ejemplo vamos a crear un controlador de eventos del evento RowUpdated del GridView que determine si se ha generado una excepcin y de ser as muestra los detalles de la excepcin en un control web Label. Comience agregando un Label a la pgina Asp.Net estableciendo su propiedad ID en ExceptionDetails y limpie su propiedad Text. Con el fin de llamar la atencin del usuario a este mensaje, establezca su propiedad CssClass en Warning, que es una clase CSS que hemos aadido al archivo Styles.css en el tutorial anterior. Recordemos que esta clase CSS hace que el texto del Label se muestre en color rojo, negrita, cursiva y extra grande.

Figura 7. Aada un control web Label a la pgina Como deseamos que este control web Label sea visible nicamente, inmediatamente despus que se haya producido la excepcin, establezca su propiedad Visible en false en el controlador de eventos Page_Load:
Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load ExceptionDetails.Visible = False End Sub

Con este cdigo, en la primera visita a la pagina y en las posteriores devoluciones de datos, el control ExceptionDetails tendr su propiedad Visible establecida en False. Las excepciones a nivel de DAL y BLL, que detectamos con el controlador de eventos RowUpdated del GridView, establecen la propiedad Visible del control ExceptionDetails en True. Como el controlador de eventos web se produce despus del controlador de evento Page_Load en el ciclo de vida de la pgina, el Label se mostrar. Sin embargo, en la siguiente devolucin de datos, el controlador de eventos Page-Load revertir la propiedad Visible a False, ocultndolo de la vista de nuevo. Nota: De forma alternativa, podramos eliminar la necesidad de establecer la propiedad Visible del control ExceptionDetails en Page_Load asignando su propiedad Visible en false en la sintaxis declarativa y desactivando su View State (Ajuste la propiedad View State a False). Veremos este enfoque en un futuro tutorial. Con el control Label agregado, nuestro siguiente paso es crear el controlador del evento RowUpdated del GridView. Seleccione el control GridView en el diseo, vaya a la ventana propiedades y haga clic sobre el icono del rayo, que mostrar los eventos del GridView. All debera haber una entrada para el evento RowUpdating del GridView, como hemos creado un controlador de eventos para este evento, anteriormente en este tutorial. Ahora crearemos un controlador de eventos para el evento RowUpdated. Nota: Tambin puede crear el controlador de eventos por medio de la lista ubicada en la parte superior del archivo de cdigo de la clase subyacente. Seleccione el control GridView de la lista desplegable de la izquierda y el evento RowUpdated de la lista de la derecha. La creacin de este controlador de eventos agrega el siguiente cdigo al cdigo de la clase subyacente de la pgina Asp.Net:
Protected Sub GridView1_RowUpdated(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _ Handles GridView1.RowUpdated End Sub

Figura 8. Crear un controlador de eventos para el evento RowUpdated del GridView El segundo parmetro de entrada de este controlador de eventos es un objeto de tipo GridViewUpdatedEventArgs, que tiene tres propiedades de inters para el manejo de excepciones: Exception, una referencia a la excepcin que se ha producido, si no se ha generado ninguna excepcin, esta propiedad tiene el valor de Null. ExceptionHandled, un valor booleano que indica si la excepcin fue controlada en el controlador de eventos RowUpdated, si es false (por defecto) la excepcin se emite nuevamente, filtrndola hasta el tiempo de ejecucin de ASP.Net. KeepInEditMode, Si se pone en True la fila editada del GridView permanece en modo de edicin, si es False (por defecto), la fila del GridView vuelve a su modo de solo lectura. Nuestro cdigo a continuacin, debe comprobar si Exception no es Null, lo que significa que en este caso se ha generado una excepcin en el desarrollo de la operacin. Si este es el caso, queremos: Mostrar un mensaje amigable en el Label ExceptionDetails

Indicar que la excepcin ha sido controlada Mantener la fila en modo de edicin

El siguiente cdigo realiza estos objetivos:


Protected Sub GridView1_RowUpdated(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _ Handles GridView1.RowUpdated If e.Exception IsNot Nothing Then ExceptionDetails.Visible = True ExceptionDetails.Text = "There was a problem updating the product. " If e.Exception.InnerException IsNot Nothing Then Dim inner As Exception = e.Exception.InnerException If TypeOf inner Is System.Data.Common.DbException Then ExceptionDetails.Text &= _ "Our database is currently experiencing problems." & _ "Please try again later." ElseIf TypeOf inner _ Is System.Data.NoNullAllowedException Then ExceptionDetails.Text += _ "There are one or more required fields that are missing." ElseIf TypeOf inner Is ArgumentException Then Dim paramName As String = CType(inner, ArgumentException).ParamName ExceptionDetails.Text &= _ String.Concat("The ", paramName, " value is illegal.") ElseIf TypeOf inner Is ApplicationException Then ExceptionDetails.Text += inner.Message End If End If e.ExceptionHandled = True e.KeepInEditMode = True End If End Sub

Este controlador de eventos inicia comprobando si e.Exception=Null Si no es as la propiedad Visible del Label ExceptionDetails se establece en True y su propiedad Text en Hubo un problema al actualizar el producto. Los detalles de la excepcin actualmente generada, se encuentran en la propiedad InnerException del objeto e.Exception. Esta excepcin interna es examinada, si es de un tipo particular, se agrega

un mensaje de ayuda adicional a la propiedad Text del Label ExceptionDetails. Por ltimo las propiedades ExceptionHandled y KeepInEditMode se establecen en True. La figura 9 muestra una captura de pantalla de esta pgina cuando se omite el nombre de un producto; La figura 10 muestra los resultados al introducir un valor ilegal en UnitPrice (-50).

Figura 9. El BoundField ProductName debe contener un valor

Figura 10. No se permiten valores negativos de UnitPrice

Al establecer la propiedad e.ExceptionHandled en True, el controlador de eventos RowUpdated indica que se ha manejado la excepcin. Por lo tanto la excepcin no se propagar hasta el tiempo de ejecucin de Asp.Net. Nota: Las figuras 9 y 10 muestran una forma graciosa de controlar las excepciones planteadas por las entradas no validas de un usuario. Sin embargo lo ideal, en primer lugar es que una entrada no valida nunca llega a la capa lgica de negocios, la pgina ASP.Net debe asegurarse que las entradas del usuario sean validas antes de invocar el mtodo UpdateProduct de la clase ProductsBLL. En el siguiente tutorial veremos cmo agregar controles de validacin a las interfaces de edicin e insercin que garanticen que los datos enviados a la capa lgica de negocios son conformes a la reglas del negocio. Los controles de validacin no solo impiden la invocacin del mtodo UpdateProduct hasta que el usuario proporcione los datos validos, sino que tambin proporciona al usuario una experiencia ms informativa para la identificacin de los problemas de entrada de datos. Paso 3: Manejo con gracia de las excepciones a nivel BL Cuando insertamos, actualizamos o eliminamos datos, la capa de acceso a datos puede producir una excepcin de error en los datos relacionados. La base de datos puede estar desconectada, una columna requerida de una tabla de la base de datos podra no tener el valor especificado o una restriccin a nivel de tabla podra haber sido incumplida. Adems de las restricciones estrictamente relacionadas con los datos, la capa lgica de negocios puede utilizar las excepciones para indicar que las reglas de negocio no han sido cumplidas. En el tutorial de creacin de una capa lgica de negocios, hemos aadido una comprobacin de reglas de negocio a la recarga original UpdateProduct. En concreto si el usuario marca como descontinuado un producto, se requiere que el producto no sea el nico proporcionado por el proveedor. Si esta condicin no se ha cumplido, se genera una ApplicationException. Para la recarga UpdateProduct creada en este tutorial, vamos a agregar una regla de negocio que prohbe que el campo UnitPrice se establezca en un valor que sea mayor al doble del valor original de UnitPrice. Para ello modificamos la recarga UpdateProduct para que realice esta inspeccin y emita una ApplicationException si la norma no se cumple. El mtodo actualizado es el siguiente:
<System.ComponentModel.DataObjectMethodAttribute _

(System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateProduct(ByVal productName As String, _ ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _ ByVal productID As Integer) As Boolean Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID) If products.Count = 0 Then Return False End If Dim product As Northwind.ProductsRow = products(0) If unitPrice.HasValue AndAlso Not product.IsUnitPriceNull() Then If unitPrice > product.UnitPrice * 2 Then Throw New ApplicationException( _ "When updating a product price," & _ " the new price cannot exceed twice the original price.") End If End If product.ProductName = productName If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If Dim rowsAffected As Integer = Adapter.Update(product) Return rowsAffected = 1 End Function

Con este cambio, cualquier actualizacin de precios de un producto que sea mayor que el doble del valor actual del producto, ocasiona que se origine una ApplicationException. Al igual que la excepcin generada desde la DAL, la ApplicationException planteada en la BLL puede ser detectada y manejada en el controlador de evento RowUpdated del GridView. De hecho, el cdigo del controlador de evento RowUpdated como est escrito, detecta correctamente esta excepcin y muestra el valor de la propiedad Message del ApplicationExceptions. La figura 11

muestra una captura de pantalla cuando un usuario intenta actualizar el precio de Chai a $50.00, que es ms del doble que su precio actual de $19.95.

Figura 11. Las reglas de negocio no permiten un aumento de ms del doble del precio actual del producto Nota: Lo ideal sera que nuestras reglas de la lgica de negocios sean rediseadas en las sobrecargas del mtodo UpdateProduct y en el mtodo comn. Esto se deja de tarea para el lector. Resumen Durante las operaciones de insercin, actualizacin y eliminacin, tanto el control de datos web como el ObjectDataSource invocan a los eventos previos y posteriores que emite la operacin actual. Como vimos en este y en el anterior tutorial, cuando se trabaja con un GridView editable, el GridView genera el evento RowUpdating, seguido por el evento Updating del ObjectDataSource, momento en el cual el command actualiza el objeto subyacente del ObjectDataSource. Despus que la operacin se ha terminado, el ObjectDataSource desencadena el evento Updated, seguido por el evento RowUpdated del GridView. Podemos crear controladores para los eventos previos con el fin de personalizar los parmetros de entrada, o para los eventos posteriores con el fin de inspeccionar y dar respuesta a los resultados de la operacin. Los controladores de eventos posteriores, son ms comnmente utilizados para detectar si se produjo una excepcin durante la

operacin. En el frente de la excepcin, estos controladores de eventos posteriores opcionalmente pueden controlar la excepcin por su propia cuenta. En este tutorial vimos como manejar esa excepcin al mostrar un mensaje de error amigable. En el siguiente tutorial veremos cmo reducir la posibilidad de excepciones derivadas de los formatos de los datos introducidos (como el ingreso de un Unitprice negativo). En concreto veremos cmo agregar controles de validacin para las interfaces de edicin e insercin.

19.

AGREGAR CONTROLES DE VALIDACION PARA LAS INTERFACES DE

EDICION E INSERCIN
En este tutorial examinaremos lo fcil que es agregar controles de validacin al InsertItemTemplate y EditItemTemplate de un control de datos web, para proporcionar una interfaz de usuario infalible. Introduccin Los controles GridView y DetailsView que hemos explorado en los ltimos tres tutoriales han estado compuestos de BoundFields y CheckBoxFields (los tipos de campos agregados automticamente por Visual Studio cuando enlazamos un GridView o un DetailsView a un control de origen de datos por medio de la etiqueta inteligente). Cuando editamos una fila de un GridView o de un DetailsView los BoundFields que no son de solo lectura son convertidos en TextBox, desde los cuales el usuario puede modificar los datos existentes. Del mismo modo al insertar un nuevo registro en un control DetailsView los BoundFields cuya propiedad InsertVisible se establece en True (por defecto) son presentados como TextBox vacios, en los cuales el usuario puede proporcionar los valores de los campos del nuevo registro. Del mismo modo los CheckBoxFields que estn deshabilitados en la interface estndar de solo-lectura son convertidos en CheckBoxs habilitados en las interfaces de insercin y edicin existentes. Mientras que las interfaces de edicin e insercin predeterminadas para los BoundFields y los CheckBoxFields pueden ser tiles, la interfaz no tiene ningn tipo de validacin. Si un usuario comete un error de ingreso de datos, tal como la omisin del campo ProductName o ingresa un valor no valido para UnitsInStock (por ejemplo -50) se emite una excepcin desde la profundidad de la arquitectura de la aplicacin. Si bien esta excepcin puede ser manejado con gracia como se mostro en el tutorial anterior; idealmente las interfaces de usuario de edicin e insercin debera incluir controles de validacin para en primer lugar evitar el ingreso de datos no vlidos. Con el fin de proporcionar una interfaz de edicin e insercin personalizada, debemos sustituir el BoundField o CheckBoxField por un TemplateField. Los TemplateFields fueron el tema de discusin en los tutoriales Uso de TemplateFields en el control GridView y Uso de TemplateFields en el control DetailsView, pueden consistir de varias plantillas definiendo interfaces separadas para diferentes estados de fila. El

ItemTemplate del TemplateField se utiliza cuando presentamos campos o filas de solo lectura en los controles DetailsView o GridView, mientras que el EditItemTemplate e InsertItemTemplate indican a la interface usar el modo de edicin e insercin, respectivamente. En este tutorial vamos a ver lo fcil que es agregar controles de validacin al EditItemTemplate e InsertItemTemplate del TemplateField para proporcionar una interfaz de usuario infalible. En concreto este tutorial toma el ejemplo creado en el tutorial Examen de eventos asociados con la actualizacin, insercin y eliminacin y amplia las interfaces de edicin e insercin para incluir la validacin adecuada. Paso 1: Replicar el ejemplo de Examinar los eventos asociados a insertar, actualizar y editar En el tutorial Examen de eventos asociados con actualizar, insertar y eliminar creamos una pgina que muestra los nombres y precios de los productos en un GridView editable. Adems la pgina incluye un DetailsView cuya propiedad DefaultMode se estableci en Insert, con lo que siempre se presenta en modo de insercin. A partir de este DetailsView, el usuario puede ingresar el nombre y el precio para un nuevo producto, hacer clic en Insertar y agregarlo al sistema (Figura 1) Nuestro objetivo para este tutorial es ampliar el GridView y el DetailsView para proporcionar controles de validacin. En particular nuestra lgica de validacin: Exige que se proporcione el nombre del producto cuando va a ser ingresado o editado. Exige que se proporcione el precio, al insertar un registro, cuando editamos un registro tambin se exige que se proporcione el precio, pero utilizara la lgica de programacin del controlador de eventos RowUpdating, que se presento con anterioridad. Asegura que el valor introducido en el precio, es un formato de moneda valido.

Figura 1. El ejemplo anterior permite al usuario aadir nuevos productos y editar los ya existentes Antes que podamos observar la ampliacin del ejemplo anterior para incluir la validacin, primero tenemos que replicar el ejemplo de DataModificationEvents.aspx a UIValidation.aspx. Para lograr esto tendremos que copiar tanto el marcado declarativo de DataModificationEvents.aspx y su cdigo fuente. Primero copiamos el marcado declarativo realizando los siguientes pasos: 1. Abra la pgina DataModificationEvents. aspx en Visual Studio 2. Vaya al marcado declarativo de la pgina (haga clic en el botn fuente en la parte inferior de la pgina) 3. Copie el texto dentro de <asp:content> y </asp:content> (lneas 3 a 44), como se muestra en la figura 2.

Figura 2. Copiar el texto dentro de <asp:Content> 1. Abra la pgina UIValidation.aspx 2. Vaya al marcado declarativo de la pgina 3. Pegue el texto en el control <asp:Content> Para copiar el cdigo fuente, abra la pgina DataModification.aspx.vb y copie solo el texto en la clase EditInsertDelete_DataModificationEvents. Copie los tres controladores de eventos (Page_Load, GridView1, RowUpdating y ObjectDataSource1_Inserting) pero no copie la declaracin de la clase. Pegue el texto copiado en la clase EditInsertDelete_UIValidation.aspx en UIValidation.aspx.vb. Despus de mover el contenido y el cdigo desde DataModificationEvents.aspx a UIValidation.aspx, tmese un momento para ver su progreso en el navegador. Usted debe ver la misma salida y experimentar la misma funcionalidad en cada una de estas dos pginas (refirase a la figura 1 para una captura de pantalla de DataModificationEvents.aspx en accin). Paso 2: Convertir los BoundFields en TemplateField Para agregar controles de validacin a las interfaces de edicin e insercin, los BoundFields utilizados por los controles GridView y DetailsView necesitan ser convertidos en TemplateFields. Para realizar esto haga clic en los vnculos editar columnas y editar campos en las etiquetas inteligentes de los controles GridView y DetailsView respectivamente. All seleccione cada uno de los BoundFields y haga clic en el enlace Convertir este campo en TemplateField

Figura 2. Convertir cada BoundFields del GridView y el DetailsView en TemplateField Convertir un BoundField en un TemplateField por medio de los cuadros de dialogo Campos y genera un TemplateField que exhibe las mismas interfaces de edicin, insercin, y solo-lectura como las del propio BoundField. El siguiente marcado muestra la sintaxis declarativa para el campo ProductName en el DetailsView luego que se ha convertido en un TemplateField:
<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> </EditItemTemplate> <InsertItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> </InsertItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

Tenga en cuenta que este TemplateField tiene tres plantillas creadas automticamente, ItemTemplate, EditItemTemplate e InsertItemTemplate. El ItemTemplate muestra un solo valor de campo de datos (ProductName) usando un control web Label, mientras que el EditItemTemplate y el InsertItemTemplate presentan el valor del campo de datos en un control web TextBox que asociado el campo de datos con la propiedad Text del TextBox usando el enlace de datos de dos vas. Puesto que en esta pgina solo utiliza el DetailsView para insertar, puede quitar el ItemTemplate y el EditItemTemplate de estos dos TemplateFields, aunque no hay nada malo en dejarlos. Dado que el GridView no soporta las funciones de insercin predeterminadas del DetailsView, convertir el campo ProductName del GridView en un TemplateField resulta en un ItemTemplate y un EditItemTemplate:
<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

Al hacer clic en Convertir este campo en un TemplateField, Visual Studio crea un TemplateField cuyas plantillas imitan la interfaz de usuario de los BoundFields convertidos. Usted puede comprobar esto visitando esta pgina desde un navegador. Encontrar que la apariencia y el comportamiento de los TemplateFields son idnticos a la experiencia de cuando se utilizamos en su lugar BoundFields. Nota: Sintase libre de personalizar la interfaz de edicin en las plantillas si es necesario. Por ejemplo puede querer tener el TextBox en el TemplateField UnitPrice presentado como un cuadro de texto ms pequeo que el cuadro de texto ProductName. Para realizar esto podemos configurar la propiedad Columns del TextBox a un valor adecuado, o proporcionar un ancho absoluto a travs de la propiedad Width. En el siguiente tutorial veremos cmo personalizar completamente la interfaz de edicin, sustituyendo el TextBox con un control de datos web alternativo.

Paso 3: Agregar controles de validacin al EditItemTemplate del GridView Cuando construimos formularios de ingreso de datos, es importante que los usuarios puedan acceder a todos los campos y que las entradas proporcionadas sean validas, y los valores tengan el formato apropiado. Para ayudar a asegurar que las entradas de los usuarios son validas, Asp.Net proporciona cinco controles de validacin preinstalados, que estn diseados para ser usados para validar el valor de la entrada de un control individual:

RequiredFieldValidator, asegura que el valor sea proporcionado CompareValidator, valida el valor contra el valor de otro control web o un valor
constante, o se asegura que el formato del valor es vlido para el tipo de objetos especificado.

RangeValidator, garantiza que un valor esta dentro de un rango de valores. RegularExpressionValidator, valida un valor contra una expresin regular. CustomValidator, valida un valor contra un mtodo personalizado definido por el
usuario.

Para obtener ms informacin sobre estos cinco controles, echemos vistazo a la seccin Controles de validacin de los tutoriales de Inicio rpido de Asp.Net. Para nuestro tutorial necesitaremos usar un RequiredFieldValidator, tanto en el TemplateField ProductName del GridView y del DetailsView y un RequiredFieldValidator en el TemplateField UnitPrice del DetailsView. Adems tendremos que aadir un ComparateValidator para el TemplateField UnitPrice de ambos controles que asegure que el precio ingresado tiene un valor mayor o igual a 0 y que se presenta en un formato de moneda valido. Nota: Si bien Asp.Net 1.x tuvo estos mismos cinco controles de validacin, Asp.Net 2.0 ha agregado una serie de mejoras, las dos primeras secuencias pertenecientes al lado del cliente soportan otros navegadores que no sean Internet Explorer y la capacidad de controles de validacin parciales en una pgina en grupos de validacin. Para obtener ms informacin sobre las nuevas funciones de los controles de validacin en Asp.Net 2.0, refirase a Discusin de los controles de validacin es Asp.Net 2.0.

Comencemos con la adicin de los controles de validacin necesarios para el EditItemTemplate en los TemplateFields del GridView. Para ello haga clic en el enlace Editar plantillas en la etiqueta inteligente del control GridView para mostrar la interfaz de edicin de plantilla. Desde aqu puede seleccionar en la lista desplegable la plantilla que desea editar. Como queremos ampliar la plantilla de edicin, tenemos que aadir los controles de validacin a los EditTemplate de ProductName y UnitPrice.

Figura 4. Necesitamos ampliar los EditItemTemplate de ProductName y UnitPrice En el EditItemTemplate ProductName, agregue un RequiredFieldValidator arrastrndolo desde el cuadro de herramientas a la interfaz de edicin de plantillas, colocndolo despus del TextBox. Todos los controles de validacin trabajan validando la entrada de un solo control web Asp.Net. Por lo tanto tenemos que indicar que el RequiredFieldValidator que acabamos de agregar debe validar el TextBox en el EditItemTemplate, esto se logra estableciendo la propiedad ControlToValidate del control de validacin al ID del control web correspondiente. Actualmente el TextBox tiene un ID no descriptivo TextBox1, pero lo cambiaremos por algo ms apropiado. Haga clic sobre el cuadro de texto en la plantilla y luego desde la ventana propiedades, cambie el ID TextBox1 por EditProductName.

Figura 5. Agregar un RequiredFieldValidator al EditItemTemplate ProductName

Figura 6. Cambie el ID del TextBox por EditProductName Luego, establezca la propiedad ControlToValidate del RequiredFieldValidator a EditProductName. Finalmente establezca la propiedad ErrorMessage en Debe proporcionar el nombre del producto y la propiedad Text en *. El valor de la propiedad Text si lo hay, es el texto que es mostrado por el control de validacin, si la validacin falla. El valor de la propiedad ErrorMessage, el cual es requerido, es usado el control ValidationSummary, si el valor de la propiedad Text se omite, el valor de la

propiedad ErrorMessage es tambin el texto que muestra el control de validacin, si la entrada nos es vlida. Despus de establecer estas tres propiedades del RequiredFieldValidator, la pantalla debe ser similar a la de la figura 7.

Figura 7. Ajuste las propiedades ControlToValidate, ErrorMessage y Text del RequiredFieldValidator Con el RequiredFieldValidator agregado a la EditItemTemplate del ProductName, todo lo que queda es aadir la validacin necesaria para el EditItemTemplate del UnitPrice. Como ya hemos decidido que para esta pgina, el UnitPrice es opcional si editamos un registro, no necesitamos agregar un RequiredFieldValidator. Sin embargo es necesario que agreguemos un CompareValidator, para asegurarnos que el UnitPrice que se suministre, tenga formato moneda y sea mayor o igual a 0. Antes de agregar el CompareValidator al EditItemTemplate del UnitPrice, primero cambiaremos el ID del control web TextBox de TextBox2 a EditUnitPrice. Despus de realizar este cambio, agregamos el CompareValidator, estableciendo su propiedad ControlToValidate en EditUnitPrice, su propiedad ErrorMessage a El precio debe ser mayor o igual a cero y no debe incluir el smbolo moneda y finalmente su propiedad Text a *. Para indicar que el valor de UnitPrice debe ser mayor o igual a cero, establezca la propiedad Operator del CompareValidator en GreaterThanEqual, su propiedad

ValueToCampare a 0 y su propiedad Type en Currency. La siguiente sintaxis declarativa muestra el EditItemTemplate del TemplateField UnitPrice luego que se han realizado estos cambios:
<EditItemTemplate> <asp:TextBox ID="EditUnitPrice" runat="server" Text='<%# Bind("UnitPrice", "{0:c}") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="EditUnitPrice" ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol" Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate>

Despus de realizar estos cambios, abra la pagina en un navegador. Si intenta omitir el nombre o introduce un valor de precio no valido al editar un producto aparece un asterisco junto al cuadro de texto. En la figura 8 se muestra, un valor de precio como $19.95 que incluye el smbolo de moneda y es considerado como no valido. El tipo Moneda del CompareValidator permite separadores de dgitos (por ejemplo comas o puntos dependiendo de la configuracin de la cultura) y un signo inicial de ms o menos, pero no permite un smbolo de moneda. Este comportamiento puede dejar perplejos a los usuarios que utilicen la interfaz de edicin actual, que presenta UnitPrice usando el formato moneda. Nota: Recuerde que en el tutorial Eventos asociados con la insercin, actualizacin y eliminacin, establecimos la propiedad DataFormatString del BoundField en {0:c} con el fin de darle el formato de moneda. Adems fijamos la propiedad ApplyFormatInEditMode en True, para dar formato de moneda al UnitPrice en la interfaz de edicin del GridView. Al convertir el BoundField en un TemplateField, Visual Studio tomo nota de estas configuraciones y formateo la propiedad Text del TextBox como moneda, utilizando la sintaxis de enlace de datos <%#Bind (UnitPrice, {0:c})%>.

Figura 8. Aparece un asterisco junto a los cuadro de texto con entradas invalidas. Aunque la validacin trabaja como esta, el usuario debe quitar manualmente el smbolo de moneda cuando edita un registro, lo cual no es aceptable. Para remediar esto tenemos tres opciones: 1. Configura el EditItemTemplate para que el valor UnitPrice no tenga el formato de una moneda. 2. Permitir al usuario el ingreso de un smbolo moneda mediante la eliminacin del CompareValidator y remplazndolo por un RegularExpresionValidator que verifica apropiadamente un valor formateado como moneda. El problema aqu es que la expresin para validar un valor moneda no es pequea y requiere escribir cdigo si se quiere incorporar la configuracin de la cultura. 3. Eliminar el control de validacin en conjunto y basar la lgica de validacin del lado del servidor en el controlador de eventos del RowUpdating del GridView. Vamos con la opcin #1 para este ejercicio. Actualmente el UnitPrice est formateado como moneda debido a su expresin de enlace para el TextBox en el EditItemTemplate: <%#Bind (UnitPrice, {0:c})%>. Cambie la instruccin de enlace por Bind(UnitPrice, {0:n2}), que da formato al resultado como un numero con dos dgitos de precisin. Esto se puede realizar directamente a travs de la sintaxis declarativa o haciendo clic en la opcin EditarDataBindings en el TextBox EditUnitPrice en el EditItemTemplate del TemplateField UnitPrice (Ve las figuras 9 y 10).

Figura 9. Haga clic en el enlace Editar DataBindings del TextBox

Figura 10. Especifique el formato adecuado en la declaracin Bind Con este cambio, el formato del precio en la interfaz de edicin incluye una coma como separador de grupo y un periodo como separador decimal, pero deja fuera el smbolo moneda. Nota: El EditItemTemplate del UnitPrice no incluye un RequiredFieldValidator, permitiendo que se produzca una devolucin de datos y actualizar la lgica desde un comienzo. Sin embargo, el controlador de eventos RowUpdating copiado del tutorial Eventos asociados en la insercin, actualizacin y eliminacin incluye una verificacin

mediante programacin que asegura que se proporcione UnitPrice. Sintase libre para eliminar esta lgica, dejarla como esta o agregar un RequiredFieldValidator al EditItemTemplate del UnitPrice. Paso 4. Resumen de los problemas de ingreso de datos Adems de los cinco controles de validacin, Asp.Net incluye el control ValidationSummary, que muestra el ErrorMessage de los controles de validacin que detectaron datos no validos. Este resumen de datos se puede mostrar como texto en la pgina o a travs de un cuadro de mensaje emergente, del lado del cliente. Vamos a mejorar este tutorial incluyendo un cuadro de mensaje en el lado del cliente que resuma los problemas de validacin. Para ello, arrastre un control ValidationSummary desde el cuadro de herramientas hasta el diseador. La ubicacin del control de validacin en realidad no importa, ya que vamos a configurarlo para que solamente muestre el resumen como un cuadro de mensaje. Despus de agregar este control, establezca su propiedad ShowSummary a False y su propiedad ShowMessageBox en True. Agregando esto los errores de validacin se resumen en un cuadro de mensaje del lado del cliente.

Figura 11. Los errores de validacin se resumen en un cuadro de mensaje del lado del cliente Paso 5. Agregar controles de validacin al InsertItemTemplate del DetailsView

Todo lo que resta en este tutorial es agregar los controles de validacin a la interfaz de insercin del DetailsView. El proceso de agregar controles de validacin a las plantillas del DetailsView es idntico al examinado en el paso 3, por lo tanto vamos a apresurar la tarea en este paso. Como hicimos con el EditItemTemplate del GridView, cambiemos el nombre de los ID indescriptibles de los TextBox, TextBox 1, TextBox 2 por InsertProductName e InsertUnitPrice. Agregue un RequiredFieldValidator al InsertItemTemplate ProductName. Ajuste el ControlToValidate al ID del TextBox, su propiedad Text a * y su propiedad ErrorMessage en Usted debe proporcionar el nombre del producto. Como el UnitPrice es requerido por esta pgina cuando agregamos un nuevo registro, agregue un RequiredFieldValidator al InserItemTemplate del UnitPrice, asigne sus propiedades ControlToValidate, Text y ErrorMessage adecuadamente. Por ltimo agregue un CompareValidator al InsertItemTemplate del UnitPrice, configure sus propiedades ControlToValidate, Text, ErrorMessage, Type, Operator y ValueToCompare de la misma forma que hicimos con el CompareValidator de Unit Price en el EditItemTemplate del GridView. Despus de agregar estos controles de validacin, un nuevo producto no puede ingresarse al sistema, si no se suministra su nombre o si su precio es un nmero negativo o tiene un formato invalido.

Figura 12. Se ha agregado a la lgica de validacin a la interfaz de insercin del DetailsView

Paso 6. Dividiendo los controles de validacin en grupos de validacin: Nuestra pgina consta de dos grupos de controles de validacin lgicamente desiguales: las que corresponden a la interfaz de edicin del GridView y las que corresponden a la interfaz de insercin del DetailsView. Por defecto cuando se produce una devolucin de datos, se comprueban todos los controles de validacin de la pgina. Sin embargo cuando editamos un registro, no deseamos que se comprueben los controles de la interfaz de insercin del DetailsView. La Figura 13 muestra nuestro dilema actual, cuando un usuario esta editando un producto con valores perfectamente legales, al hacer clic en actualizar origina un error de validacin porque el nombre y precio en la interfaz de insercin se encuentran en blanco.

Figura 13. La actualizacin de un producto origina que se disparen los controles de validacin de la interfaz de insercin. Los controles de validacin en Asp.Net 2.0 pueden dividirse en grupos de validacin por medio de su propiedad ValidationGroup. Para asociar un conjunto de controles de validacin en un grupo, basta con establecer su propiedad ValidationGroup en el mismo valor. Para nuestro tutorial, establecemos las propiedades ValidationGroup de los controles de validacin del TemplateFields del GridView en EditValidationControls y las propiedades ValidationGroup de los TemplatesFields del DetailsView en InsertValidationControls. Estos cambios pueden realizarse directamente en el marcado declarativo o por medio de la ventana propiedades cuando utilizamos la interfaz de edicin plantillas del Diseador. Adems de los controles de validacin, los botones y los controles relacionados con el botn en Asp.Net 2.0, tambin incluyen una propiedad ValidationGroup. La validacin

de un grupo de validacin comprueba su validez solamente cuando se produce una devolucin de datos originada por un botn que tiene el mismo valor en la propiedad ValidationGroup. Por ejemplo, para que el botn Insertar del DetailsView active el grupo de validacin InsertValidationControls deber establecer la propiedad ValidationGroup del CommandField en InsertValidationControls (Ver Figura 14). Adems establezca las propiedades ValidationGroup de los CommandFields del GridView en EditValidationControls.

Figura 14. Establezca la propiedad ValidationGroup del CommandField del DetailsView en InsertValidationControls Despus de estos cambios, los TemplateFields y los CommandFields del GridView y el DetailsView deben ser similares a lo siguiente: Los TemplateFields y CommandField de DetailsView
<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <InsertItemTemplate> <asp:TextBox ID="InsertProductName" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="InsertProductName" ErrorMessage="You must provide the product name"

ValidationGroup="InsertValidationControls">* </asp:RequiredFieldValidator> </InsertItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="UnitPrice" SortExpression="UnitPrice"> <InsertItemTemplate> <asp:TextBox ID="InsertUnitPrice" runat="server" Text='<%# Bind("UnitPrice") %>' Columns="6"> </asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ControlToValidate="InsertUnitPrice" ErrorMessage="You must provide the product price" ValidationGroup="InsertValidationControls">* </asp:RequiredFieldValidator> <asp:CompareValidator ID="CompareValidator2" runat="server" ControlToValidate="InsertUnitPrice" ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol" Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0" ValidationGroup="InsertValidationControls">* </asp:CompareValidator> </InsertItemTemplate> </asp:TemplateField> <asp:CommandField ShowInsertButton="True" ValidationGroup="InsertValidationControls" />

Los TemplateFields y CommandField del GridView


<asp:CommandField ShowEditButton="True" ValidationGroup="EditValidationControls" /> <asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="EditProductName" runat="server" Text='<%# Bind("ProductName") %>'> </asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="EditProductName" ErrorMessage="You must provide the product name" ValidationGroup="EditValidationControls">* </asp:RequiredFieldValidator>

</EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="UnitPrice" SortExpression="UnitPrice"> <EditItemTemplate> <asp:TextBox ID="EditUnitPrice" runat="server" Text='<%# Bind("UnitPrice", "{0:n2}") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="EditUnitPrice" ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol" Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0" ValidationGroup="EditValidationControls">* </asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label2" runat="server" Text='<%# Bind("UnitPrice", "{0:c}") %>'> </asp:Label> </ItemTemplate> </asp:TemplateField>

Hasta este momento, los controles de validacin de edicin especificados, se disparan solamente cuando se hace clic en el botn actualizar del GridView, y los controles de validacin de insercin especificados se disparan solamente cuando hacemos clic sobre el botn Insertar del DetailsView con el fin de resolver el problema sealado en la figura 13. Sin embargo, con este cambio nuestro control ValidationSummary ya no se muestra cuando ingresamos datos invlidos. El control ValidationSummary tambin contiene una propiedad ValidationGroup y solamente muestra el resumen de la informacin relacionada con los controles de validacin de su grupo de validacin. Por lo tanto es necesario tener dos controles de validacin en esta pgina, uno para el grupo de validacin InsertValidationControls y otra para EditValidationControls.
<asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="True" ShowSummary="False"

ValidationGroup="EditValidationControls" /> <asp:ValidationSummary ID="ValidationSummary2" runat="server" ShowMessageBox="True" ShowSummary="False" ValidationGroup="InsertValidationControls" />

Con esta adicin, nuestro tutorial esta completo. Resumen Aunque los BoundFields pueden proporcionar una interfaz de insercin y edicin, dicha interfaz no es personalizable. Por lo general deseamos aadir controles de validacin a la interfaz de edicin e insercin para asegurarnos que el usuario introduzca las entradas requeridas en un formato valido. Para realizar esto hay que convertir los BoundFields en TemplateFields y agregar los controles de validacin a la plantilla adecuada. En este tutorial hemos ampliado el ejemplo del tutorial de Examen de eventos asociados con la insercin, actualizacin y eliminacin de datos, agregando controles de validacin a la interfaz de edicin del GridView y a la interfaz de insercin del DetailsView. Por otra parte hemos visto como mostrar el resumen de la informacin de validacin por medio de un control ValidationSummary y la forma en cmo dividir los controles de validacin en grupos de validacin distintos. Como vimos en este tutorial, los TemplateFields permiten ampliar las interfaces de edicin e insercin para incluir controles de validacin. Los TemplateFields tambin se pueden ampliar para incluir controles de entradas web adicionales, lo que permite que el TextBox sea reemplazado por un control ms adecuado. En nuestro siguiente tutorial veremos la forma de sustituir el control TextBox por un control de datos DropDownList, que es ideal cuando editamos una clave externa. (Por ejemplo el CategoryID o SupplierID de la tabla Products)

20.

PERSONALIZACION DE LA INTERFAZ DE MODIFICACION DE DATOS

En este tutorial veremos cmo personalizar la interfaz de un GridView editable, remplazando los controles CheckBox y TextBox estndar con controles de entradas web alternativos. Introduccin Los BoundFields y los CheckBoxFields utilizados por los controles GridView y DetailsView simplifican el proceso de modificacin de datos debido a la capacidad de sus interfaces de lectura, insercin y edicin. Estas interfaces pueden presentarse sin necesidad de agregar ningn marcado declarativo adicional o cdigo. Sin embargo las interfaces de los BoundFields y los CheckBoxFields carecen de la capacidad de personalizacin que a menudo es necesario en las aplicaciones del mundo real. Con el fin de personalizar la interfaz de edicin e insercin de un GridView o DetailsView tenemos que usar un TemplateField. En el tutorial anterior vimos como personalizar las interfaces de modificacin de datos mediante la adicin de controles web de validacin. En este tutorial veremos cmo personalizar estos controles sustituyendo los controles estndar TextBox y CheckBox del CheckBoxField con controles web de entrada alternativos. En particular vamos a construir un GridView editable que permite actualizar el nombre de un producto, categora, proveedor y el estado. Durante la edicin de una fila en particular, los campos Categora y Proveedor se presentaran como dos DropDownList, que contienen el conjunto de las categoras y proveedores disponibles para elegir. Adems sustituiremos el CheckBox por defecto del CheckBoxField con un control RadioButtonList que ofrezca las opciones Activo y descontinuado

Figura 1. La interfaz de edicin incluye dos DropDownList y un RadioButtonList Paso 1: Crear el adecuado UpdateProduct En este tutorial vamos a crear un GridView editable que permita la edicin del nombre de un producto, categora, proveedor y estado de descontinuado. Por lo tanto necesitamos una recarga al UpdateProduct que acepte cinco parmetros de entrada para los cuatro valores del producto ms el ProductID. Al igual que con nuestras recargas anteriores, esta: 1. Recuperar la informacin del producto desde la base de datos para el ProductID especificado. 2. Actualiza los campos ProductName, CategoryID, SupplierID y Discontinued 3. Enva la solicitud de actualizacin de la DAL a travs del mtodo Update () del TableAdapter Para mayor brevedad, de esta recarga en particular, omitimos la comprobacin de reglas de negocio que garantiza que un producto que se marca como descontinuado no es el nico producto suministrado por un determinado proveedor. Sintase libre si lo prefiere de aadirla o refactorizar la lgica como un mtodo separado. El siguiente cdigo muestra la nueva recarga UpdateProduct en la clase ProductsBLL:
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMet hodType.Update, False)> Public Function UpdateProduct(

ByVal productName As String, ByVal categoryID As Nullable(Of Integer), ByVal supplierID As Nullable(Of Integer), ByVal discontinued As Boolean, ByVal productID As Integer) As Boolean Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID) If products.Count = 0 Then Return False End If Dim product As Northwind.ProductsRow = products(0) product.ProductName = productName If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If product.Discontinued = discontinued Dim rowsAffected As Integer = Adapter.Update(product) Return rowsAffected = 1 End Function

Paso 2: Crear el GridView Editable Con la recarga del Update agregada, estamos listos para crear nuestro GridView Editable. Abra la pgina CustomizedUI.aspx de la carpeta EditInsertDelete y agregue un control GridView al diseador. Luego cree un nuevo ObjectDataSource desde la etiqueta del GridView. Configure el ObjectDataSource para que recupere la informacin de los productos por medio del mtodo GetProducts() de la clase ProductsBLL y luego actualice los datos del producto utilizando la recarga UpdateProduct que acabamos de crear. En las listas desplegables de las pestaas INSERT y DELETE seleccione ninguno. Como hemos visto en los tutoriales de modificacin de datos, la sintaxis declarativa del ObjectDataSource creada por Visual Studio asigna a la propiedad OldValuesFormatString el valor de original_{0}. Esto por supuesto no funcionara con nuestra capa lgica de negocios, ya que nuestros mtodos no esperan que se pase el

valor original de ProductID. Por lo tanto como hemos hecho en los tutoriales anteriores, tmese un momento para quitar la asignacin de esta propiedad desde la sintaxis declarativa, o en su lugar establezca el valor de esta propiedad en {0}.

Figura 2. Configure el ObjectDataSource para que utilice la recarga UpdateProduct que acabamos de crear Despus de este cambio el marcado declarativo debe tener el siguiente aspecto:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProducts" TypeName="ProductsBLL" UpdateMethod="UpdateProduct"> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="discontinued" Type="Boolean" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> </asp:ObjectDataSource>

Tenga en cuenta que la propiedad OldValuesParameterFormatString se ha eliminado y que hay un Parameter en la coleccin UpdateParameters para cada uno de los parmetros de entrada esperado por la recarga UpdateProduct.

Mientras el ObjectDataSource se configura para actualizar solo un subconjunto de valores del producto, el GridView actualmente muestra todos los campos de los productos. Tmese un momento para editar el GridView para que: Este solo incluya los BoundFields ProductName, SupplierName, CategoryName y el CheckBoxField Discontinued Los campos CategoryName y SupplierName aparecen antes del CheckBoxField (Discontinued) La propiedad HeaderText de los BoundFields CategoryName y SupplierName se establezca en Categora y Proveedor respectivamente. El soporte de edicin este habilitado (Ver la casilla de verificacin Habilitar Edicin en la etiqueta inteligente) Despus de estos cambios, el diseo ser similar a la Figura 3, con la sintaxis declarativa que se muestra posteriormente.

Figura 3. Eliminar los campos innecesarios del GridView


<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True"

SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView>

Hasta este punto el comportamiento de solo lectura del GridView est completo. Cuando vemos los datos, cada producto es presentado como una fila del GridView, mostrando el nombre del producto, categora, proveedor y estado del Descontinuado. Nota: Como se explico en el tutorial Descripcin general de actualizar, insertar y eliminar datos, es de vital importancia que el ViewState del GridView este habilitado (comportamiento por defecto). Si establece la propiedad ViewState del GridView en falso se corre el riesgo de tener usuarios concurrentes eliminando o editando registros de manera no intencional.

Figura 4. La interfaz de solo lectura del GridView est completa Paso 3: Usar un DropDownList para las interfaces de Categora y Proveedor Recordemos que el objeto ProductsRow contiene las propiedades CategoryID, CategoryName, SupplierID y SupplierName, que proporcionan los valores ID de llave fornea actual en la tabla Products de la base de datos y los correspondientes valores Name en las tablas Categories y Suppliers. El CategoryID y SupplierID del ProductRows

pueden ser tanto de leer y escribir, mientras que las propiedades CategoryName y SupplierName estn marcadas como de solo lectura. Debido a la situacin de solo lectura de las propiedades CategoryName y

SupplierName, los correspondientes BoundField tienen su propiedad ReadOnly establecida en true, previniendo que estos valores se modifiquen cuando la fila se edite. Aunque puede establecer la propiedad ReadOnly en False, presentar los BoundFields CategoryName y SupplierName como TextBoxes durante la edicin, dar lugar a que se emita una excepcin cuando el usuario intente actualizar un producto, ya que la recarga UpdateProduct no toma entradas para CategoryName y SupplierName. De hecho no queremos crear esto en la recarga, por dos razones: La tabla Products no tiene campos SupplierName y CategoryName, sino SupplierID y CategoryID. Por lo tanto queremos que nuestro mtodo pase estos valores de ID particulares y no la bsqueda de sus valores en las tablas. Requiere que el usuario escriba el nombre del proveedor o de una categora, esta opcin no es la ideal, ya que requiere que el usuario conozca todas las categoras y proveedores disponibles y que su ortografa sea la correcta. Los campos proveedor y categora deben mostrar los nombres de las categoras y de los proveedores en el modo de solo lectura (como ahora) y una lista desplegable debe mostrar sus opciones cuando son editadas. Usando un DropDownList, el usuario puede ver rpidamente que categoras y proveedores son validos y escoger entre ellos haciendo ms fcil su decisin. Para proporcionar este comportamiento, necesitamos convertir los BoundFields SupplierName y CategoryName en TemplateFields cuyo ItemTemplate emita los valores SupplierName y CategoryName y cuyo EditItemTemplate use un control DropDownList para mostrar la lista de categoras y proveedores validos. Adicin de los DropDownList Categoras y Proveedores Comencemos por convertir los BoundFields SupplierName y CategoryName en TemplateFields: haciendo clic en el enlace Editar columnas en la etiqueta inteligente del control GridView, seleccione el BoundField de la lista inferior izquierda y haga clic en el enlace Convertir este campo en un TemplateField. El proceso de conversin creara un

TemplateField con un ItemTemplate y un EditTemplate, como se muestra en la siguiente sintaxis declarativa:


<asp:TemplateField HeaderText="Category" SortExpression="CategoryName"> <EditItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("CategoryName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

Como el BoundField est marcado como de solo lectura, tanto el ItemTemplate y el EditItemTemplate contienen un control Label cuya propiedad Text esta enlazada al campo de datos aplicado (CategoryName, en la sintaxis de ms arriba). Tenemos que modificar el EditItemTemplate, remplazando el control web Label por un control DropDownList. Como hemos visto en los tutoriales anteriores, la plantilla puede ser editada desde el diseador o directamente en la sintaxis declarativa. Para editar a travs del diseador, haga clic en el enlace Editar plantillas de la etiqueta inteligente del GridView y optamos por trabajar con el EditItemTemplate del campo Category. Retire el control web Label y sustityalo por un control DropDownList, estableciendo su propiedad ID en Categoras

Figura 5. Remueva el TextBox y agregue un DropDownList al EditItemTemplate Luego necesitamos poblar el DropDownList con las categoras disponibles. Haga clic en el enlace de Elegir origen de datos en la etiqueta inteligente del DropDownList y escoja la opcin crear un nuevo ObjectDataSource llamado CategoriesDataSource.

Figura 6. Cree un nuevo control ObjectDataSource llamado CategoriesDataSource Haga que el ObjectDataSource devuelva todas las categoras, enlazndolo al mtodo GetCategories() de la clase CategoriesBLL.

Figura 7. Enlace el ObjectDataSource al mtodo GetCategories () de la clase CategoriesBLL

Por ltimo configure las definiciones del DropDownList de modo que se muestre el campo CategoryName en cada ListItem del DropDownList con el campo CategoryID utilizado como valor.

Figura 8. Se muestra el campo CategoryName y se utiliza como valor el campo CategoryID Despus de realizar estos cambios el marcado declarativo para el EditItemTemplate en el TemplateField del CategoryName incluir un DropDownList y un ObjectDataSource:
<asp:TemplateField HeaderText="Category" SortExpression="CategoryName"> <EditItemTemplate> <asp:DropDownList ID="Categories" runat="server" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID"> </asp:DropDownList> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server"

Text='<%# Bind("CategoryName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

Nota: El DropDownList en el EditItemTemplate debe tener su ViewState habilitado. Sumado a que la sintaxis de enlace a datos agregada a la sintaxis declarativa del DropDownList y los comandos Eval() y Bind() solo pueden aparecer en los controles cuyo ViewState est habilitado. Repita estos pasos del para agregar un DropDownList Esto llamado supone Suppliers agregar al un

EditItemTemplate

TemplateField

SupplierName.

DropDownList a la EditItemTemplate y crear otro ObjectDataSource. Sin embargo, el ObjectDataSource del DropDownList Suppliers debe ser configurado para que invoque el mtodo GetSuppliers() de la clase SuppliersBLL. Adems configure el DropDownList para que muestre el campo CompanyName y utilice el campo SupplierID como el valor para sus ListItems. Luego de aadir el DropDownList a los dos EditItemTemplates, cargue la pgina en un navegador y haga clic en el botn editar para el producto Sazonador Cajun del Chef Anton. Como muestra la figura 9, las columnas categoras y proveedores del producto son presentadas como listas desplegables que contienen todas las categoras y proveedores disponibles para su seleccin. Sin embargo, tenga en cuenta que los primeros elementos en ambas listas son seleccionados por defecto (para la categora Bebidas y el proveedor Lquidos Exticos) a pesar que el sazonador Cajun del Chef Anton es un condimento suministrado por Delicias Cajun de Nueva Orleans.

Figura 9. El primer elemento de la lista del DropDownList es seleccionado por defecto Adems si hace clic en actualizar, usted encontrar que los valores SupplierID Y CategoryID se establecen en Null. Ambos comportamientos no deseados son causados porque el EditItemTemplate del DropDownList no est enlazado a los campos de datos del producto subyacente. Enlazar los DropDownList a los campo de datos CategoryID y SupplierID Con el fin de editar los DropDownList categoras y proveedores de productos establezca los valores adecuados y haga que estos valores sean enviados de regreso al mtodo UpdateProduct de la BLL al hacer clic en actualizar, necesitamos enlazar las propiedades SelectedValue de los DropDownList a los campos CategoryID y SupplierID usando el enlace de datos bidireccional. Para lograr esto con el DropDownList Categories puede agregar SelectedValue=<%# Bind (CategoryID) %> directamente en la sintaxis declarativa. De forma alternativa puede establecer el enlace de datos del DropDownList editando la plantilla a travs del diseador y haciendo clic en el enlace Editar DataBindings en la etiqueta inteligente del DropDownList. Luego indique que la propiedad SelectedValue debe estar enlazada al campo CategoryID utilizando el enlace de datos bidireccional (Ver Figura 10). Repita ya sea el diseo o el proceso declarativo de enlazar el DropDownList Suppliers al campo de datos SupplierID. Una vez que se han aplicado los enlaces a las propiedades SelectedValue de los dos DropDownList, las columnas categoras y proveedores de los productos editados por defecto son los valores del producto actual. Al hacer clic en Update, los valores CategoryID y SupplierID de las lista de elementos del DropDownList seleccionados sern pasados al mtodo UpdateProduct. La figura 11 muestra el tutorial despus que se ha agregado la sentencia de enlace de datos, note como los elementos seleccionados para el Sazonador Cajun del Chef Anton, Condimentos y Delicias Cajun de Nueva Orleans son correctos.

Figura 10. Enlace el CategoryID al DropDownList a travs de la propiedad SelectedValue mediante el enlace de datos bidireccional

Figura 11.Los valores actuales de categora y proveedor del producto editado son seleccionados de forma predeterminada Manejo de valores Nulls Las columnas CategoryID y SupplierID de la tabla Products pueden ser Null, pero los DropDownList en el EditItemTemplate no incluyen un elemento de la lista para representar un valor NULL. Esto tiene dos consecuencias: El usuario no puede utilizar nuestra interfaz para cambiar la categora de un producto o proveedor de un valor no-Null a un valor Null. Si un producto tiene un CategoryID o un SupplierID Null, al hacer clic en el botn Editar dar como resultado una excepcin. Esto es porque el valor Null devuelto por CategoryID (o SupplierID) en la declaracin Bind no se asigna a un valor en el

DropDownList (el DropDownList produce una excepcin cuando su propiedad SelectedValue se establece en ningn valor en su coleccin de elementos de lista). Con el fin de soportar los valores Null en CategoryID y SupplierID, tenemos que aadir otro ListItem a cada DropDownList para representar un valor Null. En el tutorial Informe Maestro-Detalle con un DropDownList, vimos como agregar un ListItem adicional al DropDownList enlazado, lo cual implica establecer la propiedad AppendDataBoundItems del DropDownList en True y agregar manualmente el ListItem adicional. Sin embargo en el tutorial anterior hemos aadido un ListItem con un Value de -1. Sin embargo, la lgica de enlace de datos en Asp.Net, convierte automticamente una cadena en blanco a un valor Null y viceversa. Por lo tanto para este tutorial queremos que el Value del ListItem sea una cadena vaca. Comience por establecer la propiedad AppendDataBoundItems de ambos

DropDownLists en True. Luego agregue el ListItem Null agregando el siguiente elemento <asp:ListItem> a cada DropDownList para que el marcado declarativo sea as:
<asp:DropDownList ID="Categories" runat="server" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID" SelectedValue='<%# Bind("CategoryID") %>' AppendDataBoundItems="True"> <asp:ListItem Value="">(None)</asp:ListItem> </asp:DropDownList>

Hemos optado por utilizar Ninguno como el valor de texto para este ListItem, pero tambin se puede cambiar a una cadena en blanco si lo desea. Nota: Como vimos en el tutorial Informe Maestro/Detalle con un DropDownList, los ListItem se pueden agregar a un DropDownList por medio del diseador, haciendo clic en la propiedad tem en la ventana propiedades (el cual muestra el editor de la coleccin ListItem). Sin embargo asegrese de agregar el ListItem Null a este tutorial por medio de la sintaxis declarativa. Si utiliza el editor de la coleccin ListItem, la sintaxis declarativa generada omite por completo el Value establecido cuando se asigna una cadena en blanco, creando un marcado declarativo como: <asp:ListItem>(None)</asp:ListItem>. Si bien esto puede parecer inofensivo, la falta de valor hace que el DropDownList utilice en su lugar el valor de la propiedad Text. Esto significa que si el ListItem Null esta seleccionado, el valor (Ninguno) intentara ser

asignado al CategoryID, originando una excepcin. Estableciendo explcitamente Value=, un valor Null ser asignado a CategoryID cuando el ListItem Null esta seleccionado. Repita estos pasos para el control DropDownList de Proveedores. Con este ListItem adicional, la interfaz de edicin ahora puede asignar valores Null a los campos CategoryID y SupplierID, como se muestra en la figura 12.

Figura 12. Seleccione (Ninguno) para asignar un valor Null para la categora o proveedor del producto Paso 4: Uso de RadioButtons para el Status Discontinued Actualmente el campo de datos Discontinued de los productos se expresa usando un CheckBoxField, el cual presenta un CheckBoxField deshabilitado para las filas de solo lectura y un CheckBox habilitado para la fila que se est editando. Si bien esta interfaz de usuario es a menudo conveniente, si es necesario podemos personalizarlo por medio de un TemplateField. Para este tutorial cambiaremos el CheckBoxField en el TemplateField para que utilice un control RadioButtonList con dos opciones activo y descontinuado, en el cual el usuario puede especificar el valor Discontinued del producto. Comience por convertir el CheckBoxField Discontinued en un TemplateField, lo cual creara un TemplateField con un ItemTemplate y un EditItemTemplate. Ambas plantillas incluyen un control CheckBox con su propiedad Checked enlazada al campo de datos

Discontinued, la nica diferencia entre ambos es que la propiedad Enabled del CheckBox en el ItemTemplate se establece en False. Remplace la casilla de verificacin, tanto en el ItemTemplate y el EditItemTemplate con un control RadioButtonList, estableciendo ambas propiedades ID del RadioButtonList en DiscontinuedChoice. Luego indique que cada RadioButtonList debe contener dos botones, uno llamado activo con un valor False y otro llamado Discontinued con un valor de True. Para realizar esto puede ingresar elementos <asp:ListItem> directamente en la sintaxis declarativa o utilizar el editor de la coleccin ListItem del diseador. La figura 13 muestra el editor de la coleccin ListItem despus que las dos opciones del radio button se han especificado.

Figura 13. Agregar las opciones Activo y Descontinuado al RadioButtonList Como el RadioButtonList del ItemTemplate no debe ser editable, establezca su propiedad Enabled en False, dejando la propiedad Enabled en True (por defecto) para el RadioButtonList en el EditItemTemplate. Esto har que las opciones en la fila no editable sean solo de lectura, pero permita al usuario cambiar los valores del RadioButton en la fila editada. Aun tenemos que para asignar que la las propiedades SelectedValue del de los controles sea

RadioButtonList

opcin

correspondiente

RadioButtonList

seleccionada basada en el campo de datos Discontinued del producto. Al igual que con los DropDownList examinados en el ejemplo anterior, esta sintaxis de enlace de datos

puede ser agregada directamente en el marcado declarativo o a travs del enlace editar DataBindings en la etiqueta inteligente de los RadioButtonList. Despus de agregar los dos RadioButtonList y configurarlos, el marcado declarativo del TemplateField Discontinued sera similar a:
<asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued"> <ItemTemplate> <asp:RadioButtonList ID="DiscontinuedChoice" runat="server" Enabled="False" SelectedValue='<%# Bind("Discontinued") %>'> <asp:ListItem Value="False">Active</asp:ListItem> <asp:ListItem Value="True">Discontinued</asp:ListItem> </asp:RadioButtonList> </ItemTemplate> <EditItemTemplate> <asp:RadioButtonList ID="DiscontinuedChoice" runat="server" SelectedValue='<%# Bind("Discontinued") %>'> <asp:ListItem Value="False">Active</asp:ListItem> <asp:ListItem Value="True">Discontinued</asp:ListItem> </asp:RadioButtonList> </EditItemTemplate> </asp:TemplateField>

Con estos cambios la casilla de verificacin Discontinued se ha transformado en una lista de casillas de verificacin para mostrar un par de botones (Ver Figura 14). Durante la edicin de un producto, el apropiado RadioButton es seleccionado y el estado discontinued del producto puede ser actualizado seleccionando el otro RadioButton y dando clic en actualizar.

Figura 14. La casilla Discontinued ha sido remplazada por un par de botones Nota: Dado que la columna Discontinued en la tabla Productos no puede tener valores Null, no debe preocuparse por capturar la informacin Null en la interfaz. Sin embargo si la columna Discontinued aceptara valores Null podramos aadir un tercer botn a la lista, estableciendo su Value en una cadena vaca (Value=) al igual que con los DropDownList Categora y proveedor. Resumen Mientras que los BoundField y los CheckBoxField son presentados automticamente como de solo lectura, edicin e interfaces de insercin, carecen de una capacidad de personalizacin. Sin embargo, a menudo podremos personalizar la interfaz de edicin o insercin, agregando controles de validacin (como vimos en el tutorial anterior) o mediante la personalizacin del ingreso de datos en la interfaz de usuario (como vimos en este tutorial). La personalizacin de la interfaz con un TemplateField se puede resumir en los siguientes pasos: Agregue un TemplateField o convierta un BoundField o CheckBoxField en un TemplateField. Ampli la interfaz si es necesario Enlace los campos de datos adecuadamente a los controles web agregados recientemente utilizando el enlace de datos bidireccional.

Adems de utilizar los controles web incorporados en Asp.Net, tambin podemos personalizar las plantillas del TemplateField con controles de usuario y controles de compilador de servidor personalizados.

21.

APLICACIN DE LA CONCURRENCIA OPTIMISTA

Para una aplicacin web que permite a diversos usuarios modificar los datos, existe el riesgo que dos usuarios puedan editar los mismos datos al mismo tiempo. En este tutorial vamos a aplicar el control de simultaneidad optimista para manejar este riesgo. Introduccin Para las aplicaciones web que solo permiten a los usuarios visualizar los datos, o para aquellas que incluyen a un nico usuario que puede modificar los datos, no hay amenaza que dos usuarios sobrescriban accidentalmente los cambios de los otros. Sin embargo para las aplicaciones web que permiten a varios usuarios editar y eliminar datos, existe la posibilidad que la modificacin de un usuario entre en conflicto con la de otro usuario concurrente. Si no hay una poltica de concurrencia, cuando dos usuarios realizan la edicin de un nico registro al mismo tiempo, el usuario que realice los ltimos cambios, anula los cambios realizados por el primer usuario. Por ejemplo imagine que dos usuarios Jisun y Sam estuvieron visitando una pgina de nuestra aplicacin, que permite a los usuarios eliminar y actualizar los productos por medio de un control GridView. Ambos hacen clic en el botn editar en el GridView simultneamente. Jisun cambia el nombre del producto por Chai Tea y hace clic en el botn actualizar. El resultado neto es una sentencia UPDATE que se enva a la base de datos que establece todos los campos de producto actualizables (a pesar que Jisun solo actualiza un campo, ProductName). En este momento la base de datos tiene los valores Chai Tea, la categora Bebidas, el proveedor Lquidos Exticos y as sucesivamente para este producto en particular. Sin embargo, el GridView de la pantalla de Sam sigue mostrando el nombre del producto de la fila edi table como Chai. Unos segundos despus que se realizan los cambios de Jisun, Sam actualiza la categora a condimentos y hace clic en actualizar. Esto resulta en una sentencia UPDATE que se enva a la base de datos y establece el nombre del producto en Chai, la CategoryID al correspondiente CategoryID de los condimentos y as sucesivamente. Los cambios de nombre del producto realizados por Jisun han sido sobrescritos. La figura 1 representa esta serie de eventos.

Figura 1. Cuando dos usuarios actualizan un registro simultneamente hay riesgo que un usuario sobrescriba los cambios realizados por otro usuario Del mismo modo cuando dos usuarios visitan una pgina, un usuario puede estar en medio de la actualizacin de un registro cuando este es eliminado por otro usuario. O entre el momento en que se carga la pgina y se ha hecho clic en el botn eliminar, otro usuario puede haber modificado el contenido del usuario. Existen tres estrategias de control de concurrencia disponibles:

No hacer nada, si usuarios concurrentes modifican un registro, se deja la ltima


actualizacin del ltimo usuario (comportamiento por defecto)

Simultaneidad Optimista, asumimos que si bien pueden existir conflictos de


concurrencia de vez en cuando, la gran mayora de conflicto surgirn con el tiempo, por lo tanto si se plantea un conflicto solo se tiene que informar al usuario que sus datos no pueden guardarse porque otro usuario ha modificado los mismos datos.

Concurrencia pesimista, asumimos que los conflictos de concurrencia son comunes


y que los usuarios no van a tolerar que se les diga que sus cambios no se realizaron debido a la actividad concurrente de otro usuario, por lo tanto cuando un usuario comienza a actualizar un registro, lo bloquea, evitando de esta manera que cualquier otro usuario edite o borre ese registro hasta que no haya completado sus modificaciones.

Todos los tutoriales hasta el momento han utilizado la estrategia de solucin de concurrencia por defecto, es decir se dejan los ltimos cambios realizados. En este tutorial examinaremos la forma de aplicar el control de concurrencia optimista. Nota: No veremos ejemplos de simultaneidad pesimista en esta serie de tutoriales. La concurrencia pesimista es utilizada raramente, ya que si no se aplica de forma adecuada puede impedir que otros usuarios actualicen datos. Por ejemplo si un usuario bloquea un registro para editarlo y luego se va durante el da sin antes desbloquearlo, ningn otro usuario ser capaz de actualizar este registro hasta que el usuario original regrese y complete la actualizacin. Por lo tanto en las situaciones donde se usa concurrencia pesimista, generalmente hay un tiempo de espera que de alcanzarse, cancela el bloqueo. Un ejemplo de control de concurrencia pesimista, son los sitios web de ventas de tickets que bloquean un asiento especial por un corto periodo de tiempo, mientras el usuario completa el pedido. Paso 1. Como se implementa la simultaneidad optimista El control de simultaneidad optimista trabaja asegurndose que el registro que se elimina o se actualiza tenga los mismos valores que cuando inicio el proceso de eliminacin o edicin. Por ejemplo al hacer clic en el botn editar de un GridView editable, los valores del registro se leen de la base de datos y se muestran en TextBox y otros controles web. Estos valores originales se guardan en el GridView. Despus cuando el usuario realice los cambios y haga clic en el botn actualizar, el valor original y los nuevos valores son enviados a la capa lgica de negocios y luego dentro de la capa de acceso a datos. La capa de acceso a datos debe emitir una sentencia SQL que solo actualizar el registro si los valores originales que el usuario edito inicialmente son idnticos a los valores que permanecen en la base de datos. La figura 2 muestra una secuencia de estos eventos.

Figura 2. Para que la actualizacin o eliminacin tengan xito, los valores originales deben ser iguales a los valores actuales en la base de datos Existen varios enfoques para la implementacin de concurrencia optimista (Vea Lgica de actualizacin de la concurrencia optimista de Peter A. Bromberg). El DataSet tipiado de ADO.Net proporciona una implementacin que puede ser configurada con solo marcar una casilla de verificacin. Habilitar la simultaneidad optimista para un TableAdapter en un DataSet tipiado aumenta las declaraciones UPDATE y DELETE para incluir una comparacin de todos los valores originales de la clausula WHERE. Por ejemplo, la siguiente sentencia UPDATE, actualiza el nombre y precio de un producto solo si los valores actuales de la base de datos son iguales a los recuperados originalmente cuando se actualizo el registro en el GridView. Los parmetros

@ProductName y @UnitPrice contienen los nuevos valores ingresados por el usuario, mientras que los parmetros @Original_ProductName y @Original_UnitPrice contienen los valores que fueron cargados originalmente cuando se hizo clic en el botn Editar del GridView:
UPDATE Products SET ProductName = @ProductName, UnitPrice = @UnitPrice WHERE ProductID = @original_ProductID AND ProductName = @original_ProductName AND UnitPrice = @original_UnitPrice

Nota: Esta sentencia UPDATE se ha simplificado para facilitar su lectura. En la prctica la verificacin de UnitPrice en la clausula WHERE estar ms involucrada con si UnitPrice contiene Null y si Null=Null siempre devuelve false (en su lugar debemos usar IsNull) Adems de utilizar otra sentencia UPDATE subyacente, configurar un TableAdapter para que use concurrencia optimista tambin modifica la asignacin de sus mtodos directos DB. Recuerde que en nuestro primer tutorial Creacin de una capa de acceso a datos los mtodos directos DB son aquellos que aceptan una lista de valores escalares como parmetros de entrada (en lugar de un DataRow fuertemente tipiado o una instancia DataTable). Cuando se utiliza la concurrencia optimista, los mtodos directos DB Update() y Delete() tambin incluyen parmetros de entrada para los valores originales. Por otra parte, el cdigo en la BLL para utilizar el mtodo de actualizacin por lotes, (las recargas del mtodo Update() que aceptan DataRows y DataTables en lugar de valores escalares) tambin debe cambiarse. En lugar de ampliar los TableAdapter existentes para que utilicen la simultaneidad optimista (necesitaramos cambiar la BLL arreglada), vamos a crear un nuevo DataSet tipiado llamado NorthwindOptimisticConcurrency, al cual agregaremos un TableAdapter Products que utilice la concurrencia optimista. Luego crearemos en la capa lgica de negocios una clase ProductsOptimisticConcurencyBLL que tiene las modificaciones necesarias para soportar la concurrencia optimista en la DAL. Una vez establecido esto, estamos listos para crear la pgina Asp.Net. Paso 2. Creacin de una capa lgica de negocios que admita la concurrencia optimista

Para crear un nuevo DataSet tipiado, damos clic derecho en DAL en la carpeta App_Code y agregamos un nuevo DataSet tipiado llamado NorthwindOptimisticConcurrency. Como vimos en el primer tutorial, esto agregara un nuevo TableAdapter al DataSet tipiado, poniendo en marcha de forma automtica el asistente de configuracin del TableAdapter. En la primera pantalla, se nos pedir que especifiquemos la base de datos a la cual debe conectarse, utilizamos la configuracin NORTHWNDConnectionString del Web.Config para conectarnos a la misma base de datos Northwind.

Figura 3. Conexin a la misma base de datos Northwind Luego nos pregunta sobre la manera de consultar los datos: por medio de una sentencia SQL ad-hoc, un nuevo procedimiento o un procedimiento almacenado existente. Puesto que hemos utilizado las consultas SQL ad-hoc en nuestra DAL original, utilizamos esta opcin aqu.

Figura 4. Especifique el uso de sentencias SQL ad-hoc para recuperar los datos En la siguiente pantalla escriba la consulta SQL que se va a utilizar para recuperar la informacin del producto. Utilizaremos la misma consulta SQL que usamos para el Products TableAdapter de nuestra DAL original, que devuelve todas las columnas Product junto con los nombres de las categoras y los proveedores de los productos:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products

Figura 5. Utilice la misma consulta SQL del TableAdapter Products de la DAL original Antes de pasar a la siguiente pantalla, haga clic en el botn Opciones avanzadas. Para que este TableAdapter emplee el control de concurrencia optimista, simplemente seleccione la casilla de verificacin de la opcin Usar concurrencia optimista.

Figura 6. Habilitar control de concurrencia optimista marcando la casilla de verificacin Usar concurrencia optimista Por ltimo indique que el TableAdapter debe utilizar los enfoques de acceso a datos que llenan y devuelven un DataTable, adems debe indicar que se deben crear los mtodos DB directos. Cambie el nombre del mtodo que utiliza el enfoque de devolver un DataTable de GetData a GetProducts, con el fin de reflejar las convenciones de nomenclatura utilizadas en nuestra DAL original.

Figura 7. El TableAdapter utiliza todos los enfoques de acceso a datos Despus de completar el asistente, el diseador del DataSet incluir un TableAdapter y un DataTable Products fuertemente tipiados. Tmese un momento para cambiar el nombre del DataTable de Products a ProductsOptimisticConcurrency, esto se puede hacer haciendo clic derecho sobre la barra de titulo del DataTable y elija Cambiar nombre del men contextual.

Figura 8. Se agregan un DataTable y un TableAdapter al DataSet tipiado Para ver las diferencias entre las consultas UPDATE y DELETE del TableAdapter Products (que no utiliza concurrencia optimista) y el TableAdapter ProductsOptimisticConcurrency (que si utiliza concurrencia optimista), haga clic en el

TableAdapter y vaya a la ventana propiedades. En las subpropiedades CommandText de las propiedades DeleteCommand y UpdateCommand puede ver la sintaxis SQL actual que es enviada a la base de datos cuando los mtodos relacionados con la actualizacin y eliminacin de la DAL son invocados. Para el TableAdapter ProductsOptimisticConcurrency la sentencia DELETE usada es:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID) AND ([ProductName] = @Original_ProductName) AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULL) OR ([SupplierID] = @Original_SupplierID)) AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULL) OR ([CategoryID] = @Original_CategoryID)) AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULL) OR ([QuantityPerUnit] = @Original_QuantityPerUnit)) AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULL) OR ([UnitPrice] = @Original_UnitPrice)) AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULL) OR ([UnitsInStock] = @Original_UnitsInStock)) AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULL) OR ([UnitsOnOrder] = @Original_UnitsOnOrder)) AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULL) OR ([ReorderLevel] = @Original_ReorderLevel)) AND ([Discontinued] = @Original_Discontinued))

En cambio la sentencia DELETE para el TableAdapter Product en nuestra DAL original es mucho ms sencilla:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))

Como puede ver, la clausula WHERE en la sentencia DELETE para el TableAdapter que utiliza simultaneidad optimista incluye una comparacin entre cada uno de los valores de las columnas existentes en la tabla Products y los valores originales de las mismas en el GridView (o DetailsView o FormView) que fue poblado. Como todos los campos a excepcin de ProductID, ProductName y Discontinued pueden tener valores NULL, se pueden incluir parmetros y verificaciones adicionales para comparar correctamente los valores NULL en la clausula WHERE.

No vamos a agregar ningn DataTable adicional al DataSet con la concurrencia optimista habilitada en este tutorial, ya que nuestra pgina ASP.Net solo proporcionara informacin sobre la actualizacin y eliminacin de los productos. Sin embargo, aun necesitamos agregar el mtodo GetProductByProductID(productID) al TableAdapter ProductsOptimisticConcurrency. Para realizar esto, haga clic derecho sobre la barra del ttulo del TableAdapter (El rea encima de los nombres de los mtodos Fill y GetProducts) y seleccione agregar consulta desde el men contextual. Esto abrir al asistente de configuracin de la consulta del TableAdapter. Al igual que con la configuracin inicial del TableAdapter, seleccione crear el mtodo GetProductByProductID(productID) usando una sentencia SQL ad-hoc (Ver Figura 4). Como el mtodo GetProductByProductID(productID) devuelve la informacin de un producto en particular, indicamos que esta consulta es una sentencia de tipo SELECT que devuelve filas.

Figura 9. Marque el tipo de consulta como un SELECT que devuelva filas En la siguiente pantalla se nos solicita la consulta SQL a utilizar, con la consulta por defecto del TableAdapter ya cargada. Ampli la consulta existente para incluir la clausula WHERE ProductID=@ProductID, como se ve en la figura 10.

Figura 10. Agregue una clausula WHERE a la consulta para que devuelva un producto especifico Finalmente cambie los nombres de los mtodos generados a FillByProductID y GetProductByProductID.

Figura 11. Cambie el nombre de los mtodos a FillByProductID y GetProductByProductID. Con esto completamos el asistente, el TableAdapter ahora contiene dos mtodos para recuperar datos: GetProducts(), que devuelve todos los productos y GetProductByproductID(productID), que devuelve un producto especifico. Paso 3. Creacin de una capa lgica de negocios para la DAL con la concurrencia optimista habilitada

Nuestra clase ProductsBLL tiene ejemplos del uso de los enfoques de actualizacin por lotes y actualizacin directa de DB. Las recargas de los mtodos AddProduct y UpdateProduct usan el enfoque de actualizacin por lotes, pasando una instancia ProductRow al mtodo Update del TableAdapter. Por otro lado, el mtodo DeleteProducts usa el enfoque directo de DB, llamando al mtodo Delete(productID) del TableAdapter. Con el nuevo TableAdapter ProductsOptimisticConcurrency, ahora los mtodos directos de DB requieren que se pase los valores originales. Por ejemplo ahora el mtodo Delete espera diez parmetros de entrada: el ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder originales. Estos valores de los parmetros de entrada adicionales se usan en la clausula WHERE de la sentencia DELETE que es enviada a la base de datos, borrando solamente el registro especificado si los valores actuales de la base de datos coinciden con los de los originales. Aunque la definicin del mtodo para el mtodo Update del TableAdapter usado en el enfoque de actualizacin por lotes no ha cambiado, necesita el cdigo para grabar los valores nuevos y los originales. Por lo tanto, en lugar de intentar usar la DAL con la concurrencia optimista habilitada con nuestra clase BLL existente, crearemos una nueva capa lgica de negocios para que trabaje con nuestra nueva DAL. Agregue la clase llamada ProductsOptimisticConcurrencyBLL a la carpeta BLL dentro de la carpeta App_Code.

Figura 12. Agregue la clase ProductsOptimisticConcurrencyBLL a la carpeta BLL

Luego agregue el siguiente cdigo a la clase ProductsOptimisticConcurrencyBLL:


Imports NorthwindOptimisticConcurrencyTableAdapters <System.ComponentModel.DataObject()> _ Public Class ProductsOptimisticConcurrencyBLL Private _productsAdapter As ProductsOptimisticConcurrencyTableAdapter = Nothing Protected ReadOnly Property Adapter() As ProductsOptimisticConcurrencyTableAdapter Get If _productsAdapter Is Nothing Then _productsAdapter = New ProductsOptimisticConcurrencyTableAdapter() End If Return _productsAdapter End Get End Property <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, True)> _ Public Function GetProducts() As _ NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable Return Adapter.GetProducts() End Function End Class

Note que se usa la sentencia NorthwindOptimisticConcurrencyTableAdapters antes de comenzar la declaracin de la clase. El espacio de las el nombres clases atributo NorthwindOptimisticConcurrencyTableAdapters Adems antes de la declaracin de la contiene clase

ProductsOptimisticConcurrencyTableAdapter, que proporciona los mtodos de la DAL. encontrara System.ComponentModel.DataObject, que le indicara a Visual Studio incluir esta clase en la lista desplegable del asistente del ObjectDataSource. La propiedad Adapter de ProductsOptimisticConsurrencyTableAdapter proporciona un acceso rpido a una instancia de la clase ProductsOptimisticConcurrencyTableAdapter y continua con el enfoque usado en nuestras clases BLL originales (ProductsBLL, CategoriesBLL y as sucesivamente). Finalmente el mtodo GetProducts() llama al mtodo GetProducts() dentro de la DAL poblado y devuelve con una un objeto instancia ProductsOptimisticConcurrencyDataTable

ProductsOptimisticConcurrencyRow para cada uno de los registros en la base de datos.

Eliminacin de un producto usando el enfoque directo DB con concurrencia optimista Cuando usamos el enfoque directo DB en una DAL que utiliza concurrencia optimista, los mtodos deben pasar los valores originales y nuevos. Para la eliminacin no existen valores nuevos, por lo que solo se pasan los valores originales. En nuestra BLL, debemos todos los parmetros originales como parmetros de entrada. Haremos que el mtodo DeleteProduct en la clase ProductsOptimisticConcurrencyBLL utilice el mtodo directo DB. Esto significa que el mtodo necesita tomar los diez campos de datos de los productos como parmetros de entrada y pasarlos a la DAL, como se muestra en el siguiente cdigo:
<System.ComponentModel.DataObjectMethodAttribute _(System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteProduct( _ ByVal original_productID As Integer, ByVal original_productName As String, _ ByVal original_supplierID As Nullable(Of Integer), _ ByVal original_categoryID As Nullable(Of Integer), _ ByVal original_quantityPerUnit As String, _ ByVal original_unitPrice As Nullable(Of Decimal), _ ByVal original_unitsInStock As Nullable(Of Short), _ ByVal original_unitsOnOrder As Nullable(Of Short), _ ByVal original_reorderLevel As Nullable(Of Short), _ ByVal original_discontinued As Boolean) _ As Boolean Dim rowsAffected As Integer = Adapter.Delete( original_productID, _ original_productName, _ original_supplierID, _ original_categoryID, _ original_quantityPerUnit, _ original_unitPrice, _ original_unitsInStock, _ original_unitsOnOrder, _ original_reorderLevel, _ original_discontinued) ' Return true if precisely one row was deleted, otherwise false Return rowsAffected = 1 End Function

Si los valores originales, aquellos valores cargados que fueron cargados en el GridView (o DetailsView o FormView) difieren de los valores en la base de datos cuando el usuario presiona el botn Delete la clausula WHERE no coincide con ningn registro de la base de datos y ningn registro es afectado. Por lo tanto, el mtodo Delete del TableAdapter devolver 0 y el mtodo DeleteProduct de la BLL devolver false. Actualizar un producto usando el enfoque de actualizacin por lotes con concurrencia optimista Como notamos anteriormente el mtodo Update del TableAdapter para el enfoque de actualizacin por lotes tiene la misma definicin de mtodo, independientemente de si se emplea o no concurrencia optimista. Es decir, el mtodo Update espera un DataRow, un conjunto de DataRows, un DataTable o un DataSet tipiado. No hay parmetros de entrada adicionales para especificar los valores originales. Esto es posible porque el DataTable realiza un seguimiento de los valores originales y modificados para su DataRow. Cuando la DAL genera su sentencia UPDATE, los parmetros @original_ColumnName son poblados con los valores originales del DataRow, mientras que los parmetros @ColumnName son poblados con los valores modificados del DataRow. En la clase ProductsBLL (que usa nuestra DAL original sin concurrencia optimista), cuando usamos el enfoque de actualizacin por lotes para actualizar la informacin del producto, nuestro cdigo realiza la siguiente secuencia de eventos: 1. Lee la informacin del producto actual en la base de datos dentro de una instancia ProductRow usando el mtodo GetProductByProductID(productID) del TableAdapter. 2. Asigna los nuevos valores a la instancia ProductRow del paso 1. 3. Llama al mtodo Update del TableAdapter, pasndolo en la instancia ProductRow Sin embargo esta secuencia de pasos, podra no soportar correctamente la concurrencia optimista ya que el ProductRow poblado en el paso 1 es poblado directamente desde la base de datos, lo que significa que los valores originales usados por el DataRow son aquellos que existen actualmente en la base de datos, y no aquellos que son enlazados al GridView cuando inicia el proceso de edicin. En su lugar cuando usamos una DAL con la concurrencia optimista habilitada, necesitamos alterar la recarga del mtodo UpdateProduct para que use los siguientes pasos:

1. Leer la informacin actual del producto en la base de datos dentro de una instancia ProductsOptimisticConcurrencyRow usando el mtodo GetProductByProductID(productID) del TableAdapater. 2. Asignar los valores originales a la instancia ProductsOpimisticConcurrencyRow del paso 1. 3. Llamar al mtodo AcceptChanges de la instancia ProductsOptimisticConcurrencyRow, que indica al DataRow que sus valores actuales son los valores originales. 4. Asignar los nuevos valores a la instancia ProductsOptimisticConcurrencyRow 5. Llamar al mtodo Update del TableAdapter, pasndolo en la instancia ProductsOptimisticConcurrencyRow El paso 1 lee en todos los valores actuales de la base de datos para el registro del producto especificado. Este paso es superfluo en la recarga UpdateProduct que actualiza todas las columnas del producto (ya que estos valore se sobrescriben en el paso 2), pero es esencial para aquellas recargas donde solamente un subconjunto de los valores de las columnas son pasados como parmetros de entrada. Una vez que los valores originales han sido asignados a la instancia ProductsOptimisticConcurrencyRow, el mtodo AcceptChanges() es llamado, marcando los valores del DataRow actuales como los valores originales que sern usados en los parmetros @Original_ColumnName de la sentencia UPDATE. Luego, los nuevos valores de parmetros son asignados al ProductsOptimisticConcurrencyRow y finalmente el mtodo Update es invocado, pasndolo en el DataRow. El siguiente cdigo muestra la recarga UpdateProduct que acepta todos los campos de datos del producto como parmetros de entrada. Aunque no se muestra aqu, la clase ProductsOptimisticConcurrencyBLL incluida en la descarga de este tutorial tambin contiene una recarga UpdateProduct que acepta solamente el nombre del producto y su precio como los parmetros de entrada.
Protected Sub AssignAllProductValues( _ ByVal product As NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow, _ ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _ ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _ ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _ ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _

ByVal discontinued As Boolean) product.ProductName = productName If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull() Else product.QuantityPerUnit = quantityPerUnit End If If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If If Not unitsOnOrder.HasValue Then product.SetUnitsOnOrderNull() Else product.UnitsOnOrder = unitsOnOrder.Value End If If Not reorderLevel.HasValue Then product.SetReorderLevelNull() Else product.ReorderLevel = reorderLevel.Value End If product.Discontinued = discontinued End Sub <System.ComponentModel.DataObjectMethodAttribute( _

System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateProduct( ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _ ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _ ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _ ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _ ByVal discontinued As Boolean, ByVal productID As Integer, _ _ ByVal original_productName As String, _ ByVal original_supplierID As Nullable(Of Integer), _ ByVal original_categoryID As Nullable(Of Integer), _ ByVal original_quantityPerUnit As String, _ ByVal original_unitPrice As Nullable(Of Decimal), _ ByVal original_unitsInStock As Nullable(Of Short), _ ByVal original_unitsOnOrder As Nullable(Of Short), _ ByVal original_reorderLevel As Nullable(Of Short), _ ByVal original_discontinued As Boolean, _ ByVal original_productID As Integer) _ As Boolean 'STEP 1: Read in the current database product information Dim products As _ NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable = _ Adapter.GetProductByProductID(original_productID) If products.Count = 0 Then ' no matching record found, return false Return False End If Dim product As _ NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow = products(0) 'STEP 2: Assign the original values to the product instance AssignAllProductValues( _ product, original_productName, original_supplierID, _ original_categoryID, original_quantityPerUnit, original_unitPrice, _ original_unitsInStock, original_unitsOnOrder, original_reorderLevel, _ original_discontinued) 'STEP 3: Accept the changes product.AcceptChanges() 'STEP 4: Assign the new values to the product instance AssignAllProductValues( _ product, productName, supplierID, categoryID, quantityPerUnit, unitPrice, _

unitsInStock, unitsOnOrder, reorderLevel, discontinued) 'STEP 5: Update the product record Dim rowsAffected As Integer = Adapter.Update(product) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function

Paso 4. Pasar los valores originales y nuevos desde la pgina ASP.Net a los mtodos BLL Con la DAL y BLL completas, todo lo que resta es crear una pgina ASP.Net que pueda utilizar la lgica de concurrencia optimista creada en el sistema. Especficamente, el control de datos web (el GridView, DetailsView o FormView) debe recordar sus valores originales y el ObjectDataSource debe pasar ambos conjuntos de valores a la capa lgica de negocios. Por lo tanto la pgina ASP.Net debe ser configurada para manejar adecuadamente las violaciones de concurrencia. Comenzamos abriendo la pgina OptimisticConcurrency.aspx de la carpeta

EditInsertDelete y agregamos un GridView al diseador, estableciendo su propiedad ID en ProductsGrid. Desde la etiqueta inteligente del GridView, opte por crear un nuevo ObjectDataSource llamado ProductsOptimsiticConcurrencyDataSource. Como deseamos que este ObjectDataSource use la DAL que soporta la concurrencia optimista, lo configuramos para que utilice el objeto ProductsOptimisticConcurrencyBLL.

Figura 13, Haga que el ObjectDataSource utilice el objeto ProductsOptimisticConcurrencyBLL

Seleccione los mtodos Getproducts, UpdateProduct y DeleteProduct de las listas desplegables en el asistente. Para el mtodo UpdateProduct use la recarga que acepta todos los campos de datos de los productos. Configurar las propiedad del control ObjectDataSource Despus de completar el asistente, el marcado declarativo del ObjectDataSource lucir similar a:
<asp:ObjectDataSource ID="ProductsOptimisticConcurrencyDataSource" runat="server" DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsOptimisticConcurrencyBLL" UpdateMethod="UpdateProduct"> <DeleteParameters> <asp:Parameter Name="original_productID" Type="Int32" /> <asp:Parameter Name="original_productName" Type="String" /> <asp:Parameter Name="original_supplierID" Type="Int32" /> <asp:Parameter Name="original_categoryID" Type="Int32" /> <asp:Parameter Name="original_quantityPerUnit" Type="String" /> <asp:Parameter Name="original_unitPrice" Type="Decimal" /> <asp:Parameter Name="original_unitsInStock" Type="Int16" /> <asp:Parameter Name="original_unitsOnOrder" Type="Int16" /> <asp:Parameter Name="original_reorderLevel" Type="Int16" /> <asp:Parameter Name="original_discontinued" Type="Boolean" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> <asp:Parameter Name="productID" Type="Int32" /> <asp:Parameter Name="original_productName" Type="String" /> <asp:Parameter Name="original_supplierID" Type="Int32" /> <asp:Parameter Name="original_categoryID" Type="Int32" /> <asp:Parameter Name="original_quantityPerUnit" Type="String" />

<asp:Parameter Name="original_unitPrice" Type="Decimal" /> <asp:Parameter Name="original_unitsInStock" Type="Int16" /> <asp:Parameter Name="original_unitsOnOrder" Type="Int16" /> <asp:Parameter Name="original_reorderLevel" Type="Int16" /> <asp:Parameter Name="original_discontinued" Type="Boolean" /> <asp:Parameter Name="original_productID" Type="Int32" /> </UpdateParameters> </asp:ObjectDataSource>

Como podemos ver, la coleccin DeleteParameters contiene una instancia Parameter para cada uno de los diez parmetros de entrada en el mtodo DeleteProduct de la clase ProductsOptimisticConcurrencyBLL. De igual forma la coleccin UpdateParameters contiene una instancia Parameter para cada uno de los diez parmetros de entrada en UpdateProduct. Para los tutoriales anteriores que invocaban modificacin de datos, removimos la propiedad OldValuesParameterFormatString del ObjectDataSource en este punto, ya que esta propiedad indica que los mtodos BLL esperan que sean pasados los valores viejos (o originales) junto con los valores nuevos. Por lo tanto el valor de esta propiedad indica los nombres de los parmetros de entrada para los valores originales. Como estamos pasando los valores originales a la BLL, no removemos esta propiedad. Nota: El valor de la propiedad OldValuesParameterFormatString debe ser asignado a los nombres de los diez parmetros de entrada en la BLL que esperan los valores originales. Como llamamos as estos parmetros original_productName, de la propiedad original_supplierID y sucesivamente, dejaremos el valor

OldValuesParameterFormatString como original_{0}. Sin embargo si los parmetros de entrada de los mtodos BLL tienen nombres como old_productName, old_SupplierID y as sucesivamente, necesitamos actualizar la propiedad OldValuesParameterFormatString a old_{0}. Hay una definicin de propiedad final que debe realizarse con el fin de hacer que el ObjectDataSource pase correctamente los valores originales a los mtodos BLL. El ObjectDataSource tiene una propiedad ConflictDetection que puede asignarse a uno de los dos valores:

OverwriteChanges el valor por defecto, no enva los valores originales a los parmetros de entrada de los mtodos de la BLL. CompareAllValues enva los valores originales a los mtodos BLL, seleccionamos esta opcin cuando usamos concurrencia optimista.

Tmese

un

momento

para

establecer

la

propiedad

ConflictDetection

en

CompareAllValues. Configurar las propiedades y campos del GridView Con las propiedades del ObjectDataSource configuradas apropiadamente, pondremos nuestra atencin en la definicin del GridView. Primero como deseamos que el GridView soporte edicin y eliminacin, seleccione las casillas de verificacin Habilitar Edicin y Habilitar Eliminacin en la etiqueta inteligente del GridView. Esto agregara un CommandField cuyo ShowEditButton y ShowDeleteButton son establecidos en true. Cuando enlazamos al ObjectDataSource ProductsOptimisticConcurrencyDataSource, el GridView contiene un campo para cada uno de los campos de datos de los productos. Aunque el GridView puede ser editado, la experiencia del usuario es todo menos aceptable. Los BoundFields CategoryID y SupplierID son presentados como TextBoxes, lo que requiere que el usuario ingrese la categora y proveedor apropiados como nmeros ID. No hay un formato para los campos numricos y no hay controles de validacin que se aseguren que los nombres de los productos han sido suministrados y que los valores del UnitPrice, UnitsInStock, UnitsOnOrder y niveles de reorder son valores numricos apropiados y mayores o iguales que cero. Como discutimos en los tutoriales de Agregar controles de validacin a las Interfaces de edicin y eliminacin y Personalizacin de la interfaz de modificacin de datos, la interfaz de usuario puede ser personalizada remplazando los BoundFields con TemplateFields. Hemos modificado este GridView y su interfaz de edicin de las siguientes formas: Removimos los BoundFields ProductID, SupplierName y CategoryName Convertimos el BoundField ProductName a un ItemTemplate y agregamos un control RequiredFieldValidation. Convertimos los BoundFields CategoryID y SupplierID en TemplateFields y ajustamos la interfaz de edicin para que use DropDownLists en lugar de

TextBoxes. En estos ItemTemplates de los TemplateFields, se mostraran los campos de datos CategoryName y SupplierName. Convertimos los BoundFields UnitPrice, UnitsInStock, UnitsOnOrder y ReorderLevel en TemplateFields y agregamos controles CompareValidator. Como ya hemos examinados como realizar estas tareas en tutoriales anteriores, solo listamos la sintaxis declarativa final aqu y dejaremos la implementacin como practica.
<asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ProductsOptimisticConcurrencyDataSource" OnRowUpdated="ProductsGrid_RowUpdated"> <Columns> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" /> <asp:TemplateField HeaderText="Product" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="EditProductName" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" ControlToValidate="EditProductName" ErrorMessage="You must enter a product name." runat="server">*</asp:RequiredFieldValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Category" SortExpression="CategoryName"> <EditItemTemplate> <asp:DropDownList ID="EditCategoryID" runat="server" DataSourceID="CategoriesDataSource" AppendDataBoundItems="true" DataTextField="CategoryName" DataValueField="CategoryID" SelectedValue='<%# Bind("CategoryID") %>'> <asp:ListItem Value=">(None)</asp:ListItem> </asp:DropDownList><asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource> </EditItemTemplate>

<ItemTemplate> <asp:Label ID="Label2" runat="server" Text='<%# Bind("CategoryName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName"> <EditItemTemplate> <asp:DropDownList ID="EditSuppliersID" runat="server" DataSourceID="SuppliersDataSource" AppendDataBoundItems="true" DataTextField="CompanyName" DataValueField="SupplierID" SelectedValue='<%# Bind("SupplierID") %>'> <asp:ListItem Value=">(None)</asp:ListItem> </asp:DropDownList><asp:ObjectDataSource ID="SuppliersDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetSuppliers" TypeName="SuppliersBLL"> </asp:ObjectDataSource> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label3" runat="server" Text='<%# Bind("SupplierName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" /> <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice"> <EditItemTemplate> <asp:TextBox ID="EditUnitPrice" runat="server" Text='<%# Bind("UnitPrice", "{0:N2}") %>' Columns="8" /> <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="EditUnitPrice" ErrorMessage="Unit price must be a valid currency value without the currency symbol and must have a value greater than or equal to zero." Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label4" runat="server" Text='<%# Bind("UnitPrice", "{0:C}") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>

<asp:TemplateField HeaderText="Units In Stock" SortExpression="UnitsInStock"> <EditItemTemplate> <asp:TextBox ID="EditUnitsInStock" runat="server" Text='<%# Bind("UnitsInStock") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator2" runat="server" ControlToValidate="EditUnitsInStock" ErrorMessage="Units in stock must be a valid number greater than or equal to zero." Operator="GreaterThanEqual" Type="Integer" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label5" runat="server" Text='<%# Bind("UnitsInStock", "{0:N0}") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder"> <EditItemTemplate> <asp:TextBox ID="EditUnitsOnOrder" runat="server" Text='<%# Bind("UnitsOnOrder") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator3" runat="server" ControlToValidate="EditUnitsOnOrder" ErrorMessage="Units on order must be a valid numeric value greater than or equal to zero." Operator="GreaterThanEqual" Type="Integer" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label6" runat="server" Text='<%# Bind("UnitsOnOrder", "{0:N0}") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel"> <EditItemTemplate> <asp:TextBox ID="EditReorderLevel" runat="server" Text='<%# Bind("ReorderLevel") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator4" runat="server" ControlToValidate="EditReorderLevel" ErrorMessage="Reorder level must be a valid numeric value greater than or equal to zero."

Operator="GreaterThanEqual" Type="Integer" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label7" runat="server" Text='<%# Bind("ReorderLevel", "{0:N0}") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView>

Estamos cerca de tener nuestro ejemplo funcionando totalmente. Sin embargo, hay algunas sutilezas que aparecen y ocasionan problemas. Adicionalmente necesitamos agregar alguna interfaz que alerte al usuario cuando ocurra una violacin de concurrencia. Nota: Con el fin de que un control web de datos pase correctamente los valores originales al ObjectDataSource (que luego son pasados a la BLL) es vital que la propiedad EnableViewState se establezca en True (por defecto). Si deshabilita el ViewState, los valores originales se pierden en la devolucin de datos. Pasar los valores originales correctos al ObjectDataSource Hay un par de problemas con la forma en que ha sido configurado el GridView. Si la propiedad ConflictDetection del ObjectDataSource es establecida en CompareAllValues, cuando los mtodos Update() o Delete() del ObjectDataSource son invocados por el GridView (o DetailsView o FormView), el ObjectDataSource intenta copiar los valores originales del GridView en su instancia Parameter apropiada. Refirase de nuevo a la figura 2 para una representacin grafica de este proceso. Especficamente los valores originales del GridView son asignados a los valores en las sentencias de enlace de datos de dos vas cada vez que los datos son enlazados al GridView. Por lo tanto es esencial que los valores originales requeridos sean capturados a travs del enlace de datos de dos vas y que ellos sean proporcionados en un formato convertible.

Para ver porque esto es importante, tmese un momento para visitar nuestra pgina en un navegador. Como esperamos el GridView muestra cada producto con un botn Edit y Delete en la columna ms hacia la izquierda.

Figura 14. Los productos son listados en un GridView Si presiona el botn Delete para cualquier producto, se genera un FormatException.

Figura 15. Intentar eliminar cualquier producto genera un FormatException La FormatException es generada cuando el ObjectDataSource intenta leer el valor original UnitPrice. Como el ItemTemplate tiene el UnitPrice formateado como una moneda (<%#Bind(UnitPrice, {0:C})%>) este incluye un smbolo de moneda como

$19.95. El FormatException ocurre porque el ObjectDataSource intenta convertir esta cadena en un decimal. Para evitar este problema tenemos un nmero de opciones: Removemos el formato moneda del ItemTemplate. En lugar de usar

(<%#Bind(UnitPrice, {0:C})%>) simplemente usamos <%#Bind(UnitPrice)%>. El inconveniente de esto es que el precio no estar formateado. Mostrar el UnitPrice formateado como una moneda en el ItemTemplate, pero usar la palabra clave Eval para realizar esto. Recuerde que el Eval realiza un enlace de datos de una sola va. Aun necesitamos proporcionar el valor UnitPrice para los valores originales, por lo tanto necesitamos una sentencia de enlace de dos vas en el ItemTemplate, para esto puede ser colocado en un control web Label cuya propiedad Visible se establezca en False. Podemos usar el siguiente marcado en el ItemTemplate:
<ItemTemplate> <asp:Label ID="DummyUnitPrice" runat="server" Text='<%# Bind("UnitPrice") %>' Visible="false"></asp:Label> <asp:Label ID="Label4" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label> </ItemTemplate>

Remueve

el

formato

de

moneda

desde

el

ItemTemplate

usando

<%#Bind(UnitPrice)%>. En el controlador de evento RowDataBound del GridView, acceda mediante programacin al control web Label dentro del cual se muestra el valor UnitPrice y establezca su propiedad Text a la versin formateada. Deje el UnitPrice formateado como moneda. En el controlador de evento RowDeleting del GridView, remplace el valor UnitPrice original existente ($19.95) con un valor decimal actual usando Decimal.Parse. Vimos como realizar algo similar en el controlador de evento RowUpdating en el tutorial Manejo de excepciones a nivel de DAL y BLL en una pgina Asp.Net. Para mi ejemplo escogimos el segundo enfoque. Agregando un control web Label escondido, cuya propiedad Text esta enlazando datos bidireccionalmente a un valor UnitPrice no formateado.

Despus de resolver este problema, trate de dar clic nuevamente al botn Delete de cualquier producto. Esta vez tendremos un InvalidOperatorException cuando el ObjectDataSource intente invocar el mtodo UpdateProduct de la BLL.

Figura 16. El ObjectDataSource no puede encontrar un mtodo con los parmetros de entrada que desea enviar Viendo el mensaje de la excepcin, es claro que el ObjectDataSource desea invocar el mtodo DeleteProduct de y la BLL que incluye los Esto parmetros es debido de a entrada que los original_CategoryName original_SupplierName.

ItemTemplates de los TemplateFields CategoryID y SupplierID actualmente contienen sentencias de enlace de dos vas con los campos de datos CategoryName y SupplierName. En su lugar necesitamos incluir sentencias Eval con los campos de datos CategoryID y SupplierID. Para realizar esto, remplace las sentencias Bind existentes con sentencias Eval y luego agregue controles Label escondidos cuyas propiedades Text sean asignadas a los campos de datos CategoryID y SupplierID usando enlace de datos de dos vas, como se muestra a continuacin:
<asp:TemplateField HeaderText="Category" SortExpression="CategoryName"> <EditItemTemplate> ... </EditItemTemplate> <ItemTemplate> <asp:Label ID="DummyCategoryID" runat="server" Text='<%# Bind("CategoryID") %>' Visible="False"></asp:Label> <asp:Label ID="Label2" runat="server"

Text='<%# Eval("CategoryName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField><asp:TemplateField SortExpression="SupplierName"> <EditItemTemplate> ... </EditItemTemplate> <ItemTemplate> <asp:Label ID="DummySupplierID" runat="server" Text='<%# Bind("SupplierID") %>' Visible="False"></asp:Label> <asp:Label ID="Label3" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> HeaderText="Supplier"

Con estos cambios, somos capaces de eliminar y editar la informacin de los productos de forma exitosa. En el paso 5, veremos cmo verificar que violaciones de concurrencia estn siendo detectadas. Pero por ahora, tommonos unos minutos para tratar de actualizar y eliminar unos pocos registros y asegurarnos que la edicin y eliminacin para un solo usuario trabaja como esperamos. Paso 5. Probar el soporte de concurrencia optimista Con el fin de verificar que violaciones de concurrencia estn siendo detectadas (en lugar que los datos sean sobrescritos ciegamente), necesitamos abrir dos ventanas del navegador para esta pgina. En ambas estancias del navegador, presionamos el botn Edit para Chai. Luego en uno solo de los navegadores, cambiamos el nombre a Chai Tea y damos clic en actualizar. La actualizacin debe suceder y retornar el GridView a su estado previo a la edicin, con Chai Tea como el nuevo nombre del producto. Sin embargo en la otra instancia de ventana de navegador, el TextBox del nombre del producto aun muestra Chai. En esta segunda ventana, actualice el UnitPrice a 25.00. Sin soporte de concurrencia optimista, al presionar actualizar en la segunda instancia del navegador, cambiaremos el nombre del producto de nuevo a Chai, por lo tanto sobrescribimos los cambios hechos por la primera instancia del navegador. Sin embargo empleando la concurrencia optimista, al presionar el botn de actualizar en el segundo navegador, se origina una DBConcurrencyException.

Figura 17. Cuando una violacin de concurrencia es detectada, se genera una DBConcurrencyException La DBConcurrencyException es solamente generada cuando el enfoque de actualizacin por lotes de la DAL es utilizado. El enfoque directo DB no genera una excepcin, este solamente indica que ninguna fila fue afectada. Para ilustrar esto, regresamos el GridView de las instancias del navegador a su estado previo a la edicin. Luego en la primera instancia de navegador, presione el botn Edit y cambie el nombre del producto de Chai Tea de nuevo a Chai y presione actualizar. En la segunda ventana navegador, presione el botn Delete para Chai. Una vez presiona Delete, la pagina se publica de nuevo, el GridView invoca el mtodo Delete() del ObjectDataSource y luego el ObjectDataSource llama al mtodo DeleteProduct de la clase ProductsOptimisticConcurrencyBLL, pasando tambin los valores originales. El valor original ProductName para la segunda instancia del navegador es Chai Tea, que no coincide con el valor actual ProductName en la base de datos. Por lo tanto la sentencia DELETE emitida a la base de datos no afecta ninguna fila, ya que no hay un registro en la base de datos que satisfaga la clausula WHERE. El mtodo DeleteProduct devuelve False y los datos del ObjectDataSource son enlazados nuevamente al GridView. Desde la perspectiva final del usuario, al presionar el botn Delete para Chai Tea en la segunda ventana del navegador, la pantalla se actualiza y el producto aun esta listado, aunque ahora es mostrado como Chai (el nombre del producto cambia por el cambio en la primera instancia del navegador). Si el usuario presiona el botn Delete de nuevo, la eliminacin es exitosa, ya que el valor original ProductName del GridView (Chai) ahora coincide con el valor en la base de datos.

En ambos casos, la experiencia del usuario est lejos de ser la ideal. Claramente no deseamos mostrar al usuario detalles esenciales de la excepcin DBConcurrencyException cuando usamos el enfoque de actualizacin por lotes y el comportamiento cuando empleamos el enfoque directo DB es algo confuso para los usuarios cuando el comando falla, ya que no hay una indicacin precisa del por qu. Para remediar estos dos inconvenientes, creamos controles web Label en la pagina para proporcionar una explicacin del porque la actualizacin o eliminacin ha fallado. Para el enfoque de actualizacin por lotes, podemos determinar si ocurri o no una excepcin DBConcurrencyException en el controlador de eventos posteriores a nivel del GridView, mostrando el mensaje de advertencia necesario. Para el mtodo directo DB, podemos examinar el valor devuelto del mtodo BLL (el cual es True si una fila fue afectada o false en caso contrario) y mostrar un mensaje informativo de ser necesario. Paso 6. Agregar mensajes de informacin y mostrarlo cuando ocurra una violacin de concurrencia Cuando una violacin de concurrencia ocurre, el comportamiento exhibido depende de si la DAL uso el enfoque de actualizacin por lotes o el enfoque directo DB. Nuestro tutorial utiliza ambos enfoques; con el enfoque de actualizacin por lotes usado para actualizar y el enfoque de actualizacin directa DB usado para eliminacin. Comenzaremos, agregando dos controles web Label a nuestra pgina para explicar que una violacin de concurrencia ocurri cuando intentamos eliminar o actualizar datos. Establezca las propiedades Visible y EnableViewState del control Label en Falso, esto hace que estn ocultos en cada visita a la pagina excepto al visitar una pgina en particular donde su propiedad Visible este definida mediante programacin a True.
<asp:Label ID="DeleteConflictMessage" runat="server" Visible="False" EnableViewState="False" CssClass="Warning" Text="The record you attempted to delete has been modified by another user since you last visited this page. Your delete was cancelled to allow you to review the other user's changes and determine if you want to continue deleting this record." /> <asp:Label ID="UpdateConflictMessage" runat="server" Visible="False" EnableViewState="False" CssClass="Warning" Text="The record you attempted to update has been modified by another user since you started the update process. Your changes have been replaced with the current values. Please review the existing values and make

any needed changes." />

Adicionalmente defina sus propiedades Visible, EnableViewState y Text. Tambin establezca la propiedad CssClass en Warning, lo cual mostrara la fuente del Label grande, roja, itlica y con negrita. Esta clase Warning fue definida y agregada al Styles.css anteriormente en el tutorial Examen de los eventos asociados con la insercin, actualizacin y eliminacin. Despus de agregar estos Labels, el diseador en Visual Studio lucir similar a la Figura 18.

Figura 18. Dos controles web Label se agregan a la pgina Con este control web Label en su lugar, estamos listos para examinar como determinar cundo una violacin de concurrencia ha ocurrido, en qu punto la propiedad Visible del Label apropiado debe establecerse en True, mostrando el mensaje de informacin. Manejo de violaciones de concurrencia cuando actualizamos Veremos primero como manejar las violaciones de concurrencia cuando utilizamos el enfoque de actualizacin por lotes. Ya que las violaciones en el enfoque de actualizacin de lotes originan que se genere una excepcin DBConcurrencyException,

necesitamos agregar cdigo a nuestra pgina ASP.Net para determinar si una excepcin DBConcurrencyException ocurri durante el proceso de actualizacin. Si es as, debemos mostrar un mensaje al usuario explicando que sus cambios no pueden ser guardados porque otro usuario modifico los mismos datos entre que inicio la edicin del registro y cuando presiono el botn Update. Como vimos en el tutorial Manejo de excepciones a nivel de DAL y BLL en una pgina ASP.Net, estas excepciones pueden ser detectadas y suprimidas en los controladores de eventos posteriores a nivel de control web de datos. Por lo tanto necesitamos crear un controlador de evento para el evento RowUpdated del GridView que verifique si una excepcin DBConcurrencyException ha sido generada. El controlador de evento pasa una referencia a cualquier excepcin que haya sido generada durante el proceso de actualizacin, como se muestra en el cdigo del controlador de evento a continuacin:
Protected Sub ProductsGrid_RowUpdated _ (ByVal sender As Object, ByVal e As GridViewUpdatedEventArgs) _ Handles ProductsGrid.RowUpdated If e.Exception IsNot Nothing AndAlso e.Exception.InnerException IsNot Nothing Then If TypeOf e.Exception.InnerException Is System.Data.DBConcurrencyException Then ' Display the warning message and note that the exception has ' been handled... UpdateConflictMessage.Visible = True e.ExceptionHandled = True End If End If End Sub

En vista de una excepcin DBConcurrencyException, este controlador de evento muestra el control Label UpdateConflictMessage e indica que la excepcin ha sido manejada. Con este cdigo en su lugar, cuando una violacin de concurrencia ocurre cuando actualizamos un registro, los cambios del usuario se pierden, ya que ellos pueden haber sido sobrescritos por las modificaciones de otro usuario al mismo tiempo. En concreto el GridView es devuelto a su estado previo a la edicin y enlazado a los datos actuales de la base de datos. Esto actualizara la fila del GridView con los cambios del otro usuario, que no fueron visibles previamente. Adicionalmente el control Label UpdateConflictMessage explicara al usuario lo que ocurri. Esta secuencia de eventos es detallada en la figura 19.

Figura 19. Las actualizaciones de un usuario se pierden en vista de una violacin de concurrencia Nota: Alternativamente en lugar de devolver el GridView al estado previo a la edicin, podemos dejar el GridView en su estado de edicin, estableciendo la propiedad KeepInEditMode del objeto pasado en GridViewUpdatedEventArgs en True. Sin embargo, si tomamos este enfoque, debemos asegurarnos de enlazar nuevamente los datos al GridView (invocando su mtodo DataBind()) para que los otros valores de los usuarios sean cargados en la interfaz de edicin. El cdigo disponible para descargar con este tutorial tiene esas dos lneas comentadas de cdigo en el controlador de evento RowUpdated, sencillamente descomente estas lneas de cdigo para que el GridView permanezca en modo de edicin despus de una violacin de concurrencia. Responder a las violaciones de concurrencia cuando eliminamos

Con el enfoque directo DB, no hay una excepcin generada en vista de una violacin de concurrencia. En su lugar, la sentencia de la base de datos simplemente no afecta a ningn registro, ya que la clausula WHERE no coincide con ningn registro. Todos los mtodos de modificacin de datos creados en la BLL han sido diseados para que devuelvan un valor booleano indicando si hay o no un registro afectado. Por lo tanto para determinar, si una violacin de concurrencia ocurri cuando eliminamos un registro, podemos examinar el valor devuelto por el mtodo DeleteProduct de la BLL. El valor devuelto por un mtodo puede ser examinado en los controladores de eventos posteriores a nivel del ObjectDataSource por medio de la propiedad ReturnValue del objeto ObjectDataSourceStatusEventArgs pasado en el controlador de evento. Como estamos interesados en determinar el valor devuelto desde el mtodo DeleteProduct, necesitamos crear un controlador de evento para el evento Deleted del ObjectDataSource. La propiedad ReturnValue es de tipo object y puede ser Null si una excepcin fue generada y el mtodo fue interrumpido antes de poder devolver un valor. Por lo tanto, debemos primero asegurarnos que la propiedad ReturnValue no es Null y es un valor booleano. Asumiendo que pasamos esta verificacin, mostramos el control Label DeleteConflictMessage si el ReturnValue es falso. Esto puede realizarse usando el siguiente cdigo:
Protected Sub ProductsOptimisticConcurrencyDataSource_Deleted _ (ByVal sender As Object, ByVal e As ObjectDataSourceStatusEventArgs) _ Handles ProductsOptimisticConcurrencyDataSource.Deleted If e.ReturnValue IsNot Nothing AndAlso TypeOf e.ReturnValue Is Boolean Then Dim deleteReturnValue As Boolean = CType(e.ReturnValue, Boolean) If deleteReturnValue = False Then ' No row was deleted, display the warning message DeleteConflictMessage.Visible = True End If End If End Sub

En vista de una violacin de concurrencia, la solicitud de eliminacin de un usuario es cancelada. El GridView es actualizado, mostrando los cambios que ocurrieron en el registro entre el tiempo en que el usuario cargo la pgina hasta cuando presiono el botn Delete. Con cada violacin que se genere, se muestra el Label DeleteConflictMessage, explicando lo que sucedi (Ver Figura 20).

Figura 20. La eliminacin de un usuario es cancelada en vista de una violacin de concurrencia Resumen Las oportunidades para violaciones de concurrencia existen en cada aplicacin que permita diversos usuarios concurrentes que actualicen o eliminen datos. Si estn violaciones no son tomadas en cuenta, cuando dos usuarios actualicen simultneamente los mismos datos, aquel que realice los ltimos cambios gana, sobrescribiendo los cambios hechos por el otro usuario. De forma alternativa los desarrolladores pueden implementar controles de concurrencia optimista o pesimista. El control de concurrencia optimista asume que las violaciones de concurrencia no son frecuentes y simplemente deshabilita el comando de actualizacin o eliminacin que constituir un violacin de concurrencia. El control de concurrencia pesimista asume que las violaciones de concurrencia son frecuentes y simplemente rechaza no aceptando un comando de actualizacin o eliminacin de un usuario. Con el control de concurrencia pesimista , actualizar un registro involucra asegurarlo, previendo as que otros usuarios modifiquen o eliminen el registro mientras este asegurado. El DataSet tipiado en .Net proporciona funcionalidad para el soporte del control de concurrencia optimista. Particularmente, las sentencias UPDATE y DELETE usadas en la base de datos incluyen todas las columnas de las tablas, por lo tanto aseguramos que la adicin o eliminacin ocurra solamente si los datos actuales del registro coinciden

con los datos originales que el usuario tena cuando realizo la actualizacin o eliminacin. Una vez que la DAL ha sido configurada para soportar concurrencia optimista, los mtodos de la BLL deben ser actualizados. Adems la pgina ASP.Net que llama a la BLL debe ser configurada para que el ObjectDataSource recupere los valores originales desde su control de datos web y luego los pase dentro de la BLL. Como vimos en este tutorial, la implementacin del control de concurrencia optimista en una aplicacin web ASP.Net involucra la actualizacin de la DAL y BLL y la adicin del soporte en la pgina ASP.Net. Si es o no este trabajo adicional una inversin inteligente de nuestro tiempo y esfuerzo depende de nuestra aplicacin. Si es poco frecuente que los usuarios concurrentes actualicen datos o los datos que actualizan son diferentes unos de los otros, entonces el control de concurrencia no es una cuestin clave. Sin embargo si rutinariamente tenemos mltiples usuarios trabajando en nuestro sitio web con los mismos datos, el control de concurrencia ayuda a prevenir que las actualizaciones o eliminaciones de un usuario sean sobrescritas con los cambios de otro usuario.

22.

ADICION DE CONFIRMACION DE ELIMINACION DEL LADO DEL

CLIENTE
En las interfaces que hemos creado hasta el momento, un usuario puede accidentalmente eliminar datos presionando el botn Delete cuando en realidad tena la intencin de presionar el botn Edit. En este tutorial agregaremos un cuadro de dialogo de confirmacin del lado del cliente, que aparece cuando el botn Delete es presionado. Introduccin En la gran mayora de tutoriales anteriores vimos como usar nuestra arquitectura de aplicacin, un ObjectDataSource y los controles web de datos para proporcionar capacidades de insercin, edicin y eliminacin. Las interfaces de eliminacin que examinamos anteriormente estaban compuestas de un botn Delete, que cuando era presionado generaba una devolucin de datos e invocaba el mtodo Delete() del ObjectDataSource. Luego el mtodo Delete() invoca el mtodo configurado desde la capa lgica de negocios, que propagaba su llamada dentro de la capa de acceso a datos, emitiendo la sentencia DELETE actual a la base de datos. Aunque esta interfaz de usuario permite a los visitantes eliminar registros por medio de los controles GridView, FormView y DetailsView, esta carece de cualquier orden de confirmacin cuando el usuario presiona el botn Delete. Si un usuario accidentalmente presiona el botn Delete cuando realmente tena la intencin de presionar Edit, el registro en lugar de editarse ser eliminado. Para prevenir esto, agregaremos en este tutorial una cuadro de dialogo de confirmacin del lado del cliente que aparece cuando el botn Delete es presionado. La funcin JavaScript confirm(string) muestra su cadena como un parmetro de entrada como el texto dentro de un cuadro de dialogo modal que viene equipado con dos funciones OK y Cancel (Ver Figura 1). La funcin confirm(string) devuelve un valor booleano dependiendo de qu botn fue presionado (True, si el usuario presiono OK, y False si el usuario presiono Cancel).

Figura 1. El mtodo JavaScript confirm(string) muestra un cuadro de mensaje modal en el lado del cliente Durante el envo de un formulario, si un valor False es recuperado desde el controlador de evento del lado cliente, entonces el envo del formulario es cancelado. Usando esta funcin, podemos hacer que el controlador de evento onclick del botn Delete en el lado del cliente recupere el valor de la llamada a confirm(Are yo usure to delete this product?). Si el usuario hace clic en cancelar, confirm(string) devuelve false, por lo tanto el envo del formulario se cancela. Sin devoluciones de datos, el producto cuyo botn fue presionado no ser borrado. Sin embargo, si el usuario presiona OK en el cuadro de dialogo de confirmacin, la devolucin de datos continuara su curso y el producto ser eliminado. Consulte Uso del mtodo confirm() de JavaScript para controlar los envos del formulario para mayor informacin sobre esta tcnica. Agregar el script necesario en el lado del cliente difiere ligeramente si usamos plantillas que cuando usamos un CommandField. Por lo tanto en este tutorial observaremos un ejemplo con un FormView y GridView. Nota: Usando tcnicas de confirmacin del lado del cliente como las que discutimos en este tutorial, se asume que el usuario est visitando desde un navegador que soporta JavaScript y que tiene el JavaScript habilitado. Si estas suposiciones no son validas para un usuario en particular, presionando el botn Delete inmediatamente se origina una devolucin de datos (no se muestra el cuadro de mensaje de confirmacin). Paso 1. Crear un FormView que soporte Eliminacin Comenzamos agregando un FormView a la pgina ConfirmationOnDelete.aspx de la carpeta EditInsertDelete y lo enlazamos a un nuevo ObjectDataSource que recupere la informacin de los productos por medio del mtodo GetProducts() de la clase ProductsBLL. Tambin configuramos el ObjectDataSource para que el mtodo DeleteProduct(productId) de la clase ProductsBLL sea asignado al mtodo Delete() del ObjectDataSource; asegurndonos que las listas desplegables de las pestaas INSERT y

UPDATE se establezcan en Ninguno. Finalmente, seleccione la casilla de verificacin Habilitar Paginacin en la etiqueta inteligente del FormView. Despus de estos pasos, el marcado declarativo del nuevo ObjectDataSource lucir similar al siguiente:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL"> <DeleteParameters> <asp:Parameter Name="productID" Type="Int32" /> </DeleteParameters> </asp:ObjectDataSource>

Al igual que en los tutoriales anteriores que no usaron concurrencia optimista, tmese un momento para limpiar la propiedad OldValuesParameterFormatString del ObjectDataSource. Como este ha sido enlazado a un control ObjectDataSource que solamente soporta la eliminacin, el ItemTemplate del FormView ofrece solamente el botn Delete, careciendo de los botones New y Update. Sin embargo, el marcado declarativo del FormView ofrece unas superfluas EditItemTemplate e InsertItemTemplate que pueden ser removidas. Tmese un momento para personalizar e ItemTemplate para que este muestre solamente un subconjunto de los campos de datos de los productos. Configuramos para que muestre el nombre del producto en un encabezado <h3> encima de los nombres de categora y proveedor (junto con un botn Delete)
<asp:FormView ID="FormView1" AllowPaging="True" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" runat="server"> <ItemTemplate> <h3><i><%# Eval("ProductName") %></i></h3> <b>Category:</b> <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>'> </asp:Label><br /> <b>Supplier:</b> <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>'>

</asp:Label><br /> <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete"> </asp:LinkButton> </ItemTemplate> </asp:FormView>

Con estos cambios, tenemos un pagina web completamente funcional que permite al usuario ver los productos uno a la vez, con la habilidad de eliminar un productos sencillamente presionando el botn Delete. La figura 2 muestra una captura de pantalla de nuestro progreso hasta el momento visto por medio de un navegador.

Figura 2. El FormView muestra la informacin de un solo producto Paso 2. Llamar a la funcin confirm(string) desde el evento onclick de los botones Delete en el lado del servidor. Con el FormView creado, el paso final es configurar el botn Delete para que cuando es presionado por el visitante, la funcin JavaScript confirm(string) sea invocada. Agregar un script en el lado del cliente a un Button, LinkButton o ImageButton en el evento onclick del lado del cliente, puede realizarse usando la propiedad OnClientClick, que es nueva en ASP.Net 2.0. Como deseamos hacer que el valor de la funcin confirm(string) sea recuperado, sencillamente establecemos esta propiedad en: return confirm(Are you certain that you want delete this product?); Despus de este cambio el marcado declarativo del LinkButton Delete lucir como algo as:

<asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete" OnClientClick="return confirm('Are you certain you want to delete this product?');"> </asp:LinkButton>

Esto es todo lo que hay que hacer! La figura 3 muestra una captura de pantalla de esta confirmacin es accin. Presionando el botn Delete abrimos el cuadro de dialogo de confirmacin. Si el usuario hace clic en Cancel, la devolucin de datos es cancelada y el producto no es eliminado. Sin embargo, si el usuario hace clic en OK, la devolucin de datos continua y el mtodo Delete() del ObjectDataSource es invocado, que culmina en que el registro de la base de datos es eliminado. Nota: La cadena pasada en la funcin JavaScript confirm(string) es delimitada con apostrofes (en lugar de comillas). En JavaScript, las cadenas pueden ser delimitadas usando cualquier carcter. Usamos apostrofes aqu para que los delimitadores para la cadena pasados en confirm(string) no introduzcan una ambigedad con delimitadores usado por el valor de la propiedad OnClientClick.

Figura 3. Ahora se muestra una confirmacin cuando el botn Delete es presionado Paso 3. Configurar la propiedad OnClientClick para el botn Delete en un CommandField Cuando trabajamos con un Button, LinkButton, o ImageButton directamente en una plantilla, un cuadro de dialogo de confirmacin puede ser asociado con este sencillamente configurando su propiedad OnClientClick para recuperar los resultados de la funcin JavaScript confirm(string). Sin embargo, el CommandField, que agrega un campo de botones Delete al GridView o DetailsView, no tiene una propiedad OnClientClick que pueda ser definida mediante declaracin. En su lugar, debemos

referenciar mediante programacin el botn Delete en el GridView o DetailsView en el controlador del evento DataBound apropiado y luego establecer su propiedad OnClientClick all. Nota: Cuando establecemos la propiedad OnClientClick del botn Delete en el controlador de evento DataBound apropiado, tenemos que acceder a los datos enlazados al registro actual. Esto significa que puede ampliar el mensaje de confirmacin para incluir detalles de un registro en particular, como Are you sure you want to delete the Chai product. Esta personalizacin tambin es posible en plantillas usando la sintaxis de enlace de datos. Para practicar la definicin de la propiedad OnClientClick del botn Delete en un CommandField, agregaremos un GridView a la pgina. Configure este GridView para que use el mismo control ObjectDataSource que utilizo el control FormView. Tambin limite los BoundFields del GridView para solamente incluir el nombre del producto, categora y proveedor. Por ltimo selecciona la casilla de verificacin Habilitar Edicin en la etiqueta inteligente del GridView. Agregaremos un CommandField a la coleccin Columns del GridView con su propiedad ShowDeleteButton establecida en True. Despus de hacer estos cambios, el marcado declarativo del GridView lucir similar al siguiente:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" /> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> </Columns> </asp:GridView>

El CommandField contiene una sola instancia LinkButton Delete a la cual puede accederse mediante programacin desde el controlador de evento RowDataBound del

GridView. Una vez referenciado, podemos establecer su propiedad OnClientClick acordemente. Crear un controlador de evento para el evento RowDataBound usando el siguiente cdigo:
Protected Sub GridView1_RowDataBound(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _ Handles GridView1.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then ' reference the Delete LinkButton Dim db As LinkButton = CType(e.Row.Cells(0).Controls(0), LinkButton) ' Get information about the product bound to the row Dim product As Northwind.ProductsRow = _ CType(CType(e.Row.DataItem, System.Data.DataRowView).Row, _ Northwind.ProductsRow) db.OnClientClick = String.Format( _ "return confirm('Are you certain you want to delete the {0} product?');", _ product.ProductName.Replace("'", "\'")) End If End Sub

Este controlador de evento funciona con filas de datos (aquellas que tendrn el botn Delete) y comienza referenciando mediante programacin el botn Delete. En general usa el siguiente enfoque:
Dim obj As ButtonType = _ CType(e.Row.Cells(commandFieldIndex).Controls(controlIndex), ButtonType)

ButtonType es el tipo de botn que est siendo usado por el CommandField, Button, LinkButton o ImageButton. Por defecto el CommandField usa LinkButton, pero esto puede ser personalizado por medio de la propiedad ButtonType del CommandField. El CommandFieldIndex es el ndice ordinario del CommandField dentro de la coleccin Columns del GridView, mientras que el controlIndex es el ndice del botn Delete dentro de la coleccin Columns del CommandField. El valor del controlIndex depende de la posicin relativa del botn con respecto a los otros botones en el CommandField. Por ejemplo si el nico botn mostrado en el CommandField es el botn Delete, usamos un ndice de 0. Sin embargo si hay un botn Edit que precede el Botn Delete usamos un ndice de 2. La razn de usar un ndice de 2 es porque dos controles son

agregados al CommandField antes del botn Delete: El botn Edit y un control Literal que usado para agregar algn espacio entre los botones Edit y Delete. Para nuestro ejemplo en particular, el CommandField usa LinkButtons y siendo el campo ms a la izquierda tiene un commandFieldIndex de 0. Ya que no hay otros botones que el botn Delete en el CommandField, usaremos un controlIndex de 0. Despus de referenciar el botn Delete en el CommandField, luego grabamos la informacin del producto enlazada a la fila actual del GridView. Finalmente establecemos la propiedad OnClientClick del botn Delete al JavaScript apropiado, que incluye el nombre del Producto. Como la cadena JavaScript pasada en la funcin confirm(string) es delimitada usando apostrofes, debemos evitar que aparezca cualquier apostrofe dentro del nombre del producto. En particular, cualquier apostrofe en el nombre del producto ser evitado con \ . Con estos cambios completos, presionando un botn Delete en el GridView mostramos un cuadro de dialogo de confirmacin personalizado (ver figura 4). Al igual que con el cuadro de confirmacin del FormView, si el usuario presiona Cancel la devolucin de datos es cancelada, por lo tanto previene que ocurra la eliminacin. Nota: Esta tcnica tambin puede ser usada para accede mediante programacin al botn Delete en el CommandField en un DetailsView. Sin embargo para el DetailsView, crearemos un controlador de evento para el evento DataBound, ya que el DetailsView no tiene un evento RowDataBound.

Figura 4. Presionando los botones delete del GridView mostramos un cuadro de dialogo de confirmacin personalizado Usando TemplateFields Una de las desventajas del CommandField es que sus botones deben ser accedidos por medio de indexacin y que el objeto resultante debe ser emitido a un tipo de botn apropiado (Button, LinkButton o ImageButton). Usando nmeros mgicos y tipos fuertemente codificados hacemos que los problemas no puedan ser descubiertos hasta tiempo de ejecucin. Por ejemplo, si usted u otro desarrollador, agrega nuevos botones al CommandField en algn momento en el futuro (como un botn Edit) o cambia la propiedad ButtonType, el cdigo existente continuara compilando si errores, pero al visitar la pgina originaremos una excepcin o comportamiento inesperado, dependiendo de cmo fue escrito nuestro cdigo y que cambios fueron hechos. Un enfoque alternativo es convertir los CommandFields del GridView y DetailsView en TemplateFields. Esto generara un TemplateField con un ItemTemplate que tiene un LinkButton (o Button o ImageButton) para cada button en el CommandField. Las propiedades OnClientClick de estos botones puede ser asignada mediante declaracin, como vimos con el FormView, o mediante acceder a ellas mediante programacin en el controlador de evento DataBound apropiado usando el siguiente enfoque:
Dim obj As ButtonType = CType(e.Row.FindControl("controlID"), ButtonType)

Donde controlID es el valor de la propiedad ID del botn. Aunque este enfoque aun requiere un tipo no modificable para emitir, este remueve la necesidad de indexacin, permitiendo cambiar el diseo sin originar un error en tiempo de ejecucin. Resumen La funcin JavaScript confirm(string) es una tcnica comnmente usada para controlar el flujo de trabajo de la presentacin de formularios. Cuando se ejecuta la funcin muestra una cuadro de dialogo modal de dos botones OK y Cancel en el lado del cliente. Si el usuario presiona OK, la funcin confirm(string) devuelve true; presionando Cancel devuelve false. Esta funcionalidad, acoplada con el comportamiento del navegador para cancelar un envo de formulario si un proceso del controlador de evento durante la presentacin devuelve false, puede ser usado para mostrar un cuadro de mensaje de confirmacin cuando eliminamos un registro.

La funcin confirm(string) puede estar asociada con el controlador de evento onclick de un control web Button en el lado del cliente por medio de la propiedad OnClientClick del control. Cuando trabajamos con un botn Delete en una plantilla, ya sea en una plantilla del FormView o en un TemplateField en el DetailsView o GridView; su propiedad puede ser definida mediante declaracin o programacin, como vimos en este tutorial.

23.

LIMITACIN DE LA FUNCION DE MODIFICACION DE DATOS BASADOS

EN EL USUARIO
En una aplicacin web se permite a los usuarios editar los datos; diferentes cuentas de usuario pueden tener diferentes privilegios de edicin de datos. En este tutorial vamos a examinar como ajustar dinmicamente la capacidad de modificacin de datos basados en el usuario visitante. Introduccin Una serie de aplicaciones web soportan cuentas de usuario y proporcionan diferentes opciones, informes y funcionalidades basados en el usuario actualmente conectado. Por ejemplo para nuestros tutoriales podramos desear permitir a los usuarios de las empresas proveedoras que se conecten al sitio y actualicen la informacin de sus productos, tal vez el nombre y la cantidad por unidad junto con la informacin del proveedor como el nombre de la empresa, direccin, informacin de la persona de contacto y as sucesivamente. Adicionalmente podramos desear incluir algunas cuentas de usuario para la gente de nuestra empresa, de modo que puedan iniciar sesin y actualizar informacin de los productos tales como Unidades de existencia, nivel de pedidos, etc. Nuestra aplicacin web tambin puede permitir la visita de usuarios annimos (que no han iniciado sesin) pero limitando los datos que ellos pueden ver. En lugar de un sistema de cuentas de usuario, en su lugar nos gustara tener controles web en nuestras pginas Asp.Net para que ofrezcan la capacidad de edicin, insercin y actualizacin apropiada para el usuario actualmente conectado. En este tutorial examinaremos la forma de ajustar dinmicamente la modificacin de datos basados en el usuario actualmente conectado. En particular crearemos una pgina que muestre la informacin de los proveedores en un DetailsView editable junto con un GridView que muestre los productos suministrados por el proveedor. Si el usuario que visita la pgina es de nuestra empresa, puede: ver informacin de cualquier proveedor, editar su direccin y editar la informacin de cualquier producto suministrado por el proveedor. Sin embargo, si el usuario es de una empresa en particular, solo puede ver y editar su propia informacin de direccin y editar solamente sus productos siempre y cuando no hayan sido marcados como suspendidos.

Figura 1. Un usuario de nuestra compaa puede editar la informacin de cualquier proveedor

Figura 2. Un usuario de un proveedor en particular solo puede visualizar y editar su informacin Nota: El sistema de membreca de ASP.Net 2.0 proporciona una plataforma estandarizada y extensible para crear, administrar y validar las cuentas de usuario. Como el examen del sistema de membreca esta fuera del alcance de estos tutoriales, en su lugar este tutorial imitaremos la membreca permitiendo visitantes annimos

para seleccionar si son de un proveedor en particular o de nuestra compaa. Para mayor informacin refirase a la serie de artculos Examen de perfiles, membreca y roles en ASP.Net 2.0. Paso 1. Permitir al usuario especificar sus derechos de acceso En una aplicacin web del mundo real, la informacin de una cuenta de usuario debe incluirse si trabaja para nuestra compaa o para un proveedor en particular y esta informacin debe ser accesible mediante programacin desde nuestras pginas ASP.Net una vez que el usuario se ha registrado al sitio. Esta informacin puede ser capturada por medio del sistema de roles de ASP.Net 2.0, como la informacin del nivel de cuenta de un usuario por medio del sistema de perfiles, o por medio de algunos medios personalizados. Puesto que el objetivo de este tutorial es demostrar como ajustar las capacidades de modificacin de datos basados en el usuario registrado y no est pensado para demostrar los sistemas de perfiles, roles y membreca de ASP.Net 2.0, usaremos un mecanismo muy sencillo para determinar las capacidades para el usuario que esta visitado la pagina; un DropDownList en el cual el usuario puede indicar si ellos debe ser capaces de visualizar y editar cualquier informacin de los proveedores o de forma alternativa que solo pueden editar y visualizar la informacin de un proveedor en particular. Si el usuario indica que puede ver y editar la informacin de todos los proveedores (por defecto), este puede ir a travs de todos los proveedores, editar la informacin de direccin de cualquier proveedor y editar el nombre y la cantidad por unidad para cualquier producto suministrado por el proveedor seleccionado. Sin embargo si el usuario indica que solo puede ver y editar un proveedor en particular, entonces solo puede ver los detalles y productos para un nico proveedor y solamente puede actualizar el nombre y cantidad por unidad para aquellos productos que no estn descontinuados. Luego nuestro primer paso en este tutorial, es crear este DropDownList y poblarlo con los proveedores en el sistema. Abra la pgina UserLevelAccess.aspx en la carpeta EditInsertDelete, arrastre un DropDownList cuya propiedad ID se establezca en Suppliers y enlace este DropDownList a un nuevo ObjectDataSource llamado AllSuppliersDataSource.

Figura 3. Crear un nuevo ObjectDataSource llamado AllSuppliersDataSource Como deseamos que este DropDownList incluya todos los proveedores, configure el ObjectDataSource para que invoque el mtodo GetSuppliers() de la clase SuppliersBLL. Adema asegrese que el mtodo Update() del ObjectDataSource sea asignado al mtodo UpdateSupplierAddress de la clase SuppliersBLL, ya que este ObjectDataSource tambin ser usado por el DetailsView que agregaremos en el paso 2. Despus de completar el asistente del ObjectDataSource, complete los pasos para configurar el DropDownList Suppliers para que muestre el campo de datos CompanyName y use el campo de datos SupplierID como el valor para cada ListItem.

Figura 4. Configure el DropDownList Suppliers para que utilice los campos de datos CompanyName y SupplierID Hasta el momento el DropDownList muestra los nombres de las compaas de los proveedores en la base de datos, Sin embargo, todava necesitamos incluir una opcin Show/Edit ALL Suppliers al DropDownList. Para realizar esto establezca la propiedad AppendDataBoundItems del DropDownList Suppliers en True y luego agregue un ListItem cuya propiedad Text sea Show/Edit ALL Suppliers y su valor sea -1. Esto puede ser agregado directamente al marcado declarativo o por medio del diseador yendo a la ventana propiedades y presionando en los puntos suspensivos de la propiedad tems del DropDownList. Nota: Refirase de nuevo al tutorial Filtrado Maestro/Detalle con un DropDownList para una discusin ms detallada sobre la adicin de un tem Select All a un DropDownList enlazado. Despus que la propiedad AppendDataBoundItems ha sido definida y el ListItem se ha agregado, el marcado declarativo del DropDownList lucir similar al siguiente:
<asp:DropDownList ID="Suppliers" runat="server" AppendDataBoundItems="True" DataSourceID="AllSuppliersDataSource" DataTextField="CompanyName" DataValueField="SupplierID"> <asp:ListItem Value="-1">Show/Edit ALL Suppliers</asp:ListItem> </asp:DropDownList>

La figura 5 muestra una captura de pantalla de nuestro progreso actual, cuando lo visitamos por medio de un navegador.

Figura 5. El DropDownList Suppliers contiene un ListItem ShowAll, mas uno para cada proveedor Como deseamos actualizar la interfaz de usuario inmediatamente despus que el usuario ha cambiado su seleccin, establezca la propiedad AutoPostBack del DropDownList Suppliers en True. En el paso 2 crearemos un control DetailsView que mostrara la informacin para los proveedores basado en la seleccin del DropDownList. Luego en el paso 3, crearemos un controlador de evento para el evento SelectedIndexChanged de este DropDownList, en el cual agregaremos cdigo que enlace la informacin del proveedor apropiado al DetailsView basado en el proveedor seleccionado. Paso 2. Agregar un control DetailsView Usaremos un DetailsView para mostrar la informacin de los proveedores. Para el usuario que pueda ver y editar todos los proveedores, el DetailsView soportara paginacin, permitindole saltar a la informacin del proveedor un registro a la vez. Sin embargo si el usuario trabaja para un proveedor en particular, el DetailsView solo

mostrara la informacin de ese proveedor en particular y no incluir interfaz de paginacin. En todos los casos, el DetailsView necesita permitir al usuario editar la informacin de los campos de la direccin del proveedor, ciudad y pas. Agregue un DetailsView a la pagina debajo del DropDownList Suppliers, establezca su propiedad ID en SuppliersDetails y enlcelo al ObjectDataSource AllSupplierDataSource creado en el paso anterior. Luego seleccione las casillas de verificacin Habilitar Paginacin y Habilitar Edicin desde la etiqueta inteligente del DetailsView. Nota: Si no vez la opcin habilitar edicin en la etiqueta inteligente del DetailsView se debe a que no has asignado el mtodo Update() del ObjectDataSource al mtodo UpdateSupplierAddress de la clase SuppliersBLL. Tmese un momento para regresar y realizar el cambio de esta configuracin, despus la opcin Habilitar Edicin aparece en la etiqueta inteligente del DetailsView. Como el mtodo UpdateSupplierAddress de la clase SuppliersBLL solo acepta cuatro parmetros SupplierID, address, city y country modifique los BoundFields del DetailsView para que los BoundFields CompanyName y Phone sean de solo lectura. Adicionalmente removemos tambin el BoundField SupplierID. tiene Finalmente, su el ObjectDataSource AllSuppliersDataSource actualmente propiedad

OldValuesParameterFormatString establecida en original_{0}. Tmese un momento para remover esta propiedad estableciendo desde la sintaxis declarativa, o establecindola en su valor por defecto {0}. Despus de configurar el DetailsView SupplierDetails y el ObjectDataSource

AllSuppliersDataSource, tendremos el siguiente marcado declarativo:


<asp:ObjectDataSource ID="AllSuppliersDataSource" runat="server" SelectMethod="GetSuppliers" TypeName="SuppliersBLL" UpdateMethod="UpdateSupplierAddress"> <UpdateParameters> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="address" Type="String" /> <asp:Parameter Name="city" Type="String" /> <asp:Parameter Name="country" Type="String" /> </UpdateParameters> </asp:ObjectDataSource>

<asp:DetailsView ID="SupplierDetails" runat="server" AllowPaging="True" AutoGenerateRows="False" DataKeyNames="SupplierID" DataSourceID="AllSuppliersDataSource"> <Fields> <asp:BoundField DataField="CompanyName" HeaderText="Company" ReadOnly="True" SortExpression="CompanyName" /> <asp:BoundField DataField="Address" HeaderText="Address" SortExpression="Address" /> <asp:BoundField DataField="City" HeaderText="City" SortExpression="City" /> <asp:BoundField DataField="Country" HeaderText="Country" SortExpression="Country" /> <asp:BoundField DataField="Phone" HeaderText="Phone" ReadOnly="True" SortExpression="Phone" /> <asp:CommandField ShowEditButton="True" /> </Fields> </asp:DetailsView>

Hasta este punto el DetailsView puede ser paginado y la direccin del proveedor seleccionado puede ser actualizada independientemente de la seleccin hecha en el DropDownList Suppliers (Ver Figura 6)>

Figura 6. Cualquier informacin de los proveedores puede ser visualizada y su direccin actualizada Paso 3. Mostrar solamente la informacin del proveedor seleccionado Nuestra pgina actualmente muestra la informacin de todos los proveedores independiente de si un proveedor en particular ha sido seleccionado en el DropDownList Suppliers. Con el fin de mostrar solamente la informacin del proveedor seleccionado, necesitamos agregar otro ObjectDataSource a nuestra pgina que recupere la informacin de un proveedor en particular. Agregue un nuevo ObjectDataSource a la pgina, nombrndolo

SingleSupplierDataSource. Desde su etiqueta, presione el enlace Configurar origen de datos y haga que use el mtodo GetSupplierBySupplierID(SupplierID) de la clase SuppliersBLL. Al igual que con el ObjectDataSource AllSuppliersDataSource, haga que el mtodo Update() del ObjectDataSource SingleSupplierDataSource sea asignado al mtodo UpdateSupplierAddress de la clase SupplierBLL.

Figura 7. Configure el ObjectDataSource SingleSupplierDataSource para que use el mtodo GetSupplierBySupplierID(supplierID) Luego especificamos el origen del parmetro para el parmetro de entrada supplierID del mtodo GetSupplierBySupplierID(supplierID). Como deseamos mostrar la informacin para el proveedor seleccionado en el DropDownList, use la propiedad SelectedValue del DropDownList Suppliers como el origen del parmetro.

Figura 8. Use el DropDownList Suppliers como el origen del parmetro supplierID Aunque con este segundo ObjectDataSource agregado, el control DetailsView est actualmente configurado para siempre usar el ObjectDataSource AllSuppliersDataSource. Necesitamos agregar lgica para ajustar los orgenes de datos

usados por el DetailsView dependiendo del tem del DropDownList Suppliers seleccionado. Para realizar esto, creamos un controlador de evento SelectedIndexChanged para el DropDownList Suppliers. Esto puede crearse ms fcilmente haciendo doble clic en el DropDownList en el diseador. Este controlador de evento necesita determinar qu fuente de datos usar y debe enlazar nuevamente los datos al DetailsView. Esto puede realizarse con el siguiente cdigo:
Protected Sub Suppliers_SelectedIndexChanged _ (ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Suppliers.SelectedIndexChanged If Suppliers.SelectedValue = "-1" Then ' The "Show/Edit ALL" option has been selected SupplierDetails.DataSourceID = "AllSuppliersDataSource" ' Reset the page index to show the first record SupplierDetails.PageIndex = 0 Else ' The user picked a particular supplier SupplierDetails.DataSourceID = "SingleSupplierDataSource" End If ' Ensure that the DetailsView and GridView are in read-only mode SupplierDetails.ChangeMode(DetailsViewMode.ReadOnly) ' Need to "refresh" the DetailsView SupplierDetails.DataBind() End Sub

El controlador de evento comienza determinando si la opcin Show/Edit ALL Suppliers fue seleccionada. Si fuera as este establece el DataSourceID del DetailsView SupplierDetails en AllSuppliersDataSource y devuelve al usuario el primer registro en el conjunto de proveedores estableciendo la propiedad PageIndex en 0. Sin embargo si el usuario ha seleccionado un proveedor en particular desde el DropDownList, el DataSourceID del DetailsView es asignado a SingleSuppliersDataSource. Independientemente del origen de datos usado, el modo SuppliersDetails es revertido al modo de solo lectura y los datos son enlazados nuevamente al DetailsView llamando al mtodo DataBind() del control SuppliersDetails. Con el controlador de evento en su lugar, el control DetailsView ahora muestra el proveedor seleccionado, a menos que la opcin Show/Edit ALL Suppliers sea seleccionada, en cuyo caso todos los proveedores pueden ser vistos por medio de una

interfaz de paginacin. La figura 9 muestra la pagina cuando la opcin Show/Edit ALL Suppliers es seleccionada, note que la interfaz de paginacin es presentada, permitiendo al usuario visitar y actualizar cualquier proveedor. La figura 10 muestra la pagina con el proveedor Ma Maison seleccionado. Solamente la informacin de Ma Maison

Figura 9. Toda la informacin de los proveedores puede ser visualizada y editada

Figura 10. Solamente la informacin del proveedor seleccionado puede ser visualizada y editada

Nota: Para este tutorial, el EnableViewState del DropDownList y DetailsView debe ser establecido en True (por defecto) porque los cambios del SelectedIndex del DropDownList y la propiedad DataSourceID del DetailsView deben ser recordados a travs de las devoluciones de datos. Paso 4. Mostrar los productos de los proveedores en un GridView editable Con el DetailsView completo, nuestro siguiente paso es incluir un GridView editable que muestre aquellos productos suministrados por el proveedor seleccionado. El GridView deber permitir editar solamente los campos ProductName y QuantityPerUnit. Sin embargo si el usuario que visita la pgina es de un proveedor en particular, este deber actualizar solamente aquellos productos que no estn descontinuados. Para realizar esto necesitamos primero agregar una recarga del mtodo UpdateProducts de la clase ProductsBLL que toma solamente los campos ProductID, ProductName y QuantityPerUnit como entradas. Hemos intensificado el proceso yendo paso a paso en numerosos tutoriales, as que aqu solo veremos el cdigo, que debe ser agregado al ProductsBLL:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, False)> _ Public Function UpdateProduct(ByVal productName As String, _ ByVal quantityPerUnit As String, ByVal productID As Integer) As Boolean Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID) If products.Count = 0 Then ' no matching record found, return false Return False End If Dim product As Northwind.ProductsRow = products(0) product.ProductName = productName If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull() Else product.QuantityPerUnit = quantityPerUnit End If ' Update the product record Dim rowsAffected As Integer = Adapter.Update(product) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function

Con esta recarga creada, estamos listos para agregar el control GridView y su ObjectDataSource. Agregue un nuevo GridView a la pagina, establezca su propiedad ID a ProductsBySupplier y configrelo para usar un nuevo ObjectDataSource llamado ProductsBySupplierDataSource. Como deseamos que este GridView muestre aquellos productos del proveedor seleccionado, use el mtodo GetProductsBySupplierID(supplierID) de la clase ProductsBLL. Adems asigne el mtodo Update() a la nueva recarga UpdateProduct que acabamos de crear.

Figura 11. Configure el ObjectDataSource para que utilice la recarga UpdateProduct que recin creamos Debemos seleccionar el origen del parmetro para el parmetro de entrada supplierID del mtodo GetProductsBySupplierID(supplierID). Como deseamos mostrar los productos para el proveedor seleccionado en el DetailsView, usamos la propiedad SelectedValue del control DetailsView SupplierDetails como origen del parmetro.

Figura 12. Usar la propiedad SelectedValue del DetailsView SupplierDetails como origen de parmetro Regresando al GridView, removemos todos los campos del GridView excepto ProductName, QuantityPerUnit y Discontinued, marcando la casilla de verificacin Discontinued como de solo lectura. Adems seleccionamos la opcin Habilitar Edicin desde la etiqueta inteligente del GridView. Despus de realizar estos cambios, el marcado declarativo para el GridView y el ObjectDataSource lucir similar al siguiente:
<asp:GridView ID="ProductsBySupplier" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ProductsBySupplierDataSource"> <Columns> <asp:CommandField ShowEditButton="True" /> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" ReadOnly="True" SortExpression="Discontinued" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="ProductsBySupplierDataSource" runat="server" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProductsBySupplierID" UpdateMethod="UpdateProduct"> <UpdateParameters> <asp:Parameter Name="productName" Type="String" />

<asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> <SelectParameters> <asp:ControlParameter ControlID="SupplierDetails" Name="supplierID" PropertyName="SelectedValue" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Al

igual

que

con

los

ObjectDataSource

anteriores,

la

propiedad

OldValuesParameterFormatString de este est establecida en original_{0}, lo cual causara problemas cuando intentemos actualizar el nombre de un producto o la cantidad por unidad. Removamos esta propiedad de la sintaxis declarativa o establzcalo por defecto a {0}. Con esta configuracin completa, nuestra pgina ahora muestra los productos suministrados por el proveedor seleccionado en el GridView (ver Figura 13). Actualmente cualquier nombre o cantidad por unidad de los productos puede ser actualizado. Sin embargo, necesitamos actualizar nuestra lgica de pgina para que dicha funcionalidad sea prohibida en los productos descontinuados, para usuarios asociados con un proveedor en particular. Haremos frente a esto en la parte final del paso 5.

Figura 13. Se muestran los productos suministrados por el proveedor seleccionado

Nota: Con la adicin de este GridView editable, el controlador del evento SelectedIndexChanged del DropDownList Suppliers debe ser actualizado para devolver el GridView a su estado de solo lectura. Al contrario si un proveedor diferente es seleccionado en el medio de la edicin de la informacin de un producto, el ndice correspondiente en el GridView para el nuevo proveedor tambin deber ser editable. Para prevenir esto, sencillamente establezca la propiedad EditIndex del GridView en -1 en el controlador de evento SelectedIndexChanged. Ademas, recuerde que es importante que el ViewState del GridView este habilitado (comportamiento por defecto). Si establece la propiedad EnableViewState en False, corre el riesgo de tener usuarios simultaneos que sin intencin eliminen o editen registros. Paso 5. Deshabilitar la edicin para los productos descontinuados cuando Show/Edit ALL Suppliers no es seleccionada Aunque el GridView ProductsBySupplier es totalmente funcional, actualmente otorga mucho acceso a aquellos usuarios que son de un proveedor en particular. Para nuestras reglas de negocio, estos usuarios no deben ser capaces de actualizar los productos descontinuados. Para forzar esto, podemos esconder (o deshabilitar) el botn Edit en aquellas filas del GridView con productos descontinuados cuando la pagina este siendo visitada por un usuario de un proveedor. Cree un controlador de evento para el evento RowDataBound del GridView. En este controlado de evento necesitamos determinar si el usuario esta o no asociado con un proveedor en particular, lo cual puede ser determinado para este tutorial verificando la propiedad SelectedValue del DropDownList Suppliers, si es algo diferente a -1, entonces el usuario est asociado con un proveedor en particular. Para aquellos usuarios, necesitamos determinar si existen o no productos descontinuados. Podemos grabar una referencia a la instancia ProductRow actual enlazada a la fila del GridView por medio de la propiedad e.Row.DataItem, como discutimos en el tutorial Mostrar informacin resumida en el pie de pgina del GridView. Si el producto es descontinuado, podemos grabar una referencia mediante programacin al botn Edit en el CommandField del GridView usando las tcnicas discutidas en el tutorial, Agregar una confirmacin en el lado del cliente cuando eliminamos. Una vez que tenemos una referencia podemos entonces esconder o deshabilitar el botn.

Protected Sub ProductsBySupplier_RowDataBound _ (ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _ Handles ProductsBySupplier.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then ' Is this a supplier-specific user? If Suppliers.SelectedValue <> "-1" Then ' Get a reference to the ProductRow Dim product As Northwind.ProductsRow = _ CType(CType(e.Row.DataItem, System.Data.DataRowView).Row, _ Northwind.ProductsRow) ' Is this product discontinued? If product.Discontinued Then ' Get a reference to the Edit LinkButton Dim editButton As LinkButton = _ CType(e.Row.Cells(0).Controls(0), LinkButton) ' Hide the Edit button editButton.Visible = False End If End If End If End Sub

Con este controlador de evento en su lugar, cuando visitamos la pagina como un usuario de un proveedor especifico, aquellos productos que estn descontinuados no son editables, ya que el botn Edit est escondido para estos productos. Por ejemplo, Gumbo Mix del Chef Anton es un producto descontinuado para el proveedor New Orleans Cajun Delights. Cuando visitamos la pagina para este proveedor en particular, el botn Edit para este producto est oculto a la vista (Ver Figura 14). Sin embargo, cuando visitamos usando el Show/Edit ALL Suppliers el botn Edit est habilitado (Ver Figura 15).

Figura 14. Para los usuarios de un proveedor especifico, el botn Edit para Gumbo Mix del Chef Anton est escondido

Figura 15. Para los usuarios Show/Edit ALL Suppliers, se muestra el botn Edit para Gumbo Mix del Chef Anton Verificar los derechos de acceso en la capa lgica de negocios En este tutorial las paginas ASP.Net manejan toda la lgica con respecto a la informacin que el usuario puede ver y que productos puede editar. Idealmente esta

lgica debera estar presente en la capa lgica de negocios. Por ejemplo, el mtodo GetSuppliers() de la clase SuppliersBLL (que recupera todos los proveedores) debe incluir una verificacin que asegure que el usuario actualmente registrado no est asociado con un proveedor especifico. Al igual que el mtodo UpdateSupplierAddress podra incluir una verificacin para asegura que el usuario actualmente registrado trabaja para nuestra compaa (y por lo tanto puede actualizar la informacin de direccin de todos los proveedores) o est asociado con el proveedor cuyos datos estn siendo actualizados. No se incluyeron estas verificaciones en la capa BLL en nuestro tutorial porque los derechos de usuario son determinados por un DropDownList en la pgina, al cual las clases BLL no pueden acceder. Cuando usamos el sistema de membreca o los esquemas de autentificacin proporcionados por ASP.Net (como la autentificacin de Windows), la informacin de los usuarios actualmente registrados y la informacin de los roles pueden ser accedida desde la BLL, por lo tanto hace que las verificaciones de los derechos de acceso sean posibles en las capas de presentacin y BLL. Resumen La mayora de sitios que proporcionan cuentas de usuario necesitan personalizar la interfaz de modificacin de datos basados en el usuario registrado. Los usuarios administradores podran eliminar y actualizar cualquier registro, mientras que los usuarios que no son administradores deben limitarse a solamente actualizar o eliminar registros creados por ellos mismos. Cualquiera que sea el escenario, los controles de datos web, el ObjectDataSource y las clases de la capa lgica de negocios deben ser ampliadas para agregar o denegar cierta funcionalidad basados en el registro del usuario. En este tutorial vimos como limitar la visualizacin y edicin de datos dependiendo si el usuario est asociado con un proveedor en particular o si trabaja para nuestra compaa. Con este tutorial se concluye el examen de insercin, actualizacin y eliminacin de datos usando controles GridView, DetailsView y FormView. Iniciando nuestro prximo tutorial, prestaremos atencin en la adicin del soporte de paginacin ordenamiento.

PAGINACION Y ORDENAMIENTO DE DATOS

24.

PAGINACIN Y ORDENAMIENTO DE DATOS DEL INFORME

La paginacin y el ordenamiento son dos caractersticas muy comunes cuando se muestran datos en una aplicacin en lnea. En este tutorial echaremos un primer vistazo a la adicin de ordenamiento y paginacin a nuestros informes, que luego construiremos en futuros tutoriales. Introduccin La paginacin y el ordenamiento son dos caractersticas muy comunes cuando mostramos datos en una aplicacin en lnea. Por ejemplo en la bsqueda de libros de una biblioteca en lnea Asp.Net, puede haber cientos de libros, pero con los resultados de bsqueda, el informe muestra nicamente diez por pgina. Por otra parte los resultados pueden ser ordenados por ttulo, precios, nmero de pginas, nombre del autor y as sucesivamente. Si bien los ltimos 23 tutoriales han examinado como construir una gran variedad de informes, incluyendo las interfaces que permiten agregar, editar y eliminar datos, no hemos visto como ordenar los datos y los nicos ejemplos que hemos visto de paginacin han sido con los controles GridView y FormView. En este tutorial veremos cmo agregar ordenamiento y paginacin a nuestros informes, lo cual se pude lograr con solo seleccionar algunas casillas de verificacin. Por desgracia esta implementacin simplista tiene sus inconvenientes en la interfaz de ordenamiento que dejan un poco que desear y las rutinas de paginacin no estn diseadas para paginar eficientemente conjuntos muy grandes de resultados. En los futuros tutoriales estudiaremos la forma de superar estas limitaciones de paginacin fuera de la caja y soluciones de ordenamiento. Paso 1. Agregar paginacin y ordenacin a las pginas web de tutoriales Antes de empezar este tutorial primero tmese un momento para agregar las pginas Asp.Net que necesitaremos para este y los siguientes tres tutoriales. Comience agregando una carpeta al proyecto llamada PagingAndSorting. Luego agregue las cinco pginas Asp.Net siguientes a esta carpeta, configurndolas para que todas ellas utilicen la pgina maestra Site.master: Default.aspx SimplePagingSorting.aspx

EfficientPagind.aspx SortParameter.aspx CustomSortingUI.aspx

Figura 1. Crear una carpeta PagingSorting y agregar las pginas Asp.Net al tutorial Luego abra la pgina Default.aspx y arrastre el control de usuario

SectionTutorialListing.ascx desde la carpeta UserControls a la superficie de diseo. Este control de usuario, que creamos en el tutorial de pginas maestras y Navegacin del sitio, enumera el mapa del sitio y muestra los tutoriales de la seccin actual en una lista con vietas.

Figura 2. Agregar el control de usuario SectionTutorialListing.ascx a Default.aspx

Para que la lista de vietas muestre los tutoriales de paginacin y clasificacin que vamos a crear, debemos agregarlos al mapa del sitio. Abra el archivo Web.sitemap y agregue el siguiente cdigo de marcado, luego del marcado del nodo de Insercin, actualizacin y eliminacin:
<siteMapNode title="Paging and Sorting" url="~/PagingAndSorting/Default.aspx" description="Samples of Reports that Provide Paging and Sorting Capabilities"> <siteMapNode url="~/PagingAndSorting/SimplePagingSorting.aspx" title="Simple Paging & Sorting Examples" description="Examines how to add simple paging and sorting support." /> <siteMapNode url="~/PagingAndSorting/EfficientPaging.aspx" title="Efficiently Paging Through Large Result Sets" description="Learn how to efficiently page through large result sets." /> <siteMapNode url="~/PagingAndSorting/SortParameter.aspx" title="Sorting Data at the BLL or DAL" description="Illustrates how to perform sorting logic in the Business Logic Layer or Data Access Layer." /> <siteMapNode url="~/PagingAndSorting/CustomSortingUI.aspx" title="Customizing the Sorting User Interface" description="Learn how to customize and improve the sorting user interface." /> </siteMapNode>

Figura 3. Actualizar el mapa del sitio para incluir las nuevas pginas ASP.Net Paso 2. Mostrar la informacin de los productos en un GridView

Antes de implementar las funcionalidades de ordenamiento y paginacin, primero crearemos un GridView no ordenable ni paginable para que muestre la informacin de los productos. Esta es una tarea que hemos hecho antes varias veces a lo largo de la serie de tutoriales por lo cual los pasos deben sernos familiares. Comenzamos abriendo la pgina SimplePagingSorting.aspx y arrastre un control GridView desde la caja de herramientas hasta el diseador, estableciendo su propiedad ID en Products. Luego cree un nuevo ObjectDataSource que use el mtodo GetProducts() de la clase ProductsBLL para que recupere toda la informacin de los productos.

Figura 4. Recupere toda la informacin de los productos usando el mtodo GetProducts() Como este reporte es un reporte de solo lectura, no es necesario asignar los mtodos Insert(), Update(), y Delete() del ObjectDataSource a los mtodos ProductsBLL correspondientes; por lo tanto, seleccione Ninguno en las listas desplegables de las pestaas UPDATE, INSERT y DELETE.

Figura 5. Seleccione la opcin Ninguno en las listas desplegables de las pestaas INSERT, UPDATE y DELETE Luego personalizamos los campos del GridView para que solo sean mostrados los nombres de los productos, proveedores, categoras, precios y estado. Por otra parte, sintase libre de cambiar el formato a nivel de campo, de ajustar las propiedades HeaderText o de dar formato al precio como moneda. Despus de estos cambios, el marcado declarativo del GridView lucir similar al siguiente:
<asp:GridView ID="Products" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False" runat="server"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" SortExpression="CategoryName" ReadOnly="True" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" SortExpression="SupplierName" ReadOnly="True" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" DataFormatString="{0:C}" HtmlEncode="False" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView>

La figura 6 muestra nuestro progreso hasta el momento visto desde un navegador. Note que la pagina muestra todos los productos en una pantalla, mostrando el nombre, categora, proveedor, precio y estado de cada producto.

Figura 6. Son listados cada uno de los productos Paso 3. Agregar soporte de paginacin Mostrar todos los productos en una sola pantalla puede ser una sobrecarga de informacin para el usuario que esta visualizando los datos. Para ayudar a que los resultados sean ms manejables, podemos dividir los datos en pginas de datos ms pequeas y permitir al usuario ir a travs de las pginas de datos una pgina a la vez. Para realizar esto simplemente seleccionamos la casilla de verificacin Habilitar Paginacin desde la etiqueta inteligente del GridView (Esto establece la propiedad AllowPaging del GridView en True).

Figura 7. Habilitar la casilla de paginacin para agregar el soporte de paginacin Habilitando la paginacin limitamos el nmero de registros mostrados por pgina y agrega una interfaz de paginacin al GridView. Por defecto la interfaz de paginacin, mostrada en la figura 7, es una serie de nmeros de pgina, que permite al usuario navegar rpidamente de una pgina de datos a otra. Esta interfaz de paginacin debe sernos familiar, ya que la vimos cuando agregamos el soporte de paginacin a los controles DetailsView y FormView en tutoriales pasados. Los controles DetailsView y FormView solamente muestran un registro por pgina. Sin embargo, el GridView consulta su propiedad PageSize para determinar cuntos registros mostrar por pagina (esta propiedad por defecto tiene un valor de 10). La interfaz de paginacin del GridView, FormView y DetailsView puede ser personalizada usando las siguientes propiedades: PagerStyle indica la informacin de estilo para la interfaz de paginacin; puede especificar herramientas como BackColor, ForeColor, CssClass, HorizontalAlign y as sucesivamente. PagerSettings contiene un grupo de propiedades que pueden personalizar la funcionalidad de la interfaz de paginacin; PageButtonCount indica el nmero mximo de nmeros de pgina en la interfaz de paginacin Numeric (por defecto 10); la propiedad Mode indica cmo opera la interfaz de paginacin y puede ser establecida por:

NextPrevious muestra los botones Next y Previous, permitiendo al usuario ir hacia adelante o a atrs una pgina a la vez. NextPreviousFirstLast adems de los botones Next y Previous, se incluyen los botones First y Last, permitindole al usuario saltar rpidamente a la primera o ltima pagina de datos.

Numeric muestra una serie de nmeros de pagina, permitindole al usuario saltar inmediatamente a cualquier pagina NumericFirstLast adems de los nmeros de pagina, se incluyen los botones First y Last, permitindole al usuario moverse rpidamente a la primera o ultima pagina de datos; los botones First/Last solo se muestran si todos los nmeros de pagina no caben.

Por otro lado, el GridView, DetailsView y FormView ofrecen las propiedades PageIndex y PageCount, que indica la pgina actual que est siendo visualizada y el nmero total de pginas de datos, respectivamente. La propiedad PageIndex es indexada comenzando en 0, lo que significa que cuando vemos la primera pgina de datos el PageIndex es igual a 0. Por otro lado, PageCount comienza contando en 1, lo que significa que el PageIndex es limitado por los valores entre 0 y PageCount-1 Nos tomaremos un momento para mejorar la apariencia de nuestra interfaz de paginacin del GridView. En concreto haremos que la interfaz de paginacin este alineada a la derecha con un color de fondo gris claro. En lugar de establecer estas propiedades directamente por medio de la propiedad PagerStyle del GridView, crearemos una clase CSS en Styles.css llamado PagerRowStyle y luego asignamos la propiedad CssClass del PagerStyle a travs de nuestro tema. Comience abriendo Styles.css y agregue la siguiente definicin de clase CSS:
.PagerRowStyle { background-color: #ddd; text-align: right; }

Luego, abra el archivo GridView.skin en la carpeta DataWebControls de la carpeta App_Themes. Como discutimos en el tutorial Pginas Maestras y Navegacin del sitio, los archivos skin pueden ser usados para especificar los valores predeterminados de las propiedades para un control web. Por lo tanto ampliaremos las definiciones

existentes para incluir la definicin de la propiedad CssClass del PagerStyle en PagerRowStyle. Tambin configuramos la interfaz de paginacin para mostrar un mximo de cinco botones numricos de pgina usando la interfaz de paginacin NumericFirstLast.
<asp:GridView runat="server" CssClass="DataWebControlStyle"> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> <RowStyle CssClass="RowStyle" /> <HeaderStyle CssClass="HeaderStyle" /> <FooterStyle CssClass="FooterStyle" /> <SelectedRowStyle CssClass="SelectedRowStyle" /> <PagerStyle CssClass="PagerRowStyle" /> <PagerSettings Mode="NumericFirstLast" PageButtonCount="5" /> </asp:GridView>

La experiencia de paginacion del usuario La figura 8 muestra la pagina web visitada por medio de un navegador luego que la casilla de verificacin Habilitar Paginacion del GridView ha sido seleccionada y las configuraciones de PagerStyle y PageSettings se han realizado por medio del archivo GridView.skin. Note que solamente se muestran diez registros a la vez y la interfaz de paginacin indica que est visitando la primera pgina de datos.

Figura 8. Con la paginacin habilitada solo se muestra un conjunto de registros a la vez Cuando el usuario presiona uno de los nmeros de pginas en la interfaz de paginacin, se produce una devolucin de datos que carga nuevamente la pgina

mostrando la pgina de registros solicitada. La Figura 9 muestra el resultado despus de optar por ver la pagina final de datos. Note que la pagina final solamente tiene un solo registro, esto es porque hay 81 registros en total, lo que origina 8 pginas de 10 registros por pagina mas una pgina con un solo registro.

Figura 9. Presionar un numero de pagina origina una devolucin de datos y muestra el apropiado conjunto de registros Flujo de trabajo de la paginacin en el lado del servidor Cuando el usuario final presiona un botn en la interfaz de paginacin, se genera una devolucin de datos y comienza el siguiente flujo de trabajo en el lado del servidor: 1. Se genera el evento PageIndexChanging del GridView (o DetailsView o FormView). 2. El ObjectDataSource solicita nuevamente todos los datos desde la BLL; los valores de las propiedad PageIndex y pageSize son usadas para determinar que registros recuperados desde la BLL necesitan ser mostrados en el GridView. 3. Se genera el evento PageIndexChanged del GridView En el paso 2, el ObjecDataSource solicita nuevamente todos los datos a su origen de datos. Este estilo de paginacin es comnmente conocido como paginacin por defecto, ya que es el comportamiento de paginacin usado por defecto cuando definimos la propiedad AllowPaging en True. Con la paginacin por defecto el control de datos web ingenuamente recupera todos los registros para cada pgina de datos, aunque solamente un grupo de registros son presentados en la actualidad en el HTML que se envi al navegador. A menos que los datos de la base de datos sean almacenados en cache por la BLL o el ObjectDataSource, la paginacin por defecto es inviable para conjuntos de resultados lo suficientemente grandes o para aplicaciones con muchos usuarios concurrentes.

En el prximo tutorial examinaremos como implementar la paginacin personalizada. Con la paginacin personalizada puede indicar especficamente al ObjectDataSource que solo recupere el conjunto preciso de registros necesarios para la pgina de datos solicitada. Como puede imaginar, la paginacin personalizada proporciona mayor eficiencia en la paginacin de grandes resultados. Nota: Aunque la paginacin por defecto no es adecuada cuando paginamos grandes resultados de datos o en sitios con muchos usuarios simultneos, la realizacin de la paginacin personalizada requiere ms cambios y esfuerzos en su implementacin y no es tan sencillo como seleccionar una casilla de verificacin (como con la paginacin por defecto). Por lo tanto, la paginacin por defecto puede ser la opcin ideal para sitios web pequeos y con poco trfico o cuando paginamos conjuntos de resultados relativamente pequeos, ya que es mucho ms fcil y rpido de implementar. Por ejemplo, si se que nunca tendr ms de 100 productos en mi base de datos, la ganancia de rendimiento mnima disfrutada con la paginacin personalizada probablemente no sea compensada con el esfuerzo que requiere implementarla, Sin embargo si podramos llegar a tener miles de productos, no implementar la paginacin personalizada dificultara en gran medida la escalabilidad de nuestra aplicacin. Paso 4. Personalizar la interfaz de paginacin Los controles de datos web proporcionan un nmero de propiedades que pueden ser usadas para mejorar la experiencia de paginacin del usuario. Por ejemplo, la propiedad PageCount indica cuantas pginas en total hay, mientras que la propiedad PageIndex indica la pgina actual que est siendo visitada y puede ser definido para mover rpidamente un usuario a la pgina especificada. Para mostrar cmo usar estas propiedades para mejorar la experiencia de paginacin del usuario, agregaremos un control web Label a nuestra pgina que informe al usuario que pagina est visitando actualmente, junto con un control DropDownList que les permita saltar rpidamente a cualquier pgina dada. Primero, agregue un control web Label a nuestra pgina, estableciendo su propiedad ID en PagingInformation y limpie su propiedad Text. Luego agregue un controlador de evento para el evento DataBound del GridView y luego agregue el siguiente cdigo:

Protected Sub Products_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Products.DataBound PagingInformation.Text = String.Format("You are viewing page {0} of {1}...", _ Products.PageIndex + 1, Products.PageCount) End Sub

Este controlador de evento asigna a la propiedad Text del Label PagingInformation un mensaje informando al usuario la pagina que est visitando actualmente Products.PageIndex+1 y el total de paginas Products.PageCount (agregamos un 1 a la propiedad Products.PageIndex porque el PageIndex est indexado comenzando en 0). Seleccione asignar la propiedad Text de este Label en el controlador del evento DataBound en lugar del controlador de evento PageIndexChanged porque el evento DataBound se genera cada vez que los datos son enlazados al GridView, mientras que el PageIndexChanged solo se dispara cuando el ndice de pgina cambia. Cuando el GridView est inicialmente enlazando datos en la primera visita a la pgina, el evento PageIndexChanging no se dispara (mientras que el evento DataBound si lo hace). Con esta adicin, ahora se muestra al usuario un mensaje indicando la pgina que est visitando y cuantas pginas de datos en total hay.

Figura 10. Se muestran el nmero de pgina actual y el nmero total de pginas Adems del control Label, tambin agregamos un DropDownList que muestre los nmeros de pginas en el GridView con la pgina seleccionada que se ve actualmente. La idea es que el usuario pueda saltar rpidamente desde la pgina actual a cualquier otra, simplemente seleccionando el ndice de la nueva pgina en el DropDownList.

Comience agregando un DropDownList al diseador, estableciendo su propiedad ID en PageList y seleccionando la opcin Habilitar AutoPostBack en su etiqueta inteligente. Luego regresamos al controlador de evento DataBound y agregamos el siguiente cdigo:
' Clear out all of the items in the DropDownList PageList.Items.Clear() ' Add a ListItem for each page For i As Integer = 0 To Products.PageCount 1 ' Add the new ListItem Dim pageListItem As New ListItem(String.Concat("Page ", i + 1), i.ToString()) PageList.Items.Add(pageListItem) ' select the current item, if needed If i = Products.PageIndex Then pageListItem.Selected = True End If Next

Este codigo inicia limpiando los tems en el DropDownList PageList. Esto puede parecer innecesario, ya que no esperaramos que el nmero de pginas cambie, pero otros usuarios podran usar simultneamente el sistema, agregando o eliminando registros de la tabla Products. Aquellas inserciones y eliminaciones pueden alterar el nmero de pginas de datos. Luego necesitamos crear los nmeros de pgina y que cada uno se asigne al PageIndex actual del GridView seleccionado por defecto. Para realizar esto con un bucle desde 0 a PageCount-1, agregamos un nuevo ListItem en cada iteracin y establecemos su propiedad Selected en True si el ndice de iteracin actual es igual a la propiedad PageIndex del GridView. Finalmente creamos un controlador de evento para el SelectedIndexChanged del DropDownList, que se genera cada vez que el usuario selecciona un tem diferente en la lista. Para crear este controlador de evento, simplemente damos doble clic al DropDownList en el diseador y luego agregamos el siguiente cdigo:
Protected Sub PageList_SelectedIndexChanged(sender As Object, e As System.EventArgs) _ Handles PageList.SelectedIndexChanged

' Jump to the specified page Products.PageIndex = Convert.ToInt32(PageList.SelectedValue) End Sub

Como muestra la figura 11, simplemente cambiando la propiedad PageIndex del GridView hacemos que los datos sean enlazados nuevamente al GridView. En el controlador de evento DataBound del GridView, el apropiado ListItem del DropDownList esta seleccionado.

Figura 11. El usuario es llevado automticamente a la pagina seis cuando selecciona Pagina 6 en la lista de tem del DropDownList Paso 5. Agregar un soporte de ordenamiento bidireccional Agregar un soporte de ordenamiento es tan sencillo como agregar un soporte de paginacin, simplemente seleccionamos la opcin Habilitar Ordenamiento en la etiqueta inteligente del GridView (la cual establece la propiedad AllowingSorting del GridView en True). Esto presenta cada uno de los encabezados de los campos del GridView como LinkButtons que cuando son presionados, originan una devolucin de datos y recuperan los datos ordenados por la columna presionada en orden ascendente. Dando clic en el mismo encabezado hacemos que el LinkButton organice nuevamente los datos en orden descendente. Nota: Si est usando una capa de acceso a datos personalizada en lugar de un DataSet tipiado, podra no tener habilitada la opcin Habilitar ordenamiento en la etiqueta inteligente del GridView. El GridView solamente tiene habilitada esta opcin para enlazar las fuentes de datos que soportan el ordenamiento de forma nativa. El DataSet

tipiado proporciona un soporte de ordenamiento ya que el DataTable de ADO.Net proporciona un mtodo Sort, que cuando es invocado, ordena los DataRows del DataTable usando el criterio especificado. Si su DAL no devuelve objetos que soporten el ordenamiento de forma nativa necesitara configurar el ObjecDataSource para pasar la informacin de ordenamiento a la capa lgica de negocios, que puede ordenar los datos o tener los datos ordenados en la DAL. Exploraremos como ordenar los datos en la capa de lgica de negocios y en la capa de acceso a datos en un prximo tutorial. Los LinkButtons de ordenar son presentados como hipervnculos de HTML, cuyos colores actuales (azul para un enlace no visitado y rojo oscuro para un enlace visitado) chocan con el color de fondo de la fila de encabezado. Haremos que todos los enlaces de la fila de encabezado sean mostrados en blanco, independientemente de si han sido visitados o no. Esto puede realizarse agregando la siguiente clase a Styles.css:
.HeaderStyle a, .HeaderStyle a:visited { color: White; }

Esta sintaxis indica usar texto blanco cuando mostramos hipervnculos dentro de un elemento que use la clase HeaderStyle. Despus de agregar este CSS, cuando visitamos la pagina por medio de un navegador, su pantalla lucir similar a la figura 12. En concreto, la figura 12 muestra los resultados despus que el enlace del encabezado del campo Price ha sido presionado.

Figura 12. Los resultados han sido ordenados por el UnitPrice de forma ascendente Examinar el flujo de trabajo del ordenamiento Todos los campos del GridView, los BoundField, CheckBoxField, TemplateField y as sucesivamente; tienen una propiedad SortExpression que indica la expresin que debe ser usada para ordenar los datos cuando el enlace del encabezado del campo es presionado. El GridView tambin tiene una propiedad SortExpression. Cuando el LinkButton de ordenamiento del encabezado es presionado, el GridView asigna el valor SortExpression de los campos a su propiedad SortExpression. Luego los datos son nuevamente recuperados desde el ObjectDataSource y ordenados de acuerdo a la propiedad SortExpression del GridView. La siguiente lista detalla la secuencia de pasos que se siguen cuando un usuario final ordena los datos en un GridView: 1. Se genera el evento Sorting del GridView 2. La propiedad SortExpression del GridView es establecida con la SortExpression del campo cuyo encabezado del LinkButton de ordenamiento fue presionado. 3. El ObjectDataSource recupera nuevamente todos los datos desde la BLL y luego los ordena usando la SortExpression del GridView 4. La propiedad PageIndex del GridView es reiniciada a 0, lo que significa que cuando los datos son ordenados el usuario es devuelto a la primera pagina de datos ( asumiendo que el soporte de paginacin ha sido implementado) 5. Se genera el evento Sorted del GridView Al igual que con la paginacin por defecto, la opcin de ordenamiento por defecto vuelve a recuperar todos los registros desde la BLL. Cuando usamos soporte de

ordenamiento sin paginacin o cuando usamos ordenamiento con paginacin por defecto, no existe una forma de evitar este impacto en el rendimiento (bajo almacenamiento en cache de los datos de la base de datos). Sin embargo como veremos en un futuro tutorial, es posible ordenar eficientemente datos cuando usamos paginacin personalizada Cuando enlazamos un ObjectDataSource al GridView por medio de la lista desplegable en la etiqueta inteligente del GridView, cada campo del GridView tiene asignada automticamente su propiedad SortExpression al nombre del campo de datos en la clase ProductsRow. Por ejemplo, la SortExpression del BoundField ProductName est establecida en ProductName, como se muestra en el siguiente marcado declarativo:
<asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" />

Un campo puede ser configurado para que no sea ordenable, limpiando su propiedad SortExpression (asignndole una cadena vaca). Para ilustrar esto, imaginemos que no deseamos permitir que nuestros usuarios ordenen nuestros productos por precio. La propiedad SortExpression del BoundField UnitPrice puede ser removida desde el marcado declarativo o por medio del cuadro del cuadro de dialogo Fields (que es accesible presionando el enlace Editar Columnas en el etiqueta inteligente del GridView)

Figura 13. Los resultados han sido ordenados por UnitPrice en orden ascendente

Una vez que la propiedad SortExpression ha sido removida para el BoundField UnitPrice, el encabezado es presentado como un texto en lugar de cmo un enlace, evitando que los usuarios ordenen los datos por precio.

Figura 14. Removiendo la propiedad SortExpression, los usuarios no pueden ordenar los productos por precio Ordenar mediante programacin el GridView Podemos ordenar el contenido del GridView mediante programacin usando el mtodo Sorted del GridView. Sencillamente pasamos el valor SortExpression para ordenar junto con el SortDirection (Ascendente o Descendente) y los datos del GridView sern ordenados nuevamente. Imagine que deseamos quitar el orden por UnitPrice porque estamos aburridos que los clientes compren solamente los productos con los precios ms bajos. Sin embargo deseamos animarlos a comprar los productos ms costosos, por lo tanto debemos habilitar el orden de los productos por precio, pero solo desde los productos ms costosos a los ms econmicos. Para realizar esto agregue un control web Button a la pagina, establezca su propiedad ID en SortPriceDescending y su propiedad Text a Sort by Price. Luego cree un controlador de evento para el evento Clic del Button y de doble clic sobre el control Button en el diseador. Agregue el siguiente cdigo a este controlador de evento:

Protected Sub SortPriceDescending_Click(sender As Object, e As System.EventArgs) _ Handles SortPriceDescending.Click 'Sort by UnitPrice in descending order Products.Sort("UnitPrice", SortDirection.Descending) End Sub

Dando clic al botn regresamos al usuario a la primera pagina con los productos ordenados por precio, desde el ms costoso al mas econmico (Ver Figura 15).

Figura 15. Al presionar el botn ordenamos los productos del ms costoso al ms econmico Resumen En este tutorial vimos como implementar las capacidades de paginacin y ordenamiento por defecto, lo cual fue tan fcil como seleccionar una casilla de verificacin. Cuando un usuario pagina u ordena datos, ocurre un flujo de trabajo como el siguiente: 1. Ocurre una devolucin de datos 2. Se generan los eventos previos a nivel de control de datos (PageIndexChanging o Sorting) 3. Todos los datos son recuperados nuevamente por el ObjectDataSource

4. Se

generan

los

eventos

posteriores

nivel

de

control

de

datos

(PageIndexChanged o Sorted) Aunque la implementacin de paginacin y ordenamiento bsico es muy sencilla, se requiere de ms esfuerzo cuando utilicemos una paginacin personalizada mucho ms eficiente o para mejorar ms la interfaz de ordenamiento y paginacin. En futuros tutoriales exploraremos estos tpicos.

25.

PAGINACIN EFICIENTE A TRAVS DE LARGAS CANTIDADES DE

DATOS
La opcin de paginacin por defecto de la presentacin de un control de datos no es adecuada cuando trabajamos con grandes cantidades de datos, ya que el control de origen de datos subyacente recupera todos los registros, aunque solo se muestre un subconjunto de los datos. En tales circunstancias debemos recurrir a la paginacin personalizada. Introduccin Como discutimos en el tutorial anterior, la paginacin puede ser implementada de dos formas: Paginacin por defecto puede ser implementada simplemente seleccionando la opcin de la casilla de verificacin Habilitar Paginacin en la etiqueta inteligente del control de datos web, sin embargo, cada vez que visualicemos una pgina de datos, el ObjectDataSource recupera todos los registros, incluso aunque solo un subconjunto de ellos se muestre en la pagina. Paginacin Personalizada mejora el rendimiento de la paginacin por defecto recuperando solamente los registros de la base de datos para una pgina en particular solicitada por el usuario; sin embargo, la paginacin personalizada involucra un poco mas de esfuerzo en la implementacin que la paginacin por defecto. Debido a la facilidad de implementacin, simplemente seleccionando una casilla de verificacin y listo, la paginacin por defecto es una opcin atractiva. Este enfoque de paginacin recupera todos los registros, hacindola una opcin imposible cuando paginamos grandes cantidades de datos i para sitios con muchos usuarios concurrentes. En tales circunstancias debemos usar la paginacin personalizada con el fin de proporcionar un sistema de respuesta. El desafo de la paginacin personalizada es ser capaces de escribir una consulta que devuelva el conjunto preciso de registros necesarios para una pgina de datos particular. Afortunadamente, Microsoft SQL Server 2005 proporciona una nueva palabra clave para rangos de resultados, la cual nos permite escribir una consulta que puede recuperar de forma eficiente el subconjunto de registros apropiado. Aunque la interfaz

de usuario para la paginacin personalizada es idntica a la de la paginacin por defecto, pasar de una pgina a la siguiente usando paginacin personalizada puede tener ms rdenes de magnitud ms rpida que la paginacin por defecto. Nota: El aumento de rendimiento exacto exhibido por la paginacin personalizada depende del nmero total de registros que son paginados y estn siendo cargados en el servidor de base de datos. Al final de este tutorial veremos algunos indicadores que muestran los beneficios en rendimiento obtenidos con la paginacin personalizada. Paso 1. Entender el proceso de paginacin personalizada Cuando paginamos datos, los registros precisos deben ser mostrados en una pgina dependiendo de la pgina de datos que est siendo solicitada y el nmero de registros mostrados por pgina. Por ejemplo, imaginemos que deseamos pgina 81 productos, mostrando 10 productos por pgina. Cuando visualizamos la primera pagina, deseamos los productos 1 al 10, cuando vemos la segunda pgina estamos interesados en los productos 11 a 20 y as sucesivamente. Hay tres variables que dictan que registros deben ser recuperados y como debe presentarlos la interfaz de paginacin: StartRowIndex el ndice de la primera fila en la pgina de datos que se muestra, este ndice puede ser calculado multiplicando el ndice de la pgina por los registros que se muestran por pgina ms uno. Por ejemplo, cuando paginamos 10 registros a la vez, para la primera pgina (cuyo ndice de pgina es 0), el StartRowIndex es 0*10+1, o 1; para la segunda pgina (cuyo ndice de pgina es 1), el StartRowIndex es 1*10+1, o 11. MaximumRows el nmero mximo de registros que se muestran por pgina. Esta variable es referida como el nmero mximo de filas por pgina, ya que la ltima pgina puede devolver menos registros. Por ejemplo, cuando paginas 81 productos con 10 registros por pgina, la pgina novena o final solo tiene un solo registro. Ninguna pgina mostrara ms registros que el valor de MaximumRows. TotalRecordCount el nmero total de registros que estn siendo paginados. Aunque esta variable no es necesaria para determinar que registros son recuperados para una pgina dada, esto dictamina la interfaz de paginacin. Por

ejemplo, si hay 81 productos que estn siendo paginados, la interfaz de paginacin sabe que muestra nueve nmeros de pgina en la UI de paginacin. Con la paginacin por defecto, el StartRowIndex es calculado como el producto del ndice de la pagina y el tamao de la pagina ms uno, mientras que MaximumRows es sencillamente el tamao de la pagina. Como la paginacin por defecto recupera todos los registros desde la base de datos cuando presenta cualquier pagina de datos, se conoce el ndice de cada fila, por lo tanto moverse a la fila de StartRowIndex es una tarea trivial. Por otra parte, el Total Record Count es fcil de leer, ya que simplemente es el nmero de registros en el DataTable (o cualquier objeto que se utilice para almacenar los resultados de la base de datos). Dadas las variables StartRowIndex y MaximumRows, la implementacin de la paginacin personalizada solo debe devolver el subconjunto preciso de registros empezando con el StartRowIndex hasta el MaximumRows de registros despus de eso. La paginacin personalizada proporciona dos desafos: Debe ser capaz de asociar eficientemente un ndice de fila con cada fila en los datos completos que puede iniciar devolviendo los registros desde el StartRowIndex especificado. Necesitamos proporcionar el nmero total de registros que estn siendo paginados. En los prximos dos tutoriales, examinaremos el script SQL necesario para responder a estos dos desafos. Adicional el Script SQL, necesitaremos implementar mtodos en la DAL y BLL. Paso 2. Recuperar el nmero total de registros que estn siendo paginados Antes de examinar cmo recuperar el subconjunto de registros preciso para la pgina que est siendo mostrada, primero veremos cmo devolver el nmero total de registros que estn siendo paginados. Esta informacin es necesaria con el fin de configurar apropiadamente la interfaz de paginacin para el usuario. El nmero total de registros devueltos por una consulta SQL particular puede ser obtenido usando la funcin agregada COUNT. Por ejemplo, para determinar el nmero total de registros en la tabla Products, podemos usar la siguiente consulta:

SELECT COUNT(*) FROM Products

Agregaremos un mtodo a nuestra DAL para que devuelva esta informacin. En particular crearemos un mtodo DAL llamado TotalNumberOfProducts() que ejecute la sentencia SELECT mostrada anteriormente. Comenzamos abriendo el archivo del DataSet tipiado Northwind.xsd de la carpeta App_Code/DAL. Luego hacemos clic derecho sobre el ProductsTableAdapter en el diseador y escogemos Agregar Consulta. Como vimos en los tutoriales anteriores, esto nos permite agregar un nuevo mtodo a la DAL, que cuando se invoque ejecute una sentencia SQL en particular o un procedimiento almacenado. Al igual que con nuestros mtodos del TableAdapter de los tutoriales anteriores, para este optaremos usar una sentencia SQL ad-hoc.

Figura 1. Usar una sentencia Ad-hoc En la siguiente pantalla podemos especificar qu tipo de consulta queremos crear. Como esta consulta devuelve un solo valor, el valor escalar del numero de registros en la tabla Products, seleccione la opcin SELECT que devuelve un solo valor.

Figura 2. Configure la consulta para que use una sentencia SELECT que devuelva un solo valor Despus de indicar el tipo de consulta a utilizar, debemos especificar la consulta.

Figura 3. Use la consulta SELECT COUNT(*) FROM Products Finalmente especifique el nombre para el mtodo. Como mencionamos anteriormente, usaremos TotalNumberOfProducts.

Figura 4. Nombre el mtodo de la DAL TotalNumberOfProducts Despus de dar clic en Finalizar, el asistente agregara el mtodo

TotalNumberOfProducts a la DAL. Los mtodos que devuelven escalares en la DAL devuelven tipos nulables, en caso que el resultado de la consulta SQL sea NULL. Sin embargo nuestra consulta COUNT siempre devolver un valor no Nulo, sin tener en cuenta si el mtodo DAL devuelve un entero nulo. Adicional al mtodo DAL, tambin necesitamos un mtodo en la BLL. Abra el archivo de clase ProductsBLL y agregue el mtodo TotalNumberOfProducts para que solo llame al mtodo TotalNumberOfProducts dentro de la DAL.
Public Function TotalNumberOfProducts() As Integer Return Adapter.TotalNumberOfProducts().GetValueOrDefault() End Function

El mtodo TotalNumberOfProducts de la DAL devuelve un entero nulable, sin embargo creamos un mtodo TotalNumberOfProducts en la clase ProductsBLL para que solo devuelva un entero estndar. Por lo tanto necesitamos hacer que el mtodo TotalNumberOfProducts de la clase ProductsBLL recupere la parte del valor del entero nulable devuelto por el mtodo TotalNumberOfProducts de la DAL. Llamar a GetValueOrDefault() devuelve el valor del entero nulable si existe: sin embargo si el entero nulable es null, este regresa por defecto el valor entero de 0.

Paso 3. Recuperar el subconjunto de registros preciso Nuestra siguiente tarea es crear mtodos en la DAL y BLL que acepte las variables StartRowIndex y MaximumRows discutidas anteriormente y recupere los registros apropiados. Antes de hacer esto primero veremos el Script necesario. El desafo que se nos presenta es que debemos asignar de manera eficiente un ndice a cada fila en los resultados completos que estn siendo paginados, para que podamos devolver solo aquellos registros comenzando en el StartRowIndex (y finalizando en el nmero mximo de registros). Este no es un desafo si ya existe una columna en la base de datos que sirva como ndice de la fila. A primera vista podramos pensar que el campo ProductID de la tabla Products sera suficiente, ya que el primer producto tiene un ProductID de 1, el segundo de 2, y as sucesivamente. Sin embargo borrar un producto deja un vaco en la secuencia, anulando este enfoque. Hay dos tcnicas generales usadas para asociar eficientemente un ndice de fila con los datos a paginar, permitiendo que se recupere el subconjunto de datos preciso: Usar la palabra clave ROW_NUMBER() de SQL Server 2005, la palabra clave ROW_NUMBER() asocia un rango con cada registro devuelto basado en algn orden. Este rango puede ser usado como un ndice de la fila para cada fila. Usando una tabla variable y SET ROWCOUNT(), la sentencia SET ROWCOUNT de SQL Server puede ser usada para especificar cuantos registros en total una consulta debe procesar antes de finalizar, las variables de tabla son variables locales T-SQL que pueden almacenar datos tabulares, de forma similar a las tablas temporales. Este enfoque trabaja igual de bien con Microsoft SQL Server 2005 y SQL Server 2000 (mientras que el enfoque ROW_NUMBER() solo funciona con SQL Server 2005). La idea aqu es crear una tabla variable que tenga una columna IDENTITY y columnas para las llaves principales de la tabla cuyos datos sern paginados. Luego el contenido de la tabla cuyos datos son paginados es colocado en la tabla variable, asociando un ndice de fila secuencial ( a travs de la columna IDENTITY) a cada registro en la tabla. Una vez que la tabla variable ha sido poblada, una sentencia SELECT sobre la tabla variable unida con una tabla

subyacente, puede ser ejecutad para extraer los registros particulares. La sentencia SET ROWCOUNT es usada para limitar de forma inteligente el nmero de registros que deben ser colocados en la tabla variable. La eficiencia de este enfoque es basada es el numero de pagina que est siendo solicitada, ya el valor SET ROWCOUNT es asignado al valor de StartRowIndex mas MaximumRows. Cuando paginamos pocos nmeros de pginas como las primeras pginas de datos paginadas, este enfoque es muy eficiente. Sin embargo exhibe un rendimiento casi igual a la paginacin por defecto cuando recuperar una pgina cercana al final. Este tutorial implementa la paginacin personalizada usando la palabra clave ROW_NUMBER(). Para mayor informacin sobre usar una tabla variable y la tcnica SET ROWCOUNT, vez Un mtodo ms eficiente para paginar grandes cantidades de datos. La palabra clave ROW_NUMBER() asocia un rango con cada registro devuelto basado en un orden particular usando la siguiente sintaxis:
SELECT columnList, ROW_NUMBER() OVER(orderByClause) FROM TableName

ROW_NUMBER() devuelve un valor numrico que especifica el rango para cada registro de acuerdo al orden indicado. Por ejemplo para ver el rango de cada producto, ordenado desde el ms costoso al ms econmico, podemos usar la siguiente consulta:
SELECT ProductName, UnitPrice, ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank FROM Products

La figura 5 muestra los resultados de esta consulta, cuando la ejecutamos desde la ventana de consulta en Visual Studio. Note que los productos estn ordenados por precio, junto con un rango para cada fila.

Figura 5. El rango es incluido para cada fila devuelta Nota: ROW_NUMBER() es solo una de las muchas funciones nuevas de rangos validas en SQL Server 2005. Para una discusin ms a fondo de ROW_NUMBER(), junto con las otras funciones de rango, lea Devolver resultados rankeados con Microsoft SQL Server 2005. Cuando rankiamos los resultados por la columna especificada ORDER BY en la clausula OVER (UnitPrice, en el ejemplo anterior), SQL Server debe ordenar los resultados. Esta es una operacin rpida si hay un ndice enclaustrado sobre los resultados de las columnas que estn siendo ordenadas, o si hay un ndice cubierto, pero en caso contrario puede ser ms lento. Para ayudar a mejorar el rendimiento para consultas muy grandes, considerar agregar un ndice no-enclaustrado para la columna por la cual los resultados son ordenados. Ver Funciones de ranking y rendimiento en SQL Server 2005 para ver con ms detalle las consideraciones de rendimiento.

La informacin de ranking devuelta por ROW_NUMBER() no puede ser usada directamente en la clausula WHERE. Sin embargo, una tabla derivada puede ser usada para devolver el resultado ROW_NUMBER(), que luego puede aparecer en la clausula WHERE. Por ejemplo, la siguiente consulta usa una tabla derivada para devolver las columnas ProductName y UnitPrice, junto con el resultado ROW_NUMBER() y luego usar una clausula WHERE para devolver solamente los productos cuyo rango est entre 11 y 20:
SELECT PriceRank, ProductName, UnitPriceFROM (SELECT ProductName, UnitPrice, ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank FROM Products ) AS ProductsWithRowNumberWHERE PriceRank BETWEEN 11 AND 20

Extendiendo este concepto un poco ms, podemos utilizar este enfoque para recuperar una pgina especfica de datos dados los valores StartRowIndex y MaximumRows deseados:
SELECT PriceRank, ProductName, UnitPriceFROM (SELECT ProductName, UnitPrice, ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank FROM Products ) AS ProductsWithRowNumber WHERE PriceRank > <i>StartRowIndex</i> AND PriceRank <= (<i>StartRowIndex</i> + <i>MaximumRows</i>)

Nota: Como veremos despus en este tutorial, el StartRowIndex suministrado por el ObjectDataSource es indexado comenzando en cero, mientras que el valor ROW_NUMBER() devuelto por SQL Server 2005 es indexado comenzando en 1. Por lo tanto la clausula WHERE devuelve los registros donde el PriceRank es estrictamente ms grande que StartRowIndex y menor o igual a StartRowIndex+MaximumRows. Ahora que hemos discutido como ROW_NUMBER() puede ser usado para recuperar una pgina de datos en particular dado los valores StartRowIndex y MaximumRows, necesitamos implementar esta lgica como mtodos en la DAL y BLL.

Cuando creamos esta consulta debemos decidir el orden con el cual los resultados eran rankeados, ordenaremos los productos por su nombre en orden alfabtico. Esto significa que con la implementacin de la paginacin personalizada en este tutorial no crearemos un reporte de paginado personalizado que pueda ser ordenado. En el prximo tutorial, veremos cmo proporcionar esta funcionalidad. En la seccin anterior creamos los mtodos DAL como sentencias ad-hoc.

Desafortunadamente el analizador T-SQL en Visual Studio usado por el asistente del TableAdapter no permite la sintaxis OVER usada por la funcin ROW_NUMBER(). Por lo tanto debemos crear este mtodo como un procedimiento almacenado. Seleccione el men de servidores desde el men Ver (o presionando Ctrl+Alt+S) y expanda el nodo NORTHWND.MDF. Para agregar un nuevo procedimiento almacenado, haga clic derecho sobre el nodo Procedimientos almacenados y seleccione agregar un nuevo procedimiento almacenado (Ver Figura 6).

Figura 6. Agregar un nuevo procedimiento almacenado para paginar los datos Este procedimiento almacenado debe aceptar dos parmetros de entrada -

@startRowIndex y @maximumRows y usar la funcin ROW_NUMBER() ordenada por el campo ProductName, devolviendo solo aquellas filas con un @startRowIndex mayor y un @startRowindex+@maximumRows menor o igual al especificado. Ingrese el siguiente script en el nuevo procedimiento almacenado y luego haga clic en el icono guardar para agregar el procedimiento almacenado a la base de datos.
CREATE PROCEDURE dbo.GetProductsPaged( @startRowIndex int, @maximumRows int)AS SELECT

ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, CategoryName, SupplierNameFROM ( SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) AS CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) AS SupplierName, ROW_NUMBER() OVER (ORDER BY ProductName) AS RowRank FROM Products ) AS ProductsWithRowNumbersWHERE RowRank > @startRowIndex AND RowRank <= (@startRowIndex + @maximumRows)

Despus de crear el procedimiento almacenado, tmese un momento para probarlo. Haga clic derecho sobre el nombre del procedimiento almacenado GetProductsPaged en el Explorador de servidores y seleccione la opcin ejecutar. Visual Studio le solicitara los parmetros de entrada @startRowIndex y @maximumRows (Ver Figura 7). Pruebe con diferentes valores y examine los resultados.

Figura 7. Ingrese un valor para los parmetros @startRowIndex y @maximumRows Despus de escoger los valores de los parmetros de entrada, la ventana Output mostrara los resultados. La figura 8 muestra los resultados cuando pasamos el valor de 10 para los parmetros @startRowIndex y @maximumRows.

Figura 8. Los registros deben aparecer en la segunda pgina de datos Con este procedimiento almacenado creado, estamos listos para crear el mtodo en el ProductsTableAdapter. Abra el DataSet tipiado Northwind.xsd, haga clic derecho en el ProductsTableAdapter y seleccione la opcin Agregar consulta. En lugar de crear la consulta usando una sentencia ad-hoc, crela usando un procedimiento almacenado.

Figura 9. Cree el mtodo DAL usando un procedimiento almacenado existente Luego se nos pide seleccionar el procedimiento almacenado a invocar. Escoja el procedimiento almacenado GetProductsPaged desde la lista desplegable.

Figura 10. Escoja el procedimiento almacenado GetProductsPaged de la lista desplegable La siguiente pantalla nos pregunta qu clase de dato es devuelto por el procedimiento almacenado: un dato tabular, un solo valor o ningn valor. Como el procedimiento almacenado puede devolver diversos registros, indique que este devuelva datos tabulares.

Figura 11. Indique que el procedimiento almacenado devuelva datos tabulares

Finalmente, indique los nombres de los mtodos que deseamos crear. Al igual que con nuestros tutoriales anteriores, continuemos y creemos los mtodos usando los enfoques Llenar un DataTable y Devolver un DataTable. Nombre el primer mtodo FillPaged y el segundo GetProductsPaged.

Figura 12. Nombre los mtodos FillPaged y GetProductsPaged Adicionalmente al mtodo DAL creado para devolver una pgina particular de productos, tambin necesitamos proporcionar dicha funcionalidad en la BLL. Al igual que el mtodo DAL, el mtodo GetProductsPaged de la BLL debe aceptar dos entradas enteras para especificar el StartRowIndex y MaximumRows y debe devolver solo aquellos registros que estn dentro del rango especificado. Crearemos un mtodo BLL en la clase ProductsBLL que solo llame al mtodo GetProductsPaged dentro de la DAL, as:
<System.ComponentModel.DataObjectMethodAttribute( _ System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsPaged(startRowIndex As Integer, maximumRows As Integer) _ As Northwind.ProductsDataTable Return Adapter.GetProductsPaged(startRowIndex, maximumRows) End Function

Puede usar cualquier nombre para los parmetros de entrada de los mtodos de la BLL, pero como veremos pronto, seleccionando usar startRowIndex y maximumrows nos ahorramos un poco de trabajo extra cuando configuramos el ObjectDataSource para que use este mtodo. Paso 4. Configurar el ObjectDataSource para usar paginacin personalizada Con los mtodos DAL y BLL para acceder a un subconjunto particular de registros completos, estamos listos para crear un control GridView que pagine sus registros subyacentes usando la paginacin personalizada. Comenzamos abriendo la pgina EfficientPaging.aspx de la carpeta PagingAndSorting, agregamos un GridView a la pagina y lo configuramos para que use un nuevo control ObjectDataSource, En tutoriales anteriores generalmente tenamos configurado el ObjectDataSource para que usara el mtodo GetProducts de la clase ProductsBLL. Sin embargo, esta vez deseamos usar en su lugar el mtodo GetProductsPaged que devuelve solo un subconjunto particular de registros.

Figura 13. Configurar el ObjectDataSource para usar el mtodo GetProductsPaged de la clase ProductsBLL Como estamos creando un GridView de solo lectura, tome un momento para establecer el mtodo de la lista desplegable de las pestaas INSERT, UPDATE y DELETE en Ninguno Luego el asistente del ObjectDataSource nos solicita los orgenes para los valores de los parmetros de entrada startRowIndex y maximumRows del mtodo GetProductsPaged.

Estos

parmetros

de

entrada

actualmente

son

establecidos

por

el

GridView

automticamente, as que sencillamente deje la fuente establecida en Ninguno y de clic en Finalizar.

Figura 14. Deje la fuente de los parmetros de entrada en Ninguno Despus de completar el asistente del ObjectDataSource, el GridView contiene un BoundField o CheckBoxField para cada uno de los campos de datos de los productos. Sintase libre de modificar la apariencia del GridView. Hemos optado por solamente mostrar los BoundFields ProductName, CategoryName, SupplierName, QuantityPerUnit y UnitPrice. Tambin configuramos el GridView para soportar paginacin seleccionando la casilla de verificacin Habilitar Paginacin en su etiqueta inteligente. Despus de estos cambios, el marcado declarativo del GridView y ObjectDataSource debe lucir similar al siguiente:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames=ProductID DataSourceID=ObjectDataSource1 AllowPaging=True> <Columns> <asp:BoundField DataField=ProductName HeaderText=Product SortExpression=ProductName /> <asp:BoundField DataField=CategoryName HeaderText=Category ReadOnly=True SortExpression=CategoryName /> <asp:BoundField DataField=SupplierName HeaderText=Supplier SortExpression=SupplierName />

<asp:BoundField DataField=QuantityPerUnit HeaderText=Qty/Unit SortExpression=QuantityPerUnit /> <asp:BoundField DataField=UnitPrice DataFormatString={0:c} HeaderText=Price HtmlEncode=False SortExpression=UnitPrice /> </Columns></asp:GridView><asp:ObjectDataSource runat=server OldValuesParameterFormatString=original_{0} SelectMethod=GetProductsPaged TypeName=ProductsBLL> <SelectParameters> <asp:Parameter Name=startRowIndex Type=Int32 /> <asp:Parameter Name=maximumRows Type=Int32 /> </SelectParameters> </asp:ObjectDataSource> ID=ObjectDataSource1

Sin embargo, si usted visita la paginad desde un navegador, el GridView no ser encontrado.

Figura 15. El GridView no es mostrado El GridView est perdido debido a que actualmente el ObjectDataSource est usando los valores de 0 para los parmetros de entrada startRowIndex y maximumRows de GetProductsPaged. Por lo tanto, el resultado de la consulta SQL no devuelve registros y entonces no se muestra el GridView.

Para remediar esto, necesitamos configurar el ObjectDataSource para que use paginacin personalizada. Esto puede ser realizado en los siguientes pasos: Establezca la propiedad EnablePaging del ObjectDataSource en True, esto indica al ObjectDataSource uno que para debe pasar al el SelectMethod ndice de dos inicio parmetros de fila adicionales: especificar

(StartRowIndexParameterName) y uno para especificar las filas mximas (MaximumRowsParameterName). Establecer las propiedades del StartRowIndexParameterName ObjectDataSource correctamente y las MaximumRowsParameterName

propiedades StartRowIndexName y MaximumRowsParameterName indican el nombre de los parmetros de entrada pasados en el SelectMethod para propsitos de paginacin personalizada. Por defecto estos nombres de parmetros son startRowIndex y maximumRows, que es la razn por la cual cuando creamos el mtodo GetProductsPaged en la BLL, usamos estos valores para los parmetros de entrada. Si escoge usar diferentes nombres de parmetros para el mtodo GetProductsPaged de la BLL como por ejemplo startIndex y maxRows, necesita establecer las propiedades StartRowIndexName y MaximumRowsParameterName del ObjectDataSource acordemente (como startIndex para StartRowIndexName y maxRows para MaximumRowsParameterName). Establezca la propiedad SelectCountMethod del ObjectDataSource en el nombre del mtodo que devuelve el total de numero de registros que son paginados (TotalNumberOfProducts) recuerde que el mtodo TotalNumberOfProducts de la clase ProductsBLL devuelve el nmero total de registros paginados usando un mtodo en la DAL que ejecuta la consulta SELECT COUNT(*) FROM Products. Esta informacin es necesaria para que el ObjectDataSource presente correctamente la interfaz de paginacin. Remueva los elementos <asp:Parameter> del startRowIndex y maximumRows del marcado declarativo del ObjectDataSource cuando configuramos el ObjectDataSource desde el asistente, Visual Studio automticamente crea dos elementos <asp:Parameter> para los parmetros de entrada del mtodo GetProductsPaged. Estableciendo la propiedad EnablePaging en True, estos parmetros se pasan automticamente, si ellos tambin aparecen en la sintaxis declarativa, el ObjectDataSource intentara pasar cuatro parmetros para el mtodo GetProductsPaged y dos parmetros para el mtodo

TotalNumberOfProducts. Si olvida remover los elementos <asp:Parameter> cuando visitamos la pagina desde un navegador, se dar un mensaje de error como: ObjectDataSource ObjectDataSource1 could not find a non-generic method TotalNumberOfProducts that has parameters: startRowIndex, maximumRows. Despus de realizar estos cambios, la sintaxis declarativa del ObjectDataSource lucir similar a la siguiente:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged" TypeName="ProductsBLL" EnablePaging="True" SelectCountMethod=" TotalNumberOfProducts"> </asp:ObjectDataSource>

Note que las propiedades EnablePaging y SelectCountMethod han sido definidas y los elementos <asp:Parameter> han sido removidos. La figura 16 muestra una captura de pantalla de la ventana propiedades luego de que se realizan estos cambios.

Figura 16. Configure el control ObjectDataSource para que utilice la paginacin personalizada Despus de hacer estos cambios, visitemos la pgina desde un navegador. Debera ver 10 registros, ordenados alfabticamente. Tmese un momento para ir por las pginas de datos una a la vez. Aunque no hay diferencia visual desde la perspectiva final del usuario entre la paginacin por defecto y la paginacin personalizada, la paginacin personalizada, pagina ms eficientemente grandes cantidades de datos ya que solo devuelve aquellos registros que se necesitan mostrar en una pgina dada.

Figura 17. Los datos ordenados por Nombre de Producto son paginados usando paginacin personalizada Nota: Con la paginacin personalizada, el valor page count devuelto por el mtodo SelectCountMethod del ObjectDataSource es almacenado en el ViewState del GridView. Las otras variables de GridView como el PageIndex, EditIndex, SelectedIndex, la coleccin DataKeys y as sucesivamente son almacenados en el control state, que se persiste independientemente del valor de la propiedad EnableViewState del GridView. Como el valor PageCount es persistido a travs de las devoluciones de datos usando el view state, cuando se utiliza una interfaz de paginacin que incluye un enlace para ir a la ltima pgina, es imperativo que el ViewState del GridView este habilitado. (Si su interfaz de paginacin no incluye un acceso directo a la ltima pagina, puede deshabilitar el ViewState)

Presionando el enlace de la ltima pgina originamos una devolucin de datos e indicamos al GridView que actualice su propiedad PageIndex. Si el enlace de la ltima pgina es presionado, el GridView asigna su propiedad PageIndex al valor de la propiedad PageCount menos uno. Con el ViewState deshabilitado, el valor PageCount se pierda a travs de las devoluciones de datos y el PageIndex es asignado en su lugar al valor entero mximo. Luego el GridView intenta determinar el ndice de inicio de fila multiplicando las propiedades PageSize y PageCount. Esto origina un OverflowException ya que el producto excede el tamao mximo del entero permitido. Implementar paginacin y ordenamiento personalizado La implementacin de nuestra paginacin personalizada requiere que el orden con el cual los datos son paginados sea especificado estticamente cuando creamos el procedimiento almacenado GetProductsPaged. Sin embargo puede notar que la etiqueta inteligente del GridView contiene una casilla de verificacin Habilitar ordenamiento adems de la casilla de Habilitar Paginacin. Desafortunadamente agregar un soporte de ordenamiento al GridView con la implementacin de nuestra paginacin personalizada solo ordenara los registros que actualmente son visualizados en la pgina de datos. Por ejemplo, si configura el GridView para que soporte la paginacin y luego cuando visualizamos la primer pagina de datos, ordena los datos por nombre de producto en orden descendiente, esto aplica el orden de los productos en la pagina 1. Como muestra la figura 18, Carnarvon Tigers es el primer producto cuando ordenamos en sentido contrario al alfabeto, con lo cual se ignoran los otros 71 productos que estn despus de Carnarvon Tigers alfabticamente, solo los registros de la primera pgina son considerados en el ordenamiento.

Figura 18. Solamente son ordenados los datos mostrados en la pgina actual El ordenamiento solo se aplica a la pgina de datos actual porque el ordenamiento ocurre despus que los datos han sido recuperados desde el mtodo GetProductsPaged de la BLL, y este mtodo solo devuelve los registros de la pgina especificada. Para implementar el ordenamiento correctamente, necesitamos pasar la expresin de ordenamiento al mtodo GetProductsPaged para que los datos sean rankeados apropiadamente antes de devolver la pgina de datos especfica. Veremos cmo realizar esto en nuestro prximo tutorial. Implementacin de paginacin y eliminacin personalizada Si habilitamos la funcionalidad de eliminacin en el GridView cuyos datos son paginados usando la tcnica de paginacin personalizada, encontrara que cuando eliminamos el ltimo registro de la ultima pagina, el GridView desaparece en lugar de decrementar apropiadamente el PageIndex del GridView. Para reproducir este error habilitamos la eliminacin para el tutorial que acabamos de crear. Vaya a la pgina 9, que solo tiene un registro cuando paginamos 81 productos, 10 productos a la vez. Elimine este producto. Una vez elimina el ltimo producto, el GridView automticamente debera ir a la pgina 8 y exhibir la funcionalidad como la paginacin por defecto. Sin embargo la paginacin personalizada despus de eliminar el ltimo registro en la ltima pgina, solamente desaparece el GridView de la pantalla. La razn precisa de por qu sucede esto est un

poco ms all del alcance de este tutorial. En resumen es debido a que la siguiente secuencia de pasos es realizada por el GridView cuando se presiona el botn Eliminar: 1. Eliminar el registro 2. Tener los registros apropiados para mostrar especificados por PageIndex y PageSize 3. Asegurarse que el PageIndex no exceda el numero de pginas de datos en el origen de datos, si lo hace decrementar automticamente la propiedad PageIndex del GridView 4. Enlazar la pagina de datos apropiada al GridView usando los registros obtenidos en el paso 2. El problema radica en el hecho que el paso 2 el PageIndex usado cuando grabamos los registros para mostrar es todava el PageIndex de la ultima pagina cuyo nico registro fue eliminado. Por lo tanto en el paso 2, ningn registro es devuelto a la ltima pgina de datos porque no contiene ningn registro. Luego en el paso 3, el GridView revisa que su propiedad PageIndex es ms grande que el total de nmero de pginas en la fuente de datos (ya que hemos eliminado el ltimo registro de la ltima pagina) y por lo tanto decrementa su propiedad PageIndex. En el paso 4 el GridView intenta enlazarse a los datos devueltos en el paso 2, sin embargo en el paso 2 no se devolvieron registros, lo que origina un GridView vacio. Con la paginacin por defecto el problema no se presenta porque en el paso 2 todos los registros son recuperados desde el origen de datos. Para arreglar esto tenemos dos opciones. La primera es crear un controlador de evento para el evento RowDeleted del GridView que determine cuantos registros son mostrados en la pgina que acaba de ser eliminada. Si solo haba uno, entonces el registro borrado debe ser el ltimo y necesitamos decrementar el PageIndex del GridView. Por supuesto solamente deseamos actualizar el PageIndex si la operacin de eliminacin fue realizada exitosamente, esto puede ser determinado asegurndonos que la propiedad e.Exception es Null. Este enfoque funciona porque actualiza el PageIndex despus del paso 1 pero antes del paso 2. Por lo tanto en el paso 2, se devuelve el conjunto de registros adecuado. Para realizar esto use un cdigo como el siguiente:

Protected Sub GridView1_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _ Handles GridView1.RowDeleted ' If we just deleted the last row in the GridView, decrement the PageIndex If e.Exception Is Nothing AndAlso GridView1.Rows.Count = 1 Then ' we just deleted the last row GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1) End If End Sub

Una solucin alternativa es crear un controlador de evento para el evento RowDeleted del ObjectDataSource y establecer la propiedad AffectedRows al valor de 1. Despus de eliminar el registro en el paso 1(pero antes de recuperar los datos en el paso 2) el GridView actualiza su propiedad PageIndex si una o ms filas son afectadas por la operacin. Sin embargo la propiedad AffectedRows no est definida para el ObjectDataSource y por lo tanto este paso es omitido. Una forma de tener este paso ejecutado es establecer manualmente la propiedad AffectedRows si la operacin de eliminacin se completa exitosamente. Esto puede realizarse usando un cdigo como el siguiente:
Protected Sub ObjectDataSource1_Deleted( _ sender As Object, e As ObjectDataSourceStatusEventArgs) _ Handles ObjectDataSource1.Deleted ' If we get back a Boolean value from the DeleteProduct method and it's true, then ' we successfully deleted the product. Set AffectedRows to 1 If TypeOf e.ReturnValue Is Boolean AndAlso CType(e.ReturnValue, Boolean) = True Then e.AffectedRows = 1 End If End Sub

El cdigo para estos controladores de evento puede ser encontrado en la clase de cdigo subyacente del ejemplo EfficientPaging.aspx Comparar el rendimiento de la paginacin por defecto y personalizada Como la paginacin personalizada solo devuelve los registros necesarios, mientras que la paginacin por defecto devuelve todos los registros para cada pgina que est siendo vista, es claro que la paginacin personalizada en ms eficiente que la paginacin por defecto. Pero que tanto es ms eficiente la paginacin personalizada?

Qu tipo de mejoras en el rendimiento vemos con la paginacin personalizada en lugar de la paginacin por defecto? Desafortunadamente, no hay un solo tamao en la respuesta de esto. Las mejoras en el rendimiento dependen del nmero de un nmero de factores, siendo los dos ms importantes el nmero de registros paginados a travs de la carga colocada en el lado del servidor de base de datos y los canales de comunicacin entre el servidor web y el servidor de base de datos. Para pequeas tablas con solo unas pocas docenas de registros, la diferencia del rendimiento puede ser insignificante. Para tablas grandes con miles de cientos de filas, la diferencia de rendimiento es apreciable. El articulo Paginacin Personalizada en ASP.Net 2.0 con SQL Server 2005, contiene algunas pruebas de rendimiento ejecutadas para exhibir las diferencias en rendimiento entre estas dos tcnicas de paginacin cuando se pagina una tabla de datos con 50,000 registros. En estas pruebas examinamos el tiempo de ejecucin de la consulta a nivel de SQL Server (Usando el SQL Profiler) y en la pgina ASP.Net usando las funciones de seguimiento de ASP.Net. Tenga en mente que estas pruebas fueron ejecutadas en mi caja de desarrollo con un solo usuario activo y por lo tanto no son cientficas y no imitan los patrones de carga de sitios web tpicos. No obstante los resultados muestran una relativa diferencia en el tiempo de ejecucin para la paginacin por defecto y personalizada cuando trabajamos con cantidades de datos lo suficientemente grandes.

Como podemos ver, recuperar una pgina de datos particular requiri un promedio de 354 lecturas menos y fue completada en una fraccin del tiempo. En la pgina ASP.Net, personalizar la pgina puede hacer que se presenten los datos en 1/100th de tiempo que cuando usamos la paginacin por defecto. Resumen

La paginacin por defecto es fcil de implementar seleccionando simplemente la casilla de verificacin Habilitar Paginacin en la etiqueta inteligente del control de datos web, pero tanta sencillez viene con un costo de rendimiento. Con la paginacin por defecto, cuando un usuario solicita cualquier pgina de datos, se recuperan todos los registros incluso si solo se muestra una pequea fraccin de ellos. Para combatir esta sobrecarga en el rendimiento el ObjectDataSource ofrece una opcin de paginacin alternativa, la paginacin personalizada. Aunque la paginacin personalizada proporciona una mejora en los problemas de rendimiento de la paginacin por defecto al recuperar solo los registros necesarios para mostrar, esto involucra una implementacin ms compleja de la paginacin personalizada. Primero una consulta debe ser escrita para que acceda correctamente (y eficientemente) al subconjunto de datos especfico de los registros solicitados. Esto puede realizarse de muchas formas: la primera examinando en este tutorial que utiliza la funcin ROW_NUMBER() de SQL Server 2005 para rankear los resultados y luego recuperar solo los resultados cuyo rango esta dentro del rango especificado. Adems necesitamos agregar un medio para determinar el nmero total de registros que estn siendo paginados. Despus de crear estos mtodos en la DAL y BLL, tambin necesitamos configurar el ObjectDataSource para que pueda determinar cuntos registros estn siendo paginados y pueda pasar correctamente los valores StartRowIndex y MaximumRows a la BLL. Aunque implementar la paginacin personalizada requiere una serie de pasos y no es ni cercanamente tan sencilla como la paginacin por defecto, la paginacin personalizada es una necesidad cuando paginamos cantidades de datos lo suficientemente grandes. Como se mostro en los resultados examinados, la paginacin personalizada puede ahorrar segundos en el tiempo de presentacin de la pgina ASP.Net y puede superar la carga del servidor de base de datos para una o ms ordenes de esta magnitud.

26.

ORDENAMIENTO PERSONALIZADO DE DATOS PAGINADOS

En el tutorial anterior aprendimos como implementar paginacin personalizada cuando presentamos datos en una pgina web. En este tutorial veremos como ampliar el ejemplo anterior para incluir soporte para paginacin y ordenamiento personalizado. Introduccin Comparada con la paginacin por defecto, la paginacin personalizada puede mejorar el rendimiento de la paginacin de datos en rdenes de mayor magnitud, haciendo de la implementacin de la paginacin personalizada la opcin por defecto para grandes cantidades de datos. Implementar la paginacin personalizada es ms complicado de implementar que la paginacin por defecto, especialmente ms cuando agregamos ordenamiento al conjunto. En este tutorial ampliaremos el ejemplo anterior para incluir soporte personalizado para la paginacin y ordenamiento. Nota: Como la construccin de este tutorial est basado en el anterior, antes de comenzar tmese un momento para copiar el marcado declarativo dentro del elemento <asp:Content> de la pagina web (EfficientPaging.aspx) del tutorial anterior y cpielo dentro del elemento <asp:Content> en la pgina SortParameter.aspx. Refirase al paso 1 del tutorial Agregar controles de validacin a las Interfaces de Edicin e Insercin para una discusin ms detallada sobre la replicacin de la funcionalidad de una pgina ASP.Net a otra. Paso 1. Reexaminar la tcnica de paginacin personalizada Para que la paginacin personalizada trabaje apropiadamente, debemos implementar alguna tcnica que puede grabar de forma eficiente un subconjunto de registros dado los parmetros Start Row Index y Maximum Rows. Hay un manejo de tcnicas que puede ser usada para alcanzar este objetivo. En el tutorial anterior vimos como realizar esto usando la nueva funcin ranking ROW_NUMBER() de Microsoft SQL Server 2005. En concreto, la funcin ranking ROW_NUMBER() asigna un numero de fila para cada fila devuelta por una consulta que es rankeada por un orden de clasificacin especificado. Luego el subconjunto de registros apropiados es obtenido para una seccin particular de los resultados numerados devueltos. La siguiente consulta muestra cmo usar esta tcnica para recuperar aquellos productos numerados desde 11 a 20 cuando rankeamos los resultados ordenados alfabticamente por el ProductName:

SELECT ProductID, ProductName, ... FROM (SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER (ORDER BY ProductName) AS RowRank FROM Products) AS ProductsWithRowNumbers WHERE RowRank > 10 AND RowRank <= 20

Esta tcnica trabaja bien para paginacin usando un orden de clasificacin especfico (en este caso ordenado alfabticamente por ProductName), pero la consulta necesita ser modificada para mostrar los resultados ordenados por una expresin de ordenamiento diferente. Idealmente, la consulta anterior podra sobrescribirse para usar un parmetro en la clausula OVER, as:
SELECT ProductID, ProductName, ...FROM (SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER (ORDER BY @sortExpression) AS RowRank FROM Products) AS ProductsWithRowNumbers WHERE RowRank > 10 AND RowRank <= 20

Desafortunadamente, la clausula ORDER BY parametrizada no est permitida. En su lugar, debemos crear un procedimiento almacenado que acepte un parmetro de entrada @sortExpression, podemos usar una de las siguientes soluciones: Escriba consultas no modificables para cada una de las expresiones de ordenamiento que pueden ser usadas, luego use las sentencias T-SQL IF/ELSE para determinar que consulta ejecutar. Use una sentencia CASE para proporcionar expresiones ORDER BY dinmicas basadas en el parmetro de entrada @sortExpression, vea la seccin de Resultados de Consultas de Ordenamiento dinmicas en el poder de las sentencias SQL CASE para ms informacin. Trabaje la consulta apropiada como una cadena en el procedimiento almacenado y luego use el procedimiento almacenado system sp_executesql para ejecutar la consulta dinmica. Cada una de estas soluciones tiene algunos inconvenientes. La primera opcin no es tan fcil de mantener como las otras dos, ya que esta requiere que cree una consulta

para cada expresin de ordenamiento posible. Por lo tanto, si decide agregar nuevos campos ordenables al GridView tambin necesitara regresar y actualizar el procedimiento almacenado. El segundo enfoque tiene las mismas sutilezas que involucran al rendimiento cuando ordenamos columnas de la base de datos que no sean cadenas de texto y adems sufre de los mismos problemas de mantenimiento que el primero. La tercera opcin que usa consultas SQL dinmicas, introduce el riesgo de un ataque de ingreso SQL, si el atacante es capaz de ejecutar el procedimiento almacenado pasando en el parmetro de entrada los valores que escoja. Aunque ninguno de estos enfoques es perfecto, creo que la tercera opcin es la mejor de las tres. Con su uso de SQL dinmicos, este ofrece un nivel de flexibilidad que los otros dos no. Por lo tanto un ataque de ingreso SQL puede solamente ser detonado si un atacante es capaz de ejecutar el procedimiento almacenado pasando el parmetro de entrada su eleccin. Como la DAL pasa consultas parametrizadas, ADO.Net proteger estos parmetros que son enviados a la base de datos a travs de la arquitectura, lo que significa que la vulnerabilidad al ataque de inyeccin SQL solamente existe si el atacante puede ejecutar directamente el procedimiento almacenado. Para implementar esta funcionalidad, creamos un nuevo procedimiento almacenado en la base de datos Northwind llamado GetProductsPagedAndSorted. Este procedimiento almacenado solo acepta tres parmetros de entrada: @sortExpression, un parmetro de entrada de tipo nvarchar(100) que especifica cmo deben ser ordenados los resultados y es ingresado directamente despus del texto ORDER BY en la clusula OVER y @startRowIndex y @maximumRows, los mismos dos parmetros de entrada enteros del procedimiento almacenado GetProductsPaged examinado en el tutorial anterior. Creamos el procedimiento almacenado GetProductsPagedAndSorted usando el siguiente script:
CREATE PROCEDURE dbo.GetProductsPagedAndSorted ( @sortExpression nvarchar(100), @startRowIndex int, @maximumRows int ) AS -- Make sure a @sortExpression is specified

IF LEN(@sortExpression) = 0 SET @sortExpression = 'ProductID' -- Issue query DECLARE @sql nvarchar(4000) SET @sql = 'SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, CategoryName, SupplierName FROM (SELECT ProductID, ProductName, p.SupplierID, p.CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, c.CategoryName, s.CompanyName AS SupplierName, ROW_NUMBER() OVER (ORDER BY ' + @sortExpression + ') AS RowRank FROM Products AS p INNER JOIN Categories AS c ON c.CategoryID = p.CategoryID INNER JOIN Suppliers AS s ON s.SupplierID = p.SupplierID) AS ProductsWithRowNumbers WHERE RowRank > ' + CONVERT(nvarchar(10), @startRowIndex) + ' AND RowRank <= (' + CONVERT(nvarchar(10), @startRowIndex) + ' + ' + CONVERT(nvarchar(10), @maximumRows) + ')' -- Execute the SQL query EXEC sp_executesql @sql

El procedimiento almacenado comienza asegurndose que se ha especificado un valor para el parmetro @sortExpression. Si este esta perdido los productos son rankeados por el ProductID. Luego es construida la consulta SQL dinmica. Note que la consulta dinmica difiere ligeramente de las consultas anteriores usadas para recuperar todas las filas desde la tabla Products. En los ejemplos anteriores obtuvimos el nombre de la categora y proveedor de cada producto usando una subconsulta. Esta decisin se tomo en el tutorial Creacin de una capa de acceso de datos y se realizo en vez de usar JOIN porque el TableAdapter no puede crear automticamente los mtodos INSERT, UPDATE y DELETE asociados para esas consultas. Sin embargo el procedimiento almacenado GetProductsPagedAndSorted debe usar JOINs para que los resultados sean ordenados por los nombres de las categoras o proveedores. Esta consulta dinmica es construida concatenando las partes de consulta esttica y los parmetros @sortExpression, @startRowIndex y @maximumRows. Como

@startRowIndex y @maximumRows son parmetros enteros deben ser convertidos en nvarchar para que sean correctamente concatenados. Una vez que la consulta dinmica ha sido construida, esta es ejecutada por medio de sp_executesql. Tmese un momento para probar este procedimiento almacenado con diferentes valores para los parmetros @sortExpression, @startRowIndex y @maximumRows. Desde el explorador de servidores, haga clic derecho sobre el nombre del procedimiento almacenado y seleccione Ejecutar. Esto abrir el cuadro de dialogo de ejecucin de procedimiento almacenado en el cual puede ingresar los parmetros de entrada (Ver Figura 1). Para ordenar los resultados por el nombre de categora use CategoryName como el valor del parmetro @sortExpression, para ordenar por el nombre de la compaa del proveedor, use CompanyName. Despus de proporcionar los valores a los parmetros haga clic en OK. Los resultados son mostrados en la ventana Output. La figura 2 muestra los resultados cuando recuperamos los productos rankeados del 11 al 20 cuando son ordenados descendentemente por UnitPrice.

Figura 1. Pruebe diferentes valores para los tres parmetros de entrada del procedimiento almacenado

Figura 2. Los resultados del procedimiento almacenado son mostrados en la ventana Output Nota: Cuando rankeamos los resultados por la columna ORDER BY especificada en la clausula OVER, SQL Server debe ordenar los resultados. Esta es una operacin rpida si hay un ndice clustered sobre las columnas de los resultados que estn siendo ordenados o si hay un ndice covering, pero en caso contrario puede ser ms lento. Para mejorar el rendimiento para consultas muy grandes, considere agregar un ndice non-clustered para la columna por la cual los resultados son ordenados. Refirase a Funciones Ranking y rendimiento en SQL Server 2005 para ms detalles. Paso 2. Ampliar las capas de acceso a datos y de lgica de negocios Con la creacin del procedimiento almacenado GetProductsPagedAndSorted, nuestro siguiente paso es proporcionar un medio para ejecutar el procedimiento almacenado a travs de nuestra arquitectura de aplicacin. Esto implica agregar un mtodo apropiado a la DAL y BLL. Comience agregando un mtodo a la DAL. Abra el DataSet tipiado Northwind.xsd, haga clic derecho sobre el ProductsTableAdapter y seleccione la opcin Agregar Consulta en el men contextual. Como hicimos en el tutorial anterior, deseamos configurar este nuevo mtodo de DAL para que use un procedimiento almacenado existente, en este caso GetProductsPagedAndSorted. Comience indicando que desea que el nuevo mtodo del TableAdapter use un procedimiento almacenado existente.

Figura 3. Seleccione usar un procedimiento almacenado existente Para especificar el procedimiento almacenado a usar, seleccionamos el procedimiento almacenado GetProductsPagedAndSorted en la lista desplegable de la siguiente pantalla.

Figura 4. Use el procedimiento almacenado GetProductsPagedAndSorted Este procedimiento almacenado devuelve un conjunto de registros como su resultado, en la siguiente pantalla, indica que este devuelve datos tabulares.

Figura 5. Indique que el procedimiento almacenado devuelve datos tabulares Finalmente, cree mtodos DAL que usen los enfoques de Devolver un DataTable y Llenar un DataTable, nombrando los mtodos GetProductsPagedAndSorted y FillProductsPagedAndSorted respectivamente.

Figura 6. Seleccione los nombres de los mtodos Ahora que hemos ampliado la DAL, estamos listos para ir a la BLL. Abra el archivo de clase ProductsBLL y agregue un nuevo mtodo, GetProductsPagedAndSorted. Este mtodo necesita aceptar tres parmetros de entrada sortExpression, startRowIndex y maximumRows y debe llamar dentro de la DAL al mtodo GetProductsPagedAndSorted, de la siguiente manera:

<System.ComponentModel.DataObjectMethodAttribute( _ System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsPagedAndSorted(ByVal sortExpression As String, _ ByVal startRowIndex As Integer, ByVal maximumRows As Integer) _ As Northwind.ProductsDataTable Return Adapter.GetProductsPagedAndSorted(sortExpression, startRowIndex, maximumRows) End Function

Paso 3. Configurar el ObjectDataSource para pasar el parmetro SortExpression Habiendo ampliado la DAL y BLL para incluir los mtodos que usara el procedimiento almacenado GetProductsPagedAndSorted, todo lo que nos queda es configurar el ObjectDataSource de la pgina SortParameter.aspx para que use el nuevo mtodo BLL y pase el parmetro SortExpression basado en la columna por la cual el usuario ha solicitado ordenar los datos. Comience cambiando el SelectMethod del ObjectDataSource de GetProductsPaged a GetProductsPagedAndSorted. Esto puede realizarse por medio del asistente de Configuracin de origen de datos, desde la ventana propiedades o directamente en la sintaxis declarativa. Luego necesitamos proporcionar un valor para la propiedad SortParameterName del ObjectDataSource. Si esta propiedades est definida, el ObjectDataSource intenta pasarla en la propiedad SortExpression del GridView al SelectMethod. En concreto, el ObjectDataSource busca un parmetro de entrada cuyo nombre sea igual al valor de la propiedad SortParameterName. Como el mtodo GetProductsPagedAndSorted tiene el parmetro de entrada de la expresin de ordenamiento llamado sortExpression, defina la propiedad SortExpression del ObjectDataSource en sortExpression. Despus de realizar estos dos cambios el marcado declarativo del ObjectDataSource lucir similar al siguiente:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProductsPagedAndSorted" EnablePaging="True" SelectCountMethod="TotalNumberOfProducts" SortParameterName="sortExpression"> </asp:ObjectDataSource>

Nota: Al igual que en el tutorial anterior, asegrese que el ObjectDataSource no incluye los parmetros de entrada sortExpression, startRowIndex o maximumRows en su coleccin SelectParameters. Para habilitar el ordenamiento en el GridView, simplemente seleccione la casilla de verificacin Habilitar Ordenamiento en la etiqueta inteligente del GridView, lo cual establece la propiedad AllowingSorting del GridView en True originando que el texto del encabezado de cada columna sea presentado como un LinkButton. Cuando el usuario final presiona uno de los LinkButton del encabezado, se origina una devolucin de datos y se siguen los siguientes pasos: 1. El GridView actualiza su propiedad SortExpression al valor SortExpression del campo cuyo enlace del encabezado fue presionado 2. El ObjectDataSource invoca el mtodo GetProductsPagedAndSorted de la BLL, pasando la propiedad SortExpression del GridView como el valor para el parmetro de entrada sortExpression del mtodo (junto con los valores de los parmetros de entrada @startRowIndex y maximumRows apropiados ) 3. La BLL invoca el mtodo GetProductsPagedAndSorted de la DAL 4. La DAL ejecuta el procedimiento almacenado GetProductsPagedAndSorted, pasando el parmetro @sortExpression (junto con los valores del parmetro de entrada @startRowIndex y @maximumRows) 5. El procedimiento almacenado recupera el subconjunto de datos apropiado, que luego devuelve al ObjectDataSource, estos datos luego son enlazados al GridView, presentado en HTML y enviados al usuario final. La figura 7 muestra la primera pgina de resultados cuando los ordenamos ascendentemente por UnitPrice

Figura 7. Los resultados son ordenados por UnitPrice Aunque la implementacin actual puede ordenar correctamente los resultados por nombre del producto, nombre de categora, cantidad por unidad y precio unitario, al intentar ordenar los resultados por el nombre del proveedor se origina una excepcin en tiempo de ejecucin (Ver Figura 8).

Figura 8. Intentar ordenar los resultados por Proveedor origina una excepcin en tiempo de ejecucin Esta excepcin ocurre porque el SortExpression del BoundField SupplierName del GridView est establecido en SupplierName. Sin embargo el nombre del proveedor en la tabla Suppliers actualmente llamado CompanyName tiene un alias de nombre de

columna de SupplierName. Sin embargo la clausula OVER usada por la funcin ROW_NUMBER() no puede usar el alias y debe usar el nombre actual de la columna. Por lo tanto, cambie el SortExpression del BoundField SupplierName de SupplierName a CompanyName (Ver Figura 9). Como muestra la figura 10, despus de este cambio los resultados pueden ser ordenados por el proveedor.

Figura 9. Cambie el SortExpression del BoundField SupplierName a CompanyName

Figura 10. Ahora los resultados pueden ser ordenados por Proveedor Resumen

La implementacin de la paginacin personalizada examinada en el tutorial anterior requiere que el orden con el cual los resultados son ordenados sea especificado en tiempo de diseo. En concreto, esto significa que la implementacin de la paginacin personalizada que implementamos no podra proporcionar capacidades de ordenamiento al mismo tiempo. En este tutorial superamos esta limitacin ampliando el procedimiento almacenado desde el principio para incluir el parmetro de entrada @sortExpression con el cual los resultados pueden ser ordenados. Despus de crear este procedimiento almacenado y crear nuevos mtodos en la DAL y BLL, somos capaces de implementar un GridView que ofrezca paginacin y ordenamiento personalizado configurando el ObjectDataSource para pasar la propiedad SortExpression actual del GridView al SelectMethod de la BLL.

27.

CREAR UNA INTERFAZ DE USUARIO DE ORDENAMIENTO

PERSONALIZADO
Cuando mostramos una larga lista de datos ordenados, puede ser de mucho ayuda agrupar los datos relacionados introduciendo una fila separadora. En este tutorial veremos cmo crear una interfaz de usuario de ordenamiento. Introduccin Cuando mostramos una larga lista de datos ordenados donde solo hay un puado de valores diferentes en la columna ordenada, un usuario podra encontrar difcil discernir donde ocurren exactamente las diferencias de los limites. Por ejemplo hay 81 productos, pero solamente nueve opciones de categora diferentes (ocho categoras nicas mas la opcin NULL). Considere el caso de un usuario que est interesado en examinar todos los productos que estn bajo la categora Seafood. Desde la pgina que muestra todos los productos en un solo GridView, el usuario podra decidir que es mejor ordenar los productos por categora, agrupando todos los productos de Seafood. Despus de ordenar por categora luego el usuario necesita buscar a travs de la lista, buscando donde inician y terminan los productos agrupados por Seafood. Como los resultados son ordenados alfabticamente encontrar los productos de Seafood no es difcil, pero esto requiere una inspeccin cercana de la lista de tems en el Grid. Para ayudar a resaltar los lmites entre los grupos ordenados, muchos sitios web emplean una interfaz de usuario que agrega un separador entre dichos grupos. Los separadores al igual que unos mostrados en el paso 1 ayudan al usuario a encontrar rpidamente un grupo en particular e identificar sus lmites, as como tambin observar que grupos distintos de datos existen.

Figura 1. Cada grupo de categora est claramente identificado En este tutorial veremos cmo crear una interfaz de usuario de ordenamiento. Paso 1. Crear un GridView normal y ordenable Antes de explorar como ampliar el GridView para proporcionar esta interfaz de usuario mejorada, primero crearemos un GridView ordenable estndar que muestre todos los productos. Comenzamos abriendo la pgina CustomSortingUI.aspx de la carpeta PagingAndSorting. Agregamos un GridView a la pagina estableciendo su propiedad ID en ProductList y lo enlzamos a un nuevo ObjectDataSource. Configure el ObjectDataSource para que utilice el mtodo GetProducts() de la clase ProductsBLL para la seleccin de registros. Luego configure el GridView para que solamente y contenga y los el BoundFields

ProductName,

CategoryName,

SupplierName

UnitPrice

CheckBoxField

Discontinued. Finalmente configure el GridView para que soporte el ordenamiento seleccionando la casilla de verificacin Habilitar Ordenamiento en la etiqueta inteligente del GridView (o estableciendo la propiedad AllowSorting en True).Despus de realizar estas adiciones a la pgina CustomSortingUI.aspx, el marcado declarativo lucir similar al siguiente:
<asp:GridView ID="ProductList" runat="server" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="ProductID"

DataSourceID="ObjectDataSource1" EnableViewState="False"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL"> </asp:ObjectDataSource>

Tmese un momento para ver nuestro progreso hasta aqu por medio de un navegador. La figura 2 muestra el GridView ordenable cuando sus datos son ordenados por categora en orden alfabtico.

Figura 2. Los datos del GridView ordenable son ordenados por categora

Paso 2. Explorar tcnicas para agregar separadores de filas Con el GridView ordenable genrico completo, todo lo que nos resta es ser capaces de agregar los separadores de filas en el GridView antes de cada grupo ordenable nico. Pero como podemos ingresar a las filas del GridView? Esencialmente necesitamos iterar las filas del GridView, determinar las diferencias ocurridas entre los valores en la columna ordenada y luego agregar el separador de fila apropiado. Cuando pensamos en este problema, vemos natural que la solucin este en algn lugar en el controlador de evento RowDataBound del GridView. Como discutimos en el tutorial Formato Personalizado basado en los datos, este controlador de evento comnmente es usado cuando aplicamos formato a nivel de fila basados en los datos de la fila. Sin embargo, el controlador de evento RowDataBound no es la solucin aqu, ya que las filas no pueden ser agregadas al GridView mediante programacin por medio de este controlador de evento. De hecho la coleccin Rows del GridView es de solo lectura. Para agregar filas adicionales al GridView tenemos tres opciones: Agregar los metadatos de los separadores de filas a los datos actuales que son enlazados al GridView Luego que el GridView haya enlazado los datos, agregar instancias TableRow adicionales a la coleccin del control GridView Crear un control de servidor personalizado que ampli el control GridView y sobrescriba los mtodos responsables de la construccin de la estructura del GridView Crear un control de servidor personalizado debe ser el mejor enfoque si esta funcionalidad es necesaria en muchas pginas web o en muchos sitios web. Sin embargo, esto implica un poco de cdigo y una exploracin a fondo de los trabajos internos del GridView. Por lo tanto no consideraremos esta opcin para este tutorial. Las otras dos opciones agregan separadores de fila a los datos actuales que estn siendo enlazados al GridView y manipulan la coleccin del control GridView despus que ha sido enlazado, atacan el problema de forma diferente y por lo tanto merecen discusin. Agregar filas a los datos enlazados al GridView

Cuando el GridView es enlazado a su origen de datos, este crea un GridViewRow por cada registro devuelto por el origen de datos. Por lo tanto podemos ingresar los separadores de fila necesarios agregando registros separadores al origen de datos antes de enlazarlo al GridView. La figura 3 ilustra este concepto.

Figura 3. Una tcnica que involucra agregar separadores de filas al origen de datos. Use el trmino de registros separadores en comillas porque no es un registro separador especial, aunque debemos resaltar de alguna manera que un registro en particular en el origen de datos sirve como separador en lugar de cmo una fila de datos normal. Para nuestros ejemplos, estamos enlazando una instancia ProductsDataTable al

GridView, que est compuesto de ProductRows. Podemos resaltar un registro como un separador de fila, estableciendo su propiedad CategoryID en -1 (ya que este valor no existe normalmente). Para utilizar esta tcnica necesitamos realizar los siguientes pasos: 1. Recuperar mediante programacin los datos enlazados al GridView (una instancia ProductsDataTable) 2. Ordenar los datos basados en las propiedades SortExpression y SortDirection del GridView. 3. Iterar los ProductsRows en el ProductsDataTable, buscando donde estn las diferencias en la columna ordenada. 4. En cada limite del grupo, ingresar un registro separador es decir una instancia ProductRow en el DataTable, una que tenga su CategoryID establecido en -1 ( o cualquier designacin que haya decidi para marcar un registro como un registro separador). 5. Despus ingresar los separadores de filas, enlace mediante programacin los datos al GridView. Ademas de estos cinco pasos, tambin necesitamos proporcionar un controlador de evento para el evento RowDataBound del GridView. Aqu verificamos cada DataRow y determinamos si es un separador de fila, uno cuya definicin de CategoryID sea -1. Adems probablemente deseamos ajustar su formato o el texto mostrado en las celdas. Usando esta tcnica para ingresar los lmites del grupo ordenado se requiere un poco mas de trabajo que con el resaltado anteriormente, ya que tambin necesitamos proporcionar un controlador para el evento Sorting del GridView y realizar seguimiento de los valores SortExpression y SortDirection. Manipulando la coleccin del control GridView despus que ha sido enlazado En lugar de enviar los datos antes que sean enlazados al GridView, podemos agregar los separadores de filas despus que los datos han sido enlazados al GridView. El proceso de enlazar los datos se construye basados en la jerarqua del control GridView, que en realidad simplemente es una instancia Table compuesta de una coleccin de filas, cada una de las cuales est compuesta de una coleccin de celdas. Especficamente la coleccin del control GridView contiene un objeto Table como su

raz, un GridViewRow (que es derivado de la clase TableRow) para cada registro en el DataSource enlazado al GridView y un objeto TableCell en cada instancia GridViewRow para cada campo de datos en el DataSource. Para agregar separadores de filas antes de cada grupo ordenable, podemos manipular directamente esta jerarqua de control una vez que haya sido creada. Podemos confiar en que la jerarqua del control GridView ha sido creada por ltima vez cuando la pgina se esta presentando. Por lo tanto este enfoque sobrescribe el mtodo Render de la clase Page, en el punto en que la jerarqua final del control GridView es actualizada para incluir los separadores de filas necesarios. La figura 4 muestra este proceso.

Figura 4. Una tcnica alternativa manipula la jerarqua del control GridView Para este tutorial, usaremos el ltimo enfoque para personalizar la experiencia de ordenamiento del usuario. Paso 3. Agregar separadores de filas a la jerarqua del control GridView Ya que solamente deseamos agregar separadores de filas a la jerarqua del control GridView despus que la jerarqua del control ha sido creada y creada por ltima vez en

la pgina que visitamos, deseamos realizar esta adicin al final del ciclo de vida de la pagina, pero antes que la jerarqua actual del control GridView sea presentada en HTML. El ltimo momento posible de realizar esto es en el evento Render de la clase Page, el cual puede sobrescribir en nuestro cdigo de clase subyacente usando la siguiente definicin de mtodo:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter) ' Add code to manipulate the GridView control hierarchy MyBase.Render(writer) End Sub

Cuando el mtodo Render de la clase original Page es invocado base.Render(writer) cada uno de los controles en la pgina ser presentado, generando el marcado basado en su jerarqua de control. Por lo tanto es imperativo que llamemos base.Render(writter), para que la pgina sea presentada y que manipulemos la jerarqua del control GridView antes de llamar base.Render(writter), para que los separadores de filas sean agregados a la jerarqua del control GridView antes que sea presentado. Para ingresar los encabezados del grupo ordenables primero necesitamos asegurarnos que el usuario ha solicitado que los datos sean ordenados. Por defecto los contenidos del GridView no estn ordenados y por lo tanto no necesitamos ingresar ningn encabezado de grupo ordenado. Nota: Si desea que el GridView sea ordenado por una columna en particular, cuando la pgina es cargada por primera vez, llame al mtodo Sort del GridView en la primera visita a la pgina (pero no en las siguientes devoluciones de datos). Para realizar esto llamamos al controlador de evento Page_Load dentro de un condicional if (Page.IsPostBack). Regrese de nuevo al tutorial Paginacion y Ordenamiento de datos para mas informacin sobre el mtodo Sort. Asumiendo que los datos han sido ordenados, nuestra siguiente tarea es determinar porque columna fueron ordenados los datos y luego escanear las filas buscando diferencias en los valores de las columnas. El siguiente cdigo se asegura que los datos hayan sido ordenados y encuentra la columna por la cual los datos han sido ordenados:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter) ' Only add the sorting UI if the GridView is sorted

If Not String.IsNullOrEmpty(ProductList.SortExpression) Then ' Determine the index and HeaderText of the column that 'the data is sorted by Dim sortColumnIndex As Integer = -1 Dim sortColumnHeaderText As String = String.Empty For i As Integer = 0 To ProductList.Columns.Count 1 If ProductList.Columns(i).SortExpression.CompareTo( _ ProductList.SortExpression) = 0 Then sortColumnIndex = i sortColumnHeaderText = ProductList.Columns(i).HeaderText Exit For End If Next ' TODO: Scan the rows for differences in the sorted column s values End Sub

Si el GridView no ha sido ordenado, la propiedad SortExpression del GridView aun no se ha definido. Por lo tanto, solo deseamos agregar separadores de filas si la propiedad tiene algn valor. Si es as necesitamos determinar el ndice de la columna con la que los datos son ordenados. Esto es realizado recorriendo la coleccin Columns del GridView, buscando la columna cuya propiedad SortExpression sea igual a la propiedad SortExpression del GridView. Adems del ndice de la columna, tambin tomamos la propiedad HeaderText que es usada cuando mostramos los separadores de filas. Con el ndice de la columna con la cual los datos son ordenados, el paso final es enumerar las filas del GridView. Para cada fila necesitamos determinar si el valor de la columna ordenada es diferente del valor de la columna de la fila ordenada previamente. Si es as necesitamos agregar una nueva instancia GridViewRow dentro de la jerarqua del control. Esto se realiza con el siguiente cdigo:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter) ' Only add the sorting UI if the GridView is sorted If Not String.IsNullOrEmpty(ProductList.SortExpression) Then ' ... Code for finding the sorted column index removed for brevity ... ' Reference the Table the GridView has been rendered into Dim gridTable As Table = CType(ProductList.Controls(0), Table) ' Enumerate each TableRow, adding a sorting UI header if ' the sorted value has changed

Dim lastValue As String = String.Empty For Each gvr As GridViewRow In ProductList.Rows Dim currentValue As String = gvr.Cells(sortColumnIndex).Text If lastValue.CompareTo(currentValue) <> 0 Then ' there's been a change in value in the sorted column Dim rowIndex As Integer = gridTable.Rows.GetRowIndex(gvr) ' Add a new sort header row Dim sortRow As New GridViewRow(rowIndex, rowIndex, _ DataControlRowType.DataRow, DataControlRowState.Normal) Dim sortCell As New TableCell() sortCell.ColumnSpan = ProductList.Columns.Count sortCell.Text = String.Format("{0}: {1}", _ sortColumnHeaderText, currentValue) sortCell.CssClass = "SortHeaderRowStyle" ' Add sortCell to sortRow, and sortRow to gridTable sortRow.Cells.Add(sortCell) gridTable.Controls.AddAt(rowIndex, sortRow) ' Update lastValue lastValue = currentValue End If Next End If MyBase.Render(writer) End Sub

Este cdigo inicia referenciando mediante programacin el objeto Table encontrado en la raz de la jerarqua del control GridView y crea una cadena variable llamada lastValue. LastValue es usado para comparar el valor de la columna de la fila ordenada con el valor de la fila previa. Luego la coleccin Rows del GridView es enumerada y para cada fila el valor de la columna ordenada es almacenado en la variable currentValue. Nota: Para determinar el valor en particular de una columna de una fila ordenada usamos la propiedad Text de la celda. Esta trabaja bien para los BoundFields pero no trabaja como deseamos para los TemplateFields, CheckBoxFields y as sucesivamente. Veremos cmo realizar esto para los campos alternativos del GridView en breve. Luego las variables currentValue y lastValue son comparadas. Si son diferentes necesitamos agregar un nuevo separador de fila a la jerarqua del control. Esto es

realizado determinando el ndice del GridViewRow en la coleccin Rows del objeto Table, creando nuevas instancias GridViewRow y TableCell y luego agregando el TableCell y el GridViewRow a la jerarqua del control. Note que solamente el TableCell del separador de fila es formateado de tal forma que se extiende al ancho total del GridView, es formateado usando la clase CSS del SortHeaderRowStyle y tiene su propiedad Text definida como muestra el nombre del grupo ordenado (como con Category) y el valor en el grupo (como con Beverages). Finalmente lastValue es actualizado al valor del currentValue. La clase CSS usada para formatear el SortHeaderRowStyle de la fila del encabezado del grupo a ordenar necesita ser especificada en el archivo Style.css. Sintase libre de usar cualquier definicin de estilo que le parezca. Se uso la siguiente:
.SortHeaderRowStyle { background-color: #c00; text-align: left; font-weight: bold; color: White; }

Con el cdigo actual, la interfaz de ordenamiento agrega encabezados a los grupos de ordenamiento cuando se ordena por cualquier BoundField (Vea la figura 5, que muestra una captura de pantalla cuando la ordenamos por proveedor). Sin embargo, cuando ordenamos por cualquier otro tipo de campo (como por un CheckBoxField o TemplateField), los encabezados de los grupos ordenados no son encontrados (Ver Figura 6).

Figura 5. La interfaz de ordenamiento incluye encabezados de grupos de orden cuando ordenamos por BoundFields

Figura 6. Los encabezados de los grupos de orden se pierden cuando ordenamos un CheckBoxField La razn por la que los encabezados de los grupos de orden se pierden cuando ordenamos por un CheckBoxField es debido a que el cdigo actualmente solo usa la propiedad Text del TableCell para determinar el valor de la columna ordenada para cada fila. Para los CheckBoxFields la propiedad Text del TableCell es una cadena vaca; en su lugar el valor vlido para un control web CheckBox reside dentro de la coleccin Controls del TableCell.

Para manejar otros tipos de campos diferentes a los BoundFields, necesitamos ampliar el cdigo donde la variable currentValue es asignada para verificar la existencia de un CheckBox siguiente:
Dim currentValue As String = String.Empty If gvr.Cells(sortColumnIndex).Controls.Count > 0 Then If TypeOf gvr.Cells(sortColumnIndex).Controls(0) Is CheckBox Then If CType(gvr.Cells(sortColumnIndex).Controls(0), CheckBox).Checked Then currentValue = "Yes" Else currentValue = "No" End If ' ... Add other checks here if using columns with other ' End If Else currentValue = gvr.Cells(sortColumnIndex).Text End If Web controls in them (Calendars, DropDownLists, etc.) ...

en

la

coleccin

Controls

del

TableCell. remplazamos

En ese

lugar cdigo

de

usar el

currentValue=gvr.Cells(sortColumnIndex).Text,

con

Este cdigo examina la columna ordenada TableCell de la fila actual para determinar si hay cualquier control en la coleccin Controls. Si hay y el primer control es un CheckBox, la variable currentValue es estableca en Yes o No, dependiendo de la propiedad Checked del CheckBox. De otra forma, el valor es tomado de la propiedad Text del TableCell. Esta lgica puede ser replicada para manejar el ordenamiento para cualquier TemplateFields que pueda existir en el GridView. Con el cdigo agregado anteriormente, los encabezados del grupo de ordenamiento son presentados cuando ordenamos por el CheckBoxField (Ver Figura 7).

Figura 7. Los encabezados de los grupos de orden ahora son presentados cuando ordenamos un CheckBoxField Nota: Si hay productos en la base de datos con valores NULL para los campos CategoryID, SupplierID o UnitPrice, aquellos valores aparecern por defecto como cadenas vacas en el GridView, lo que significa que el texto de la fila separadora para aquellos productos con valores NULL se leer como Category (es decir ningn nombre despus de Category: como con Category:Beverages). Si desea mostrar un valor aqu, podemos definir la propiedad NullDisplayText del BoundField al texto que desea mostrar o puede agregar una sentencia adicional en el mtodo Render cuando asignamos el currentValue a la propiedad Text del separador de fila. Resumen El GridView no incluye muchas opciones predefinidas para personalizar la interfaz de ordenamiento. Sin embargo, con un poco de cdigo de bajo nivel, es posible modificar la jerarqua del control GridView para crear una interfaz ms personalizada. En este tutorial vimos como agregar una fila separadora de un grupo ordenable a un GridView ordenable, que haga ms fcil identificar y delimitar los distintos grupos. Para ejemplos adicionales de interfaces de ordenamiento personalizadas, verifique la entrada del blog Algunos Tips y trucos de ordenamiento con el GridView de ASP.Net 2.0 de Scott Gunthrie.

ACCIONES DE BOTONES PERSONALIZADAS

28.

AGREGAR Y RESPONDER A LOS BOTONES DEL GRIDVIEW

En este tutorial veremos cmo agregar botones personalizados, a una plantilla y a los campos de un control GridView o DetailsView. En concreto, contruiremos una interfaz que tenga un FormView que permita al usuario paginar a travs de los proveedores. Introduccin Aunque muchos escenarios de informes involucran acceso de solo lectura a los informes de datos, no es raro que los reportes incluyan la habilidad de realizar accionar basados en lo datos mostrados. Normalmente esto involucra agregar un control web Button, LinkButton o ImageButton a cada registro mostrado en el reporte, que al ser presionado origine una devolucin de datos e involucre algn cdigo del lado del servidor. Editar y eliminar los datos registro por registro es el ejemplo comn ms bsico. De hecho, como vimos en el tutorial Resumen sobre la Insercin, Actualizacin y Eliminacin de datos, editar y eliminar es muy comn en los controles GridView, DetailsView y FormView ya que pueden soportar dicha funcionalidad sin la necesidad de escribir una sola lnea de cdigo. Adems de los botones Edit y Delete, los controles GridView, DetailsView y FormView tambin pueden incluir Buttons, LinkButtons o ImageButtons que cuando son presionados, realizan alguna lgica personalizada en el lado del servidor. En este tutorial veremos cmo agregar botones personalizados, a un Template y a los campos de un control GridView o DetailsView. Para un proveedor determinado, el FormView mostrara informacin relacionada con el proveedor junto con un control web Button, que si es presionado marcara todos sus productos asociados como descontinuados. Adems un GridView que muestra aquellos productos proporcionados por el proveedor seleccionado, conteniendo en cada fila Buttons Price y Discount Price, que al ser presionados aumentan o reducen el UnitPrice de los productos en un 10% (Ver Figura 1).

Figura 1. El FormView y GridView contienen Buttons que realizan acciones personalizadas Paso 1. Agregar las pginas web de los tutoriales de botones Antes de ver como agregar un botn personalizado, primero tomese un momento para crear las pginas ASP.Net en nuestro proyecto de sitio web que necesitaremos para este tutorial. Comenzamos agregando una nueva carpeta llamada CustomButtons. Luego agregue las siguientes dos pginas ASP.Net a dicha carpeta, asegurndose de asociar cada pgina con la pgina maestra Site.master: Default.aspx CustomButtons.aspx

Figura 2. Agregar las pginas ASP.Net para los tutoriales relacionados con botones personalizados Al igual que en las otras carpetas, Default.aspx en la carpeta CustomButtons mostrara los tutoriales de esta seccin. Recuerde que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Por lo tanto, agregue este control de usuario a Default.aspx arrastrndolo desde el explorador de soluciones hasta la vista diseo de la pgina.

Figura 3. Agregar el control de usuario SectionLevelTutorialListing.ascx a Default.aspx Por ltimo, agregue las pginas como entradas al archivo Web.sitemap.

Especficamente, agregue el siguiente marcado despus del <siteMapNode> Paginacin y Ordenamiento:


<siteMapNode title="Adding Custom Buttons" description="Samples of Reports that Include Buttons for Performing Server-Side Actions" url="~/CustomButtons/Default.aspx"> <siteMapNode title="Using ButtonFields and Buttons in Templates" description="Examines how to add custom Buttons, LinkButtons, or ImageButtons as ButtonFields or within templates." url="~/CustomButtons/CustomButtons.aspx" /> </siteMapNode>

Despus de actualizar el Web.sitemap, tmese un momento para ver los tutoriales del sitio web por medio de un navegador. El men de la izquierda ahora incluye elementos para los tutoriales de Adicin de Botones Personalizados

Figura 4. El SiteMap ahora incluye la entrada para el tutorial de botones personalizados Paso 2. Agregar un FormView que muestre los proveedores Comenzaremos este tutorial agregando el FormView que muestra los proveedores. Como discutimos en la introduccin, este FormView permitir al usuario paginar a travs de los proveedores, mostrando los productos suministrados por el proveedor en

un GridView. Adems este FormView incluir un Button, que al ser presionado, marcara todos los productos del proveedor como descontinuados. Antes de preocuparnos con la adicin del button personalizado al FormView, primero crearemos solo el FormView para que muestre la informacin del proveedor. Comience abriendo la pgina CustomButtons.aspx en la carpeta CustomButtons. Agregue un FormView a la pgina arrastrndolo desde el cuadro de herramientas hasta el diseador y establezca su propiedad ID en Suppliers. Desde la etiqueta inteligente del FormView, seleccione crear un nuevo ObjectDataSource llamado SuppliersDataSource.

Figura 5. Crear un nuevo ObjectDataSource llamado SuppliersDataSource Configure este nuevo ObjectDataSource para que su consulta sea desde el mtodo GetSuppliers() de la clase SuppliersBLL (Ver Figura 6). Como este FormView no proporciona una interfaz para actualizar la informacin del proveedor, seleccione la opcin (Ninguno) en la lista desplegable de la pestaa UPDATE.

Figura 6. Configure el origen de datos para que use el mtodo GetSuppliers() de la clase SuppliersBLL Despus de configurar el ObjectDataSource, Visual Studio generara un

InsertItemTemplate, EditItemTemplate y un ItemTemplate para el FormView. Remueva el InsertItemTemplate y el EditItemTemplate y modifique el ItemTemplate para que solamente muestre el nombre de la compaa del proveedor y su nmero telefnico. Finalmente habilite el soporte de paginacin para el FormView, seleccionando la casilla de verificacin Habilitar Paginacin desde su etiqueta inteligente (o definiendo su propiedad AllowingPaging en True). Despus de estos cambios el marcado declarativo de nuestra pgina lucir similar al siguiente:
<asp:FormView ID="Suppliers" runat="server" DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource" EnableViewState="False" AllowPaging="True"> <ItemTemplate> <h3> <asp:Label ID="CompanyName" runat="server" Text='<%# Bind("CompanyName") %>' /> </h3> <b>Phone:</b> <asp:Label ID="PhoneLabel" runat="server" Text='<%# Bind("Phone") %>' /> </ItemTemplate> </asp:FormView> <asp:ObjectDataSource ID="SuppliersDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetSuppliers" TypeName="SuppliersBLL">

</asp:ObjectDataSource>

La figura 7 muestra la pgina CustomButtons.aspx cuando la visualizamos por medio de n navegador.

Figura 7. El FormView muestra los campos CompanyName y Phone para el proveedor actualmente seleccionado. Paso 3. Agregar un GridView que muestre los productos del proveedor seleccionado Antes de agregar a la plantilla del FormView el botn Descontinuar todos los productos, primero agregamos un GridView debajo del FormView que muestre los productos suministrados por el proveedor seleccionado. Para realizar esto, agregue un GridView a la pgina, estableciendo su propiedad ID en SuppliersProducts, y agregue un nuevo ObjectDataSource llamado SuppliersProductsDataSource.

Figura 8. Crear un nuevo ObjectDataSource llamado SuppliersProductsDataSource

Configure

este

ObjectDataSource

para

que

use

el

mtodo

GetProductsBySupplierID(supplierID) de la clase ProductsBLL. Aunque este GridView permitir ajustar el precio de un producto, no usara las funciones de edicin o eliminacin predefinidas para el GridView. Por lo tanto podemos establecer las listas desplegables de las pestaas UPDATE, INSERT y DELETE del ObjectDataSource en (None).

Figura 9. Configurar el ObjectDataSource para que use el mtodo GetProductsBySupplierID(supplierID) de la clase ProductsBLL Como el mtodo GetProductsBySupplierID(supplierID) acepta un parmetro de entrada, el asistente del ObjectDataSource nos solicita el origen del valor de este parmetro. Para pasar el valor SupplierID desde el FormView, establece la lista desplegable del origen del parmetro en Control y la lista desplegable del ControlID en Suppliers (el ID del FormView creado en el paso 2).

Figura 10. Indique que el parmetro supplierID debe venir del control FormView Suppliers Despus de completar el asistente del ObjectDataSource, el GridView contendr un BoundField o CheckBoxField para cada uno de los campos de los productos. Lo ajustaremos para mostrar solamente los BoundFields ProductName y UnitPrice junto con el CheckBoxField Discontinued; adems formatearemos el BoundField UnitPrice para que su texto tenga formato de moneda. El marcado declarativo del GridView y de su ObjectDataSource SuppliersProductsDataSource lucir similar al siguiente marcado:
<asp:GridView ID="SuppliersProducts" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="SuppliersProductsDataSource" EnableViewState="False" runat="server"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" DataFormatString="{0:C}" HtmlEncode="False" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="SuppliersProductsDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsBySupplierID" TypeName="ProductsBLL">

<SelectParameters> <asp:ControlParameter ControlID="Suppliers" Name="supplierID" PropertyName="SelectedValue" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Hasta este punto nuestro tutorial muestra un informe Maestro/Detalle, permitiendo al usuario escoger un proveedor desde el FormView en la parte superior y ver los productos suministrados por ese proveedor a travs de un GridView en la parte inferior. La Figura 1 muestra una captura de pantalla cuando seleccionamos el Proveedor Tokyo Trades en el FormView.

Figura 11. Los productos del proveedor seleccionado se muestran en el GridView Paso 4. Crear mtodos en la DAL y BLL para descontinuar todos los productos de un proveedor Antes que podamos agregar un Button al FormView para que al ser presionado, descontinue todos los productos de un proveedor, primero necesitamos agregar un mtodo a la DAL y a la BLL que realice esta accin. En concreto, este mtodo ser llamado DiscontinueAllProductsForSupplier(supplierID). Cuando el Button del FormView sea presionado, invocaremos este mtodo en la capa lgica de negocios pasando el SupplierID del proveedor seleccionado, que emitir una sentencia UPDATE a la base de datos que descontinu los productos de un proveedor especfico.

Como hemos hecho en los tutoriales anteriores, usaremos el enfoque usado anteriormente, comenzamos creando el mtodo en la DAL, luego el mtodo en la BLL y finalmente la funcionalidad de la pgina ASP.Net. Abra el DataSet tipiado Northwind.xsd en la carpeta App_Code/DAL y agregue un nuevo mtodo al ProductsTableAdapter (haga clic derecho sobre el ProductsTableAdapter y seleccione Agregar Consulta). Haciendo esto abriremos el asistente de configuracin de consultas del TableAdapter que nos llevara a travs del proceso de agregar un nuevo mtodo. Comenzamos indicando a nuestro mtodo DAL que use una sentencia SQL ad-hoc.

Figura 12. Crear el mtodo DAL usando una sentencia SQL ad-hoc Luego el asistente nos solicita el tipo de consulta que vamos a crear. Como el mtodo DisontinuedAllForSupplier(supplierID) necesita actualizar la tabla Products de la base de datos, estableciendo el campo Discontinued en 1 para todos los productos suministrados por el supplierID especificado, necesitamos crear una consulta que actualice datos.

Figura 13. Seleccione el tipo de consulta UPDATE La siguiente pantalla del asistente nos proporciona la sentencia UPDATE existente en el TableAdapter, que actualiza cada uno de los campos definidos en el DataTable Products. Remplace este texto de consulta con la siguiente sentencia:
UPDATE [Products] SET Discontinued = 1 WHERE SupplierID = @SupplierID

Despus de ingresar esta consulta y presionar Siguiente, la ltima pantalla del asistente nos solicita el nombre del nuevo mtodo, usamos DisontinuedAllProductsForSupplier. Complete el asistente presionando el botn Finalizar. Al regresar al Diseador del DataSet debemos ver un nuevo mtodo en el ProductsTableAdapter llamado DiscontinuedAllProductsForSupplier(@supplierID).

Figura

14.

Llame

al

nuevo

mtodo

de

la

DAL

DiscontinuedAllProductsForSupplier(@supplierID). Con el mtodo DiscontinuedAllProductsForSupplier(@supplierID) creado en la capa de acceso a datos, nuestra siguiente tarea es crear el mtodo DiscontinuedAllProductsForSupplier(@supplierID) en la capa lgica de negocios. Para realizar esto, abra el archivo de clase ProductsBLL y agregue lo siguiente:
Public Function DiscontinueAllProductsForSupplier(supplierID As Integer) As Integer Return Adapter.DiscontinueAllProductsForSupplier(supplierID) End Function

Este

mtodo

solamente

llama

al

mtodo

DiscontinuedAllProductsForSupplier(@supplierID) de la DAL, pasando tambin el valor del parmetro supplierID proporcionado. Si hay reglas de negocio que solamente permitan que los productos de un proveedor sean descontinuados bajo ciertas circunstancias, estas reglas deben ser implementadas aqu en la BLL. Nota: A diferencia de la recarga UpdateProducts en la clase ProductsBLL, la definicin del mtodo DiscontinuedAllProductsForSupplier(@supplierID) no incluye el atributo DataObjectMethodAttribute (<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.Data ObjectMethodType.Update, Boolean)>). Esto se opone al mtodo DiscontinuedAllProductsForSupplier(@supplierID) desde el asistente de configuracin del origen de datos del ObjectDataSource en la lista desplegable de la pestaa UPDATE.

Omitimos

este

atributo

porque

llamaremos

al

mtodo

DiscontinuedAllProductsForSupplier(@supplierID) directamente desde un controlador de evento en nuestra pgina ASP.Net. Paso 5. Agregar un Button Descontinuar todos los productos al FormView Con el mtodo DiscontinuedAllProductsForSupplier(@supplierID) en la DAL y en la BLL completo, el paso final para agregar la habilidad de descontinuar todos los productos del proveedor seleccionado es agregar un control web Button al ItemTemplate del FormView. Agregaremos un Button despus del nmero telefnico del proveedor con el texto del Button en Descontinuar todos los productos y un valor de la propiedad ID de DiscontinuedAllProductsForSupplier. Podemos agregar este control web Button por medio del diseador haciendo clic en el enlace Editar Templates en la etiqueta inteligente del FormView (Vea la Figura 15), o directamente en la sintaxis declarativa.

Figura 15. Agregar un control web Button Descontinuar todos los productos al ItemTemplate del FormView Cuando el botn es presionado por un usuario que visita la pagina, se origina una devolucin de datos y se genera el evento ItemCommand del FormView. Para ejecutar un cdigo personalizado en respuesta a que el Button ha sido presionado, podemos crear un controlador de evento para este evento. Entendiendo que el evento ItemCommand se genera cuando cualquier control web Button, LinkButton o ImageButton es presionado dentro del FormView. Lo que significa que cuando el usuario se mueva de una pgina a otra en el FormView, se genera el evento ItemCommand, al igual que cuando el usuario presiona New, Delete o Update en un FormView que soporte la insercin, eliminacin y actualizacin.

Como el evento ItemCommand se genera independientemente del Button que es presionado, en el controlador de evento necesitamos una forma de determinar si el Button Descontinuar todos los productos fue presionado o si fue algn otro Button. Para realizar esto, podemos definir la propiedad CommandName del control web Button en algn valor que lo identifique. Cuando el Button sea presionado, este valor de CommandName es pasado al controlador de evento ItemCommand, permitindonos determinar si el Button Descontinuar todos los Productos fue el botn presionado. Defina la propiedad CommandName del Button Descontinuar todos los Productos en DiscontinuedProducts. Finalmente usaremos un cuadro de dialogo en el lado del servidor para asegurarnos que el usuario realmente desea descontinuar los productos del proveedor seleccionado. Como vimos en el tutorial Agregar una confirmacin en el lado del cliente cuando eliminamos, esto puede ser realizado con un poco de JavaScript. En concreto, establecemos la propiedad OnClientClick del control web Button en return confirm('This

will mark _all_ of this supplier\'s products as discontinued. Are you certain you want to do this?')
Despus de realizar estos cambios, el marcado declarativo del FormView lucir similar al siguiente:
<asp:FormView ID="Suppliers" runat="server" DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource" EnableViewState="False" AllowPaging="True"> <ItemTemplate> <h3><asp:Label ID="CompanyName" runat="server" Text='<%# Bind("CompanyName") %>'></asp:Label></h3> <b>Phone:</b> <asp:Label ID="PhoneLabel" runat="server" Text='<%# Bind("Phone") %>' /> <br /> <asp:Button ID="DiscontinueAllProductsForSupplier" runat="server" CommandName="DiscontinueProducts" Text="Discontinue All Products" OnClientClick="return confirm('This will mark _all_ of this supplier\'s products as discontinued. Are you certain you want to do this?');" /> </ItemTemplate> </asp:FormView>

Luego cree un controlador de evento para el evento ItemCommand del FormView. En este controlador de evento primero necesitamos determinar si el Button Discontinuar todos los Productos fue presionado. Si es as, deseamos crear una instancia de la clase ProductsBLL e invocar su mtodo DiscontinuedAllProductsForSupplier(supplierID), pasando el supplierID seleccionado en el FormView:
Protected Sub Suppliers_ItemCommand(sender As Object, e As FormViewCommandEventArgs) _ Handles Suppliers.ItemCommand If e.CommandName.CompareTo("DiscontinueProducts") = 0 Then ' The "Discontinue All Products" Button was clicked. ' Invoke the ProductsBLL.DiscontinueAllProductsForSupplier(supplierID) method ' First, get the SupplierID selected in the FormView Dim supplierID As Integer = CType(Suppliers.SelectedValue, Integer) ' Next, create an instance of the ProductsBLL class Dim productInfo As New ProductsBLL() ' Finally, invoke the DiscontinueAllProductsForSupplier(supplierID) method productInfo.DiscontinueAllProductsForSupplier(supplierID) End If End Sub

Tenga en cuenta que puede accederse al SupplierID del proveedor actualmente seleccionado en el FormView usando la propiedad SelectedValue del FormView. La propiedad SelectedValue devuelve el valor del primer valor clave del registro que est siendo mostrado en el FormView. La propiedad DataKeysNames del FormView indica los campos de datos desde los cuales son extrados los valores de datos clave, esta es establecida automticamente por Visual Studio en SupplierID cuando enlazamos el ObjectDataSource al FormView anteriormente en el paso 2. Con el controlador de evento ItemCommand creado, tmese un momento para probar la pgina. Busque el proveedor Cooperativa de Quesos Las Cabras (es el quinto proveedor en el FormView). Este proveedor suministra dos productos, Queso Cabrales y Queso Manchego La Pastora; ninguno esta descontinuado. Imagine que la Cooperativa de Quesos Las Cabras ha salido del negocio y por lo tanto sus productos son descontinuados. Presione el botn Descontinuar todos los productos. Esto mostrara un cuadro de confirmacin en el lado del cliente (Ver Figura 16).

Figura 16. La Cooperativa de Quesos LasCabras suministra dos productos Si hacemos clic en Ok del cuadro de confirmacin del lado del cliente, la solicitud del formulario continuara, originando una devolucin de datos en la cual se genera el evento ItemCommand del FormView. El controlador de evento que creamos se ejecutara, invocando al mtodo DiscontinuedAllProductsForSupplier(supplierID) y se descontinuan los productos Queso Cabrales y Queso Manchego La Pastora. Si ha deshabilitado el View State del GridView, el GridView se enlazara nuevamente a los datos subyacentes almacenados en cada devolucin de datos, por lo tanto inmediatamente es actualizado se refleja que estos productos estn descontinuados (Ver Figura 17). Sin embargo, si no ha deshabilitado el View State en el GridView, necesitamos enlazar manualmente los datos al GridView despus que se realiza este cambio. Para realizar esto, sencillamente llame al mtodo DataBind() del GridView inmediatamente despus de invocar al mtodo DiscontinuedAllProductsForSupplier(supplierID).

Figura 17. Despus de presionar el botn Descontinuar todos los productos, los productos del proveedor son actualizados correctamente Paso 6. Crear una recarga UpdateProducts en la capa lgica de negocios para ajustar el precio de un producto Al igual que con el Button Discontinue All Products en el FormView, con el fin de incrementar o decrementar el precio de un producto en el GridView, necesitamos agregar los mtodos apropiados en la capa de Acceso a datos y en la capa de lgica de negocios. Como ya tenemos un mtodo que actualiza una sola fila de producto en la DAL, podemos aprovechar dicha funcionalidad creando una nueva recarga para el mtodo UpdateProduct en la BLL. Nuestra recarga UpdateProduct anterior toma una combinacin de campos del producto como entradas de valores escalares y luego actualiza solamente esos campos para el producto especificado. Para esta recarga variaremos ligeramente su estndar y en su lugar pasamos el ProductID del producto y el porcentaje con el cual ajustar el UnitPrice (lo opuesto es pasar el nuevo UnitPrice ajustado por s mismo). Este enfoque simplifica el cdigo necesario para escribir en la clase de cdigo subyacente de la pagina ASP.Net, ya que tenemos todo para determinar el UnitPrice del producto actual. La recarga UpdateProduct de este tutorial se muestra a continuacin:
Public Function UpdateProduct _ (unitPriceAdjustmentPercentage As Decimal, productID As Integer) As Boolean Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID) If products.Count = 0 Then ' no matching record found, return false Return False

End If Dim product As Northwind.ProductsRow = products(0) ' Adjust the UnitPrice by the specified percentage (if it's not NULL) If Not product.IsUnitPriceNull() Then product.UnitPrice *= unitPriceAdjustmentPercentage End If ' Update the product record Dim rowsAffected As Integer = Adapter.Update(product) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function

Esta recarga recupera la informacin relacionada con el producto especificado por medio del mtodo GetProductByProductID(productID). Luego verifica para ver si el UnitPrice del producto es asignado a un valor NULL de la base de datos. Si es as, el precio de la izquierda es inalterable; sin embargo si el valor de UnitPrice es un valor non-Null, el mtodo actualiza el UnitPrice del producto con el porcentaje especificado (unitPriceAdjustementPercent). Paso 7. Agregar los botones de Incrementar y Decrementar al GridView El GridView (y el DetailsView) estn hechos de una coleccin de campos. Adems de los BoundFields, CheckBoxFields y TemplateFields, ASP.Net incluye el ButtonField, el cual como su nombre lo implica es presentado como una columna con un Button, LinkButton o ImageButton para cada fila. De forma similar al FormView, presionando cualquier Button dentro del GridView, los botones de paginacin, edicin o eliminacin, botones de ordenamiento y as sucesivamente se origina una devolucin de datos y se genera el evento RowCommand del GridView. El ButtonField tiene una propiedad CommandName que asigna el valor especificado a cada una de las propiedades CommandName de sus botones. Al igual que el FormView, el valor CommandName es usado por el controlador del evento RowCommand para determinar que botn fue presionado. Agregaremos dos nuevos BoundFields al GridView, un botn con el texto Price +10% y el otro con el texto Price -10%. Para agregar estos BoundFields, presione el enlace Edit Columns en la etiqueta inteligente del GridView, seleccione en la lista superior de la izquierda el tipo de campo ButtonField y presione Add Button.

Figura 18. Agregar dos BoundFields al GridView Mueva los dos BoundFields para que aparezcan como los dos primeros campos del GridView, establezca las propiedades Text de estos BoundFields en Price+10% y Price10% y las propiedades CommandName en IncreasePrice y DecreasePrice, respectivamente. Por defecto, un ButtonField presenta sus columnas de botones como LinkButtons. Sin embargo, esto puede cambiarse por medio de la propiedad ButtonType del BoundField. Haremos que los dos ButtonsFields sean presentados como botones de presion normales, por lo tanto establecemos la propiedad ButtonType en Button. La Figura 19 muestra el cuadro de dialogo Fields despus de que se han realizado los cambios, siguiendo esta el marcado declarativo del GridView.

Figura 19. Configure las propiedades Text, CommandName y ButtonType de los BoundFields
<asp:GridView ID="SuppliersProducts" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="SuppliersProductsDataSource" EnableViewState="False"> <Columns> <asp:ButtonField ButtonType="Button" CommandName="IncreasePrice" Text="Price +10%" /> <asp:ButtonField ButtonType="Button" CommandName="DecreasePrice" Text="Price -10%" /> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" DataFormatString="{0:C}" HtmlEncode="False" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView>

Con los BoundFields creados, el paso final es crear un controlador de evento para el evento RowCommand del GridView. Este controlador de evento, se genera porque si los botones Price +10% o Price -10% son presionados, necesitamos determinar el ProductID para la fila cuyo botn fue presionado y luego invocar el mtodo

UpdateProduct de la clase ProductsBLL pasando el UnitPrice con el porcentaje de ajuste apropiado junto con el ProductID. El siguiente cdigo realiza estas tareas:
Protected Sub SuppliersProducts_RowCommand _ (sender As Object, e As GridViewCommandEventArgs) _ Handles SuppliersProducts.RowCommand If e.CommandName.CompareTo("IncreasePrice") = 0 OrElse _ e.CommandName.CompareTo("DecreasePrice") = 0 Then ' The Increase Price or Decrease Price Button has been clicked ' Determine the ID of the product whose price was adjusted Dim productID As Integer = Convert.ToInt32( _ SuppliersProducts.DataKeys(Convert.ToInt32(e.CommandArgument)).Value) ' Determine how much to adjust the price Dim percentageAdjust As Decimal If e.CommandName.CompareTo("IncreasePrice") = 0 Then percentageAdjust = 1.1 Else percentageAdjust = 0.9 End If ' Adjust the price Dim productInfo As New ProductsBLL() productInfo.UpdateProduct(percentageAdjust, productID) End If End Sub

Con el fin de determinar el ProductID de la fila cuyo botn Price +10% o Price -10% fue presionado, necesitamos consultar la coleccin DataKeys del GridView. Esta coleccin almacena los valores de los campos especificados en la propiedad DataKeysNames para cada fila del GridView. Como la propiedad DataKeysNames del GridView fue establecida en ProductID por Visual Studio cuando enlazamos el ObjectDataSource al GridView, DataKeys(rowIndex).Value proporciona el ProductID para el rowIndex especificado. El ButtonField pasa automticamente el rowIndex de la fila cuyo botn fue presionado a travs del parmetro e.CommandArgument. Por lo tanto, para determinar el ProductID de la fila cuyo botn Price +10% o Price -10% fue presionado, usamos:
Convert.ToInt32(SuppliersProducts.DataKeys(Convert.ToInt32(e.CommandArgument)).Value).

Al igual que con el botn Discontinue All Products, si deshabilita el View State del GridView, el GridView estar siendo enlazado a los datos subyacentes almacenado en cada devolucin de datos y por lo tanto inmediatamente sea actualizado se reflejara un cambio en el precio que ocurre despus de presionar los botones. Sin embargo si no ha deshabilitado el ViewState en el GridView, necesitara enlazar manualmente los datos al GridView despus de realizar este cambio. Para realizar esto, sencillamente hacemos un llamado al mtodo DataBind() del GridView inmediatamente despus e invocar al mtodo UpdateProduct. La figura 20 muestra la pagina cuando visualizamos los productos proporcionados por el Grandma Kellys Homestead. La Figura 20 muestra los resultados despus de que el botn Price +10% ha sido presionado dos veces para el Grandmas Boysenberry Spread y el botn Price -10% una vez para Northwoods Cranberry Sauce.

Figura 20. El GridView incluye los botones Price +10% y Price -10%

Figura 21. Los precios para el primer y el tercer producto han sido actualizados por medio de los botones Price +10% y Price -10%

Nota: El GridView (y el DetailsView) tambin pueden tener Buttons, LinkButtons o ImageButtons agregados a sus TemplateFields. Como con el BoundField, estos botones cuando son presionados, inducen una devolucin de datos, generando el evento RowCommand del GridView. Sin embargo, cuando agregamos botones en un TemplateField, el CommandArgument del Button no est definido automticamente al ndice de la fila como cuando usamos ButtonFields. Si necesita determinar el ndice de la fila del botn que fue presionado dentro del controlador de evento RowCommand, necesitara definir manualmente la propiedad CommandArgument del Button en su sintaxis declarativa dentro del TemplateField, usando un cdigo como:
<asp:Button runat="server" ... CommandArgument='<%# CType(Container,

GridViewRow).RowIndex %>' />.

Resumen Los controles GridView, DetailsView y FormView pueden incluir Buttons, LinkButtons o ImageButtons. Los botones cuando son presionados originan una devolucin de datos y generan el evento ItemCommand en los controles FormView y DetailsView y el evento RowCommand en el GridView. Estos controles web de datos tienen una funcionalidad predefinida para manejar acciones comunes relacionadas con comandos, como eliminar o editar registros. Sin embargo, tambin puede usar botones para que al ser presionados, respondan con la ejecucin de nuestro propio cdigo personalizado. Para realizar esto, necesitamos crear un controlador de evento para el evento ItemCommand o RowCommand. En este controlador de evento primero verificamos el valor CommandName entrante para determinar que botn fue presionado y luego toma la accin personalizada apropiada. En este tutorial vimos como usar botones y BoundFields para descontinuar todos los productos para un proveedor especifico o para incrementar o decrementar el precio de un producto particular en 10%.

MOSTRANDO DATOS CON EL DATALIST Y EL REPEATER

29.

MOSTRANDO DATOS CON LOS CONTROLES DATALIST Y REPEATER

En los tutoriales anteriores usamos el control GridView para mostrar datos. A partir de este tutorial, observaremos la construccin de enfoques comunes de presentacin con los controles DataList y Repeater, comenzando con los fundamentos para la presentacin de datos con estos controles. Introduccin En todos los ejemplos a lo largo de los ltimos 28 tutoriales, si necesitbamos mostrar diversos registros desde una fuente de datos, recurramos al control GridView. El GridView presenta una fila por cada registro en la fuente de datos, mostrando los campos de datos de los registros en columnas. Aunque el GridView hace que sea fcil visualizar, ir a travs de las pginas, ordenar, editar y eliminar datos: su apariencia es un poco cuadrada. Por otra parte, el marcado responsable de la estructura del GridView es fijado incluyendo una <table> HTML con una fila de tabla (<tr>) para cada registro y una celda de tabla (<td>) para cada campo. Para proporcionar un grado mayor de personalizacin en la apariencia y el marcado presentado cuando mostramos varios registros, Asp.Net 2.0 ofrece los controles DataList y Repeater (ambos controles tambin estn disponibles en la versin Asp.Net 1.x) Los controles DataList y Repeater presentan sus contenidos usando plantillas en lugar de BoundFields, CheckBoxFields y as sucesivamente. Al igual que el GridView, el DataList se presenta como un HTML <table>, pero permite mostrar mltiples registros del origen de datos por fila de la tabla. Por el contrario, el Repeater no presenta ningn marcado adicional que el que especifica explcitamente y es un candidato ideal cuando necesitamos un control ms preciso sobre el marcado emitido. En la prxima docena de tutoriales o ms, veremos la construccin de enfoques comunes de presentacin de datos con los controles DataList y Repeater, comenzando con la presentacin bsica de datos con las plantillas de estos controles. Veremos cmo dar formato a estos controles, como modificar el diseo de los registros del origen de datos con el DataList, escenarios comunes de Maestro/Detalle, formas para editar y eliminar datos; como ir a travs de las pginas de registros, entre otros. Paso 1. Agregar las pginas web tutoriales DataList y Repeater

Antes de empezar este tutorial, primero tommonos un momento para agregar las pginas Asp.Net que necesitaremos en este y los prximos tutoriales que se ocupan de mostrar los datos utilizando el control DataList y Repeater. Comencemos creando una nueva carpeta en el proyecto llamada DataList RepeaterBasics. Luego agregue las siguientes cinco pginas web a la carpeta y configrelas para que todas ellas utilicen la pgina maestra Site.master: Default.aspx Basics.aspx Formatting.aspx RepeatColumnAndDirection.aspx NestedControls.aspx

Figura 1. Crear una carpeta DataListRepeaterBasics y agregar las pginas tutoriales Asp.Net Abra la pgina Default.aspx y arrastre el control de usuario

SectionLevelTutorialListing.ascx desde la carpeta UserControls hasta la superficie de diseo. Este control de usuario, que creamos en el tutorial Pginas Maestras y Navegacin del Sitio, enumera el mapa del sitio y muestra los tutoriales de la seccin actual en una lista con vietas.

Figura 2. Agregue el control de usuario SectionLevelTutorialListing.ascx a Default.aspx Para tener una lista con vietas que muestre los tutoriales con el DataList y el Repeater que vamos a crear, necesitamos agregarlos al mapa del sitio. Abra el archivo Web.SiteMap y agregue el siguiente marcado despus del marcado del nodo Adicin de botones personalizados en el mapa del sitio:
<siteMapNode Title=Displaying Data with the DataList and Repeater .description=Samples of Reports that Use the DataList and Repeater Controls .url=~/DataListRepeaterBasics/Defaults.aspx> .<siteMapNode ..Title=Basic Examples ..description=Examines the basics for displaying data using the DataList and Repeater controls. ..url=~/DataListRepeaterBasics/Basics.aspx/> .<siteMapNode ..Title:Formating ..description=Learn how to format the DataList and the Web Controls within the DataList and Repeaters ..templates. ..url=~/DataListRepeaterBasics/Formatting.aspx/> .<siteMapNode .. Title:Adjusting the DataListLayout ..description=Illustrates how to alter the DataLists layout, showing multiple data source records per table row. ..url=~/DataListRepeaterBasics/RepeatColumnAndDirection.aspx/> .<siteMapNode

.. Title:Nesting a Repeater within a DataList ..description=Learn how to nest a Repeater within the template of a DataList. ..url=~/DataListRepeaterBasics/NestedControls.aspx/> </siteMapNode>

Figura 3. Actualizar el mapa del sitio para incluir las nuevas pginas ASP.Net Paso2. Mostrar informacin del producto con el DataList De forma similar al FormView, el resultado presentado por el control DataList depende de las plantillas en lugar de los BoundFields, CheckBoundFields, y as sucesivamente. A diferencia del FormView, el DataList est diseado para mostrar un conjunto de registros en lugar de uno solo. Comenzaremos este tutorial observando cmo enlazar la informacin de los productos al DataList. Comencemos abriendo la pgina Basics.aspx en la carpeta DataListRepeaterBasics. Luego arrastre un control DataList desde el cuadro de herramientas hasta el diseador. Como se muestra en la figura 4, antes de especificar las plantillas del DataList, el diseador lo muestra como un cuadro gris.

Figura 4. Arrastre el DataList desde el cuadro de herramientas hasta el diseador Desde la etiqueta inteligente del control DataList, agregue un nuevo ObjectDataSource y configrelo para que utilice el mtodo GetProducts de la clase ProductsBLL. Como en este tutorial estamos creando un control DataList de solo lectura, establezca en el asistente las listas desplegables de las pestaas INSERT, UPDATE y DELETE en ninguno.

Figura 5. Opte por crear un nuevo ObjectDataSource

Figura 6. Configure el ObjectDataSource para que utilice la clase ProductsBLL

Figura 7. Recupere la informacin acerca de todos los productos usando el mtodo GetProducts Despus de configurar el ObjectDataSource y asociarlo con el DataList por medio de su etiqueta inteligente, Visual Studio creara automticamente un ItemTemplate en el control DataList que muestre el nombre y el valor de cada campo de datos devueltos por el origen de datos (Vea el marcado a continuacin). Por defecto la apariencia del

ItemTemplate es idntica a las plantillas que se crean automticamente cuando enlazamos un origen de datos al FormView por medio del diseador.
<asp: DataList ID=DataList1 runat=server DataKeyField=ProductID DataSourceID=ObjectDataSource1 EnableViewState=False> <ItemTemplate> ProductID: <asp:Label ID=ProductIDLabel runat=server Text=<% # Eval(ProductID) %> /><br/> ProductName: <asp:Label ID=ProductNameLabel runat=server Text=<% # Eval(ProductName) %> /><br/> SupplierID: CategoryID: <asp:Label ID=SupplierIDLabel runat=server Text=<% # Eval(SupplierID) %> /><br/> <asp:Label ID=CategoryIDLabel runat=server Text=<% # Eval(CategoryID) %> /><br/> QuantityPerUnit: UnitPrice: UnitsInStock: <asp:Label ID=QuantityPerUnitLabel runat=server Text=<% # Eval(QuantityPerUnit) %> /><br/> <asp:Label ID=UnitPriceLabel runat=server Text=<% # Eval(UnitPrice) %> /><br/> <asp:Label ID=UnitsInStockLabel runat=server Text=<% # Eval(UnitsInStock) %> /><br/> UnitsOnOrder: <asp:Label ID=UnitsOnOrderLabel runat=server Text=<% # Eval(UnitsOnOrder) %> /><br/> ReorderLevel: <asp:Label ID=ReorderLevelLabel runat=server Text=<% # Eval(ReorderLevel) %> /><br/> Discontinued: <asp:Label ID=DiscontinuedLabel runat=server Text=<% # Eval(Discontinued) %> /><br/> CategoryName:<asp:Label ID=CategoryNameLabel runat=server Text=<% # Eval(CategoryName) %> /><br/> SupplierName: <asp:Label ID=SupplierNameLabel runat=server Text=<% # Eval(SupplierName) %> /><br/> <br/> </ItemTemplate> </asp:DataList> <asp:ObjectDataSource ID=ObjectDataSource1 runat=server OldValuesParameterFormatString=original_{0} Selectmethod=GetProducts TypeName=ProductsBLL> </asp:ObjectDataSource>

Nota: Recuerde que cuando enlazamos un origen de datos a un control FormView por medio de su etiqueta inteligente, Visual Studio crea un ItemTemplate, InsertItemTemplate y un EditItemTemplate. Sin embargo, con el DataList solamente se crea un ItemTemplate. Esto es debido a que el DataList no tiene el mismo soporte predefinido de edicin e insercin ofrecido por el FormView. El DataList contiene eventos de edicin y eliminacin relacionados y el soporte de edicin y eliminacin puede ser agregado con un poco de cdigo, pero no hay un soporte de edicin sencillo de fuera de caja como con el FormView. Veremos cmo incluir el soporte de edicin y eliminacin en el DataList en un tutorial futuro. Nos tomaremos un momento para mejorar el aspecto de esta plantilla. En lugar de mostrar todos los campos de datos, solamente mostraremos el nombre del producto, proveedor, categora, cantidad por unidad y precio unitario. usando un <table> debajo del encabezado. Para realizar estos cambios usted puede usar las funciones de edicin de plantillas en el diseador desde la etiqueta inteligente del DataList haciendo clic sobre el enlace Editar plantillas o puede modificar la plantilla manualmente por medio de la sintaxis declarativa de la pgina. Si utiliza la opcin Editar plantillas en el diseador, el marcado resultante podra no coincidir con el siguiente marcado, pero cuando se ve a travs de un navegador la captura de pantalla debe ser similar a la que se muestra en la figura 8.
<asp:DataList ID="DataList1" runat="server" DataKeyField="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <ItemTemplate> <h4><asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>' /></h4> <table border="0"> <tr> <td class="ProductPropertyLabel">Category:</td> <td><asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>' /></td> <td class="ProductPropertyLabel">Supplier:</td> <td><asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>' /></td> </tr> <tr>

Por otra parte

mostraremos el nombre en un encabezado <h4> y disearemos los campos restantes

<td class="ProductPropertyLabel">Qty/Unit:</td> <td><asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Eval("QuantityPerUnit") %>' /></td> <td class="ProductPropertyLabel">Price:</td> <td><asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>' /></td> </tr> </table> </ItemTemplate> </asp:DataList>

Nota: El ejemplo anterior utiliza los controles web Label cuya propiedad Text es asignada al valor de la sintaxis de enlace de datos. Alternativamente podramos haber omitido por completo los Labels, escribiendo solamente la sintaxis de enlace de datos. Es decir en vez de usar <asp:Label ID="CategoryNameLabel" runat="server" Text='<%#
Eval("CategoryName") %>' /> podramos haber utilizado en su lugar la sintaxis declarativa

<%# Eval("CategoryName") %>. Sin embargo dejar los controles web Label ofrece dos ventajas. En primer lugar proporciona una va ms fcil para dar formato a los datos basados en los datos, como veremos en el siguiente tutorial. En segundo lugar la opcin Editar plantillas en el diseador no muestra la sintaxis declarativa de enlace de datos que aparezca fuera de un control web. En su lugar la interfaz de edicin de plantillas est diseada para facilitar el trabajo con marcado esttico y controles web y asume que cualquier enlace de datos ha sido realizado por medio del cuadro de dialogo Editar enlace de datos el cual es accesible desde las etiquetas inteligentes de los controles web. Por lo tanto cuando se trabaja con el control DataList, el cual proporciona la opcin de editar las plantillas por medio del diseador, es preferible usar los controles Label para que el contenido sea accesible por medio de la interfaz de edicin de plantillas. Como veremos en breve, el Repeater requiere que el contenido de las plantillas sea editado desde la vista cdigo fuente. En consecuencia cuando elaboramos las plantillas del Repeater a menudo omitiremos los controles web Label a menos que sepamos de la necesidad de dar formato a la apariencia de los datos de texto enlazados basndonos en la lgica de programacin.

Figura 8. La salida de cada producto es presentada usando el ItemTemplate del DataList Paso 3. Mejorar la apariencia del DataList Al igual que los controles GridView, el DataList ofrece una serie de propiedades relacionadas con el estilo, tales como Font, ForeColor, BackColor, CssClass, ItemStyle, AlternatingItemStyle, SelectedItemStyle y as sucesivamente. Cuando trabajamos con los controles GridView y DetailsView, creamos archivos Skin en el tema DataWebControls que predefine las propiedades CssClass para estos dos controles y la propiedad CssClass de muchas de sus subpropiedades (RowStyle, HeaderStyle y as sucesivamente). Haremos lo mismo con el control DataList. Como se discuti en el tutorial Visualizacin de datos con el ObjectDataSource, un archivo skin especifica por defecto las propiedades relacionadas con el estilo de un control web, un tema es una coleccin de skin, CSS, imgenes y archivos JavaScript que definen un aspecto y apariencia particular para un sitio web. En el tutorial Visualizacin de datos con el ObjectDataSource, creamos un tema DataWebControls (que esta implementado como una carpeta dentro de la carpeta App_Themes) que actualmente tiene dos archivos skin. Agregaremos un tercer archivo skin para especificar la configuracin predeterminada del estilo para el control DataList. Para agregar un archivo skin, haga clic derecho sobre la carpeta

App_Themes/DataWebControls, haga clic en Agregar un nuevo elemento y seleccione la opcin archivo skin de la lista. Nombre el archivo DataList.skin.

Figura 9. Crear un nuevo archivo skin llamado DataList.skin Utilice el siguiente marcado para el archivo DataList.skin:
<asp:DataList runat=server CssClass=DataWebControlStyle> <AlternatingItemStyle CssClass=AlternatingRowStyle/> <ItemStyle CssClass=RowStyle/> <HeaderStyle CssClass=HeaderStyle/> <FooterStyle CssClass=FooterStyle/> <SelectedItemStyle CssClass=SelectedRowStyle/> </asp:DataList>

Estos ajustes asignan las mismas clases CSS a las apropiadas propiedades DataList como se utilizan con los controles GridView y DetailsView. Las clases CSS utilizadas aqu DataWebControlStyle, AlternatingRowStyle, RowStyle y as sucesivamente son definidos en el archivo Styles.css y fueron agregados en los tutoriales anteriores. Con la adicin de este archivo skin, la apariencia del DataList es actualizada en el diseador (podra necesitar actualizar la vista diseo para ver los efectos del nuevo archivo skin, desde el men Ver, seleccione Actualizar). Como se muestra en la figura 10 cada producto muestra alternativamente un fondo de color rosa.

Figura 10. Crear un nuevo archivo skin llamado DataList.skin Paso 4. Exploracin de las otras plantillas del DataList Adems del ItemTemplate, el DataList admite otras seis plantillas adicionales: HeaderTemplate, si se proporciona se adiciona una fila de encabezado a la salida y es usada para presentar esta fila. AlternatingItemTemplate, utilizado para presentar elementos alternos SelectedItemTemplate, utilizado para representar el elemento seleccionado, el elemento seleccionado es aquel cuyo ndice corresponde con la propiedad SelectedIndex del DataList. EditItemTemplate, utilizado para representar el elemento que se edita SeparatorTemplate, si se proporciona aade un separador entre cada elemento y es usado para presentar esta separacin FooterTemplate, si se proporciona agrega una fila de pie de pgina a la salida y se utiliza para presentar esta fila.

Al especificar el HeaderTemplate o el FooterTemplate, el DataList agrega una fila de encabezado o de pie de pgina a la salida presentada. Al igual que el encabezado y el pie de pgina en el GridView, el encabezado y el pie de pgina en el DataList no enlaza datos. Por lo tanto cualquier sintaxis de enlace de datos en el HeaderTemplate o en el FooterTemplate que intente acceder a datos enlazados devolver una cadena en blanco. Nota: Como vimos en el tutorial Visualizacin del Resumen de informacin en el pie de pgina del GridView, aunque las filas de encabezado y pie de pgina no soporten la sintaxis de enlace de datos, informacin de datos especfica puede ser introducida directamente en estas filas, desde el controlador de eventos del RowDataBound del GridView. Esta tcnica puede ser utilizada tanto para calcular totales u otra informacin de los datos enlazados al control, as como para asignar la informacin al pie de pgina. Este mismo concepto se puede aplicar a los controles DataList y Repeater, la nica diferencia es que para el DataList y Repeater se debe crear un controlador de eventos para el evento ItemDataBound (en lugar del evento RowDataBound). Para nuestro ejemplo supongamos que tiene el ttulo Informacin de los Productos en la parte superior de los resultados del DataList en un encabezado <h3>. Para esto, agregue un HeaderTemplate con el marcado correspondiente. Esto se puede lograr desde el diseador haciendo clic en el enlace Editar plantillas de la etiqueta inteligente del control DataList, seleccione HeaderTemplate en la lista desplegable y escriba el texto despus de seleccionar la opcin encabezado 3 desde la lista desplegable estilo (Ver Figura 11).

Figura 11. Agregue un HeaderTemplate con el texto Informacin de los Productos

Por otra parte, esto se puede agregar mediante declaracin ingresando el siguiente marcado dentro de las etiquetas <asp:DataList>:
<HeaderTemplate> <h3>Product Information</h3> </HeaderTemplate>

Para agregar un poco de espacio entre cada producto mostrado, aadiremos un SeparatorTemplate que incluya una lnea entre cada seccin. La etiqueta de regla horizontal (<hr>) se aade como un divisor. Cree el SeparatorTemplate para que tenga el siguiente marcado:
<SeparatorTemplate> <hr /> </SeparatorTemplate>

Nota: Al igual que el HeaderTemplate y el FooterTemplate, el SeparatorTemplate no enlaza a ningn registro desde el origen de datos y por lo tanto no puede acceder directamente a los registros del origen de datos enlazado al DataList. Despus de hacer esta adicin, cuando visualicemos la pgina por medio del navegador debe ser similar a la figura 12. Tenga en cuenta la fila de encabezado y la lnea entre cada uno de los productos mostrados.

Figura 12. El DataList incluye una fila de encabezado y una lnea horizontal entre cada producto mostrado Paso 5. Representacin de marcado especifico con el control Repeater Si usted selecciona Ver/Cdigo desde el navegador cuando visita el ejemplo del DataList de la figura 12, vera que el DataList emite un documento HTML<table> que contiene una sola fila (<tr>) con una sola celda (<td>) para cada elemento enlazado al DataList. De hecho esta salida es idntica a la que se emite desde un GridView con un sencillo TemplateField. Como veremos en un futuro tutorial, el DataList nos permite mas personalizacin de la salida, permitindonos mostrar varios registros del origen de datos como filas de una tabla. Sin embargo, Que sucede si no desea emitir un documento <table> HTML? Para el control total y completo sobre el marcado generado por un control de datos web, debemos utilizar el control Repeater. De igual manera que el control DataList, el Repeater se construye basado en plantillas. Sin embargo el repeater solo soporta estas cinco plantillas:

HeaderTemplate, si se proporciona se agrega al marcado especifico antes de los elementos ItemTemplate, se utiliza para presentar los elementos AlternatingItemTemplate, si se utiliza, se emplea para presentar los elementos alternativos SeparatorTemplate, si se proporciona se agrega al marcado especifico entre cada elemento FooterTemplate, si se utiliza se agrega al marcado especifico despus de los elementos

En ASP.Net 1.x, el control Repeater se utiliza comnmente para mostrar una lista con vietas cuyos datos provienen de un origen de datos. En este caso el HeaderTemplate y FooterTemplate contendran las etiquetas de apertura y cierre <ul> respectivamente, mientras que el ItemTemplate contendra los elementos <li> con la sintaxis de enlace a datos. Este planteamiento se puede utilizar en ASP.Net 2.0 como vimos en los ejemplos del tutorial Pginas Maestras y Navegacin del Sitio: En la pagina maestra Site.master, se utiliza un Repeater para mostrar una lista con vietas de los contenido del nivel superior del mapa el sitio () y otro Repeater anidado para mostrar las secciones hijas de las secciones de nivel superior. En SectionLevelTutorialListing.ascx, se utiliza un Repeater para mostrar una lista con vietas de las secciones hijas de la seccion actual del mapa del sitio Nota: ASP.Net 2.0 introduce el nuevo control BulletedList, que se puede enlazar a un control de origen de datos con el fin de mostrar una lista con vietas sencillas. Con el control BulletedList no es necesario especificar el cdigo HTML relacionado con la lista, sino que nos limitamos a indicar el campo de datos a mostrar como texto para cada elemento de la lista. El Repeater sirve como un atrapador de todos los controles web. Si no existe un control que genere el marcado necesario, se puede utilizar el control Repeater. Para visualizar el uso del Repeater, tendremos la lista de categoras mostradas encima del DataList de Informacin de productos creada en el paso 2. En conclusin tendremos las categoras mostradas en una <table> HTML de una sola fila con cada categora mostrada como una columna de la tabla.

Para realizar esto, comience arrastrando un control Repeater desde el cuadro de herramientas hasta el diseador, encima del DataList de informacin de productos. Al igual que con los controles DataList, el Repeater inicialmente se muestra como un cuadro gris hasta que sus plantillas sean definidas.

Figura 13. Agregar un Repeater al diseador Solo hay una opcin en la etiqueta inteligente del Repeater: Elegir el origen de datos. Opte por crear un nuevo ObjectDataSource y configrelo para que utilice el mtodo GetCategories de la clase CategoriesBLL.

Figura 14. Crear un nuevo ObjectDataSource

Figura 15. Configurar el ObjectDataSource para que utilice la clase CategoriesBLL

Figura 16. Recuperar la informacin de las categoras usando el mtodo GetCategories A diferencia de los controles DataList, Visual Studio no crea automticamente un ItemTemplate para el Repeater luego de que es enlazado al origen de datos. Adems las plantillas del Repeater no pueden especificarse por medio del diseador y deben especificarse de forma declarativa. Para ver las categoras en una sola fila de la <table> con una columna para cada categora, necesitamos que el Repeater emita un marcado similar al siguiente:
<table> <tr> <td>Category 1</td> <td>Category 2</td> ... <td>Category N</td> </tr> </table>

Como el texto <td> Category X </td> es la parte que se repite, esto aparecer en el ItemTemplate del Repeater. EL marcado que aparece antes de este -<table><tr>- se coloca en el HeaderTemplate, mientras que la finalizacin del marcado </tr></table>- se coloca en el FooterTemplate. Para introducir estos ajustes a las

plantillas, vaya a la parte declarativa de la pgina ASP.Net, haga clic en el botn fuente en la esquina izquierda de la parte inferior y escriba la siguiente sintaxis:
<asp:Repeater ID="Repeater1" runat="server" DataSourceID="ObjectDataSource2" EnableViewState="False"> <HeaderTemplate> <table> <tr> </HeaderTemplate> <ItemTemplate> <td><%# Eval("CategoryName") %></td> </ItemTemplate> <FooterTemplate> </tr> </table> </FooterTemplate> </asp:Repeater>

El Repeater emite precisamente el marcado especificado en sus plantillas, nada ms ni nada menos. La figura 17 muestra la salida del Repeater cuando se visualiza por medio de un navegador.

Figura 17. Una <table> de una sola fila muestra cada categora en una columna separada Paso 6. Mejorar la apariencia del Repeater Como el Repeater emite precisamente el marcado especificado por sus plantillas, no debera sorprendernos que no haya propiedades relacionadas con el estilo para el

Repeater. Para modificar la apariencia del contenido generado por el Repeater, debemos agregar manualmente el contenido HTML y CSS necesario, directamente a las plantillas del Repeater. Para nuestro ejemplo tendremos las columnas de las categoras alternando su color de fondo, al igual que las filas. Para realizar esto tenemos que asignar la clase CSS RowStyle a cada elemento alternativo del Repeater por medio de las plantillas ItemTemplate y AlternatingItemTemplate, de la siguiente manera:
<ItemTemplate> <td class="RowStyle"><%# Eval("CategoryName") %></td> </ItemTemplate> <AlternatingItemTemplate> <td class="AlternatingRowStyle"><%# Eval("CategoryName") %></td> </AlternatingItemTemplate>

Agregaremos una fila de encabezado a la salida con el texto de las categoras de los productos. Como no conocemos el nmero de columnas que integran la <table> resultante, la forma ms sencilla de generar una fila de encabezado que garantiza que se abarquen todas las columnas es utilizando dos <table>s. La primera <table> contendr dos filas, una fila de encabezado y la otra fila que contendr la segunda <table> de una sola fila que tiene una columna para cada categora en el sistema. Es decir debemos emitir el siguiente marcado:
<table> <tr> <th>Product Categories</th> </tr> <tr> <td> <table> <tr> <td>Category 1</td> <td>Category 2</td> ... <td>Category N</td> </tr> </table>

</td> </tr> </table>

El siguiente HeaderTemplate y FooterTemplate deseado es el resultado del marcado:


<asp:Repeater ID="Repeater1" runat="server" DataSourceID="ObjectDataSource2" EnableViewState="False"> <HeaderTemplate> <table cellpadding="0" cellspacing="0"> <tr> <th class="HeaderStyle">Product Categories</th> </tr> <tr> <td> <table cellpadding="4" cellspacing="0"> <tr> </HeaderTemplate> <ItemTemplate> <td class="RowStyle"><%# Eval("CategoryName") %></td> </ItemTemplate> <AlternatingItemTemplate> <td class="AlternatingRowStyle"> <%# Eval("CategoryName") %></td> </AlternatingItemTemplate> <FooterTemplate> </tr> </table> </td> </tr> </table> </FooterTemplate> </asp:Repeater>

La figura 18 muestra el Repeater luego de que se han realizado los cambios

Figura 18. Las columnas de categoras alternan su color de fondo e incluye una fila de encabezado Resumen Aunque el control GridView hace que sea ms fcil visualizar, editar, eliminar, ordenar y paginar los datos, su apariencia es muy cuadrada y gris. Para obtener ms control sobre el aspecto necesitamos recurrir al DataList y al Repeater. Ambos controles muestran un conjunto de registros utilizando plantillas en lugar de BoundFields, CheckBoundFields y as sucesivamente. El DataList se representa como un archivo HTML <table> que por defecto muestra cada registro del origen de datos en una sola fila, al igual que el GridView con un sencillo TemplateField. Sin embargo como veremos en un futuro tutorial, el control DataList permite ver varios registros por cada fila de tabla. Por el contrario, el Repeater emite estrictamente el marcado especificado en sus plantillas, esto no agrega ningn marcado adicional y por lo tanto se utiliza comnmente para mostrar los datos en elementos HTML que no sean una <table> (por ejemplo en una lista de vietas). Aunque el DataList y el Repeater ofrecen mayor flexibilidad en la salida presentada, carecen de muchas funciones predefinidas que se encuentran en el GridView. A medida que examinemos los siguientes tutoriales, algunas de estas funciones pueden ser conectadas de nuevo sin demasiado esfuerzo, pero tenga en mente que usando el DataList o Repeater en lugar del GridView, limitamos las funciones que podemos usar sin tener que implementar estas funciones por nosotros mismos.

30.

DAR FORMATO AL DATALIST Y REPEATER BASADOS EN LOS DATOS

En este tutorial iremos paso a paso a travs de los ejemplos sobre cmo dar formato a la apariencia de los controles DataList y Repeater ya sea mediante el uso de las funciones de formato con las plantillas o manejando el evento DataBound. Introduccin Como vimos en el ejemplo anterior el DataList ofrece una serie de propiedades relacionadas con el estilo, que modifican su aspecto. Particularmente vimos como asignar por defecto las clases CSS a las propiedades HeaderStyle, ItemStyle, AlternatingItemStyle y SelectedItemStyle del DataList. Adems de estas cuatro propiedades el control DataList incluye otra serie de propiedades relacionadas con su estilo, como Font, ForeColor, BackColor y BorderWidth por nombrar algunos. El control Repeater no posee ninguna propiedad relacionada con el estilo. Los ajustes de estilo deben realizarse directamente en el marcado de las plantillas del Repeater. Sin embargo a menudo el formato de los datos depende de los propios datos. Por ejemplo cuando visualizamos los productos, podramos desear un mostrar la informacin del producto con un color de fondo gris claro si este es descontinuado, o podramos desear resaltar el valor de UnistInStock si es igual a cero. Como vimos en los tutoriales anteriores, los controles GridView, FormView y DetailsView ofrecen dos formas diferentes de dar formato a la apariencia basado en sus datos: El evento DataBound, crear un controlador de evento DataBound, el cual se genera luego que todos los datos han sido enlazados a cada elemento (para el GridView es el evento DataBound y para el DetailsView y FormView es el evento ItemDataBound). En este controlador de evento solamente los datos enlazados pueden ser examinados y tomar decisiones de formato. Examinamos esta tcnica en el tutorial Formato Personalizado basado en los datos. La funciones de formato en las plantillas, cuando usamos TemplateFields en los controles DetailsView o GridView, o una plantilla en el FormView, podemos agregar una funcin de formato a la clase de cdigo subyacente de la pgina ASP.Net, la capa lgica de negocios o cualquier otra biblioteca de clases que sea accesible desde la aplicacin web. Esta funcin de formato puede aceptar un nmero arbitrario de parmetros de entrada, pero debe devolver el HTML para

presentar en la plantilla. Las funciones de formato fueron examinados por primera vez en el tutorial Uso de TemplateFields en el control GridView. Ambas tcnicas de formato estn disponibles en los controles DataList y Repeater. En este tutorial iremos aplicando estas tcnicas paso a paso a travs de los ejemplos, para ambos controles. Uso del controlador de eventos ItemDataBound Cuando los datos son enlazados al DataList, ya sea desde un control de origen de datos o mediante programacin asignando datos a la propiedad DataSource del control y luego llamando a su mtodo DataBind(), el DataList genera su evento DataBinding, se enumera la fuente de datos y cada registro de datos es enlazado al DataList. Para cada registro en la fuente de datos, el DataList crea un objeto DataItem que luego es enlazado al registro actual. Durante este proceso, el DataList desencadena dos eventos: ItemCreated, se genera luego que el DataListItem ha sido creado ItemDataBound, se genera luego que el registro actual ha sido enlazado al DataListItem Los siguientes pasos describen el proceso de enlace de datos para el control DataList. 1. Se genera el evento DataBinding del DataList. 2. Los datos son enlazados al DataList Para cada registro del origen de datos 1. Crea un objeto DataListItem 2. Se genera el evento ItemCreated 3. Enlaza el registro al DataListItem 4. Se genera el evento ItemDataBound 5. Se agrega el DataListItem a la coleccin Items. Cuando enlazamos datos al control Repeater, este progresa a travs de la misma secuencia de pasos. La nica diferencia es que en lugar de crear instancias DataListItem, el Repeater utiliza RepeaterItems.

Nota: Un lector astuto puede haber notado una ligera anomala entre la secuencia de pasos cuando el DataList y el Repeater enlazan los datos en comparacin a cuando lo hace el GridView. En la parte final del proceso de enlace de datos, el GridView genera el evento DataBound, sin embargo ni el control DataList ni el Repeater genera un evento. Esto es debido a que los controles DataList y Repeater son creados en el marco de ASP.Net 1.x antes que el enfoque de controlador a nivel de pre y post de eventos se convirtiera en algo comn. Al igual que los controles GridView, una de las opciones para dar formato basado en los datos es crear un controlador de evento para el evento ItemDataBound. Este controlador de evento deber inspeccionar los datos que han sido enlazados al DataListItem o RepeaterItem y modificar el formato del control de ser necesario. Para el control DataList, cambiar el formato completo de un tem puede realizarse usando las propiedades relacionadas con el estilo del DataListItem, que incluyen el Font, ForeColor, BackColor, CssClass y as sucesivamente. Para modificar el formato de algunos controles web en particular dentro de la plantilla del DataList, necesitamos acceder a ellos mediante programacin y modificar el estilo de estos controles. Anteriormente vimos como realizar esto en el tutorial Personalizacin del formato basado en los datos. Al igual que el control Repeater, la clase RepeaterItem no tiene propiedades relacionadas con el estilo, por lo tanto cualquier cambio relacionado con el estilo realizado al RepeaterItem en el controlador de evento ItemDataBound debe realizarse accediendo mediante programacin y actualizando los controles web dentro de la plantilla. Como la tcnica para dar formato al ItemDataBound en el DataList y el Repeater es prcticamente igual, nuestro ejemplo se centrara usando el DataList. Paso 1. Visualizacin de la Informacin de Productos en el DataList Antes de preocuparnos del formato, primero crearemos una pgina que utilice un DataList para mostrar la informacin de los productos. En el tutorial anterior creamos un DataList cuyo ItemTemplate mostraba el nombre, categora, proveedor, cantidad por unidad y precio de cada producto. En este tutorial repetiremos esta funcionalidad. Para realizar esto, podemos volver a crear desde cero el DataList y su ObjectDataSource o podemos copiar estos controles desde la pgina creada en el tutorial anterior (Basics.aspx) y pegarlos en la pgina de este tutorial (Formatting.aspx).

Una vez que hemos replicado la funcionalidad del DataList y el ObjectDataSource desde Basics.aspx a Formatting.aspx, nos tomaremos un momento para cambiar la propiedad ID del DataList de DataList1 a ItemDataBoundFormattingExample para mayor descripcin. Luego vemos el DataList desde un navegador. Como muestra la figura 1, la nica diferencia entre el formato de cada producto es que se alterna su color de fondo.

Figura 1. Los productos son mostrados en un control DataList Para este tutorial daremos formato al DataList en aquellos productos cuyo precio es inferior a $20.00 resaltando en color amarillo su nombre y precio. Paso 2. Establecer mediante programacin el valor de los datos en el controlador de evento ItemDataBound: Como solamente los productos cuyo precio es inferior a $20.00 se les aplicaran un formato personalizado, debemos ser capaces de determinar el precio de cada producto. Cuando enlazamos datos a un DataList, el DataList enumera los registros en su fuente de datos y para cada registro crea una instancia DataListItem, enlazando el registro del origen de datos al DataListItem. Despus que los datos del registro particular han sido enlazados al objeto DataListItem actual, se genera el evento ItemDataBound del DataList. Podemos crear un controlador para este evento que inspeccione los valores de datos del DataListItem actual y basado en sus valores, aplique los cambios de formato de ser necesario.

Cree un controlador para el evento ItemDataBound para el DataList y agregue el siguiente cdigo:
Protected Sub ItemDataBoundFormattingExample_ItemDataBound _ (sender As Object, e As DataListItemEventArgs) _ Handles ItemDataBoundFormattingExample.ItemDataBound If e.Item.ItemType = ListItemType.Item OrElse _ e.Item.ItemType = ListItemType.AlternatingItem Then ' Programmatically reference the ProductsRow instance ' bound to this DataListItem Dim product As Northwind.ProductsRow = _ CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _ Northwind.ProductsRow) ' See if the UnitPrice is not NULL and less than $20.00 If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 20 Then ' TODO: Highlight the product's name and price End If End If End Sub

Aunque el concepto y la semntica subyacente del controlador de eventos del ItemDataBound del DataList son iguales a las usadas por el GridView en el controlador de evento del RowDataBound en el tutorial Formato Personalizado basado en los datos, la sintaxis difiere ligeramente. Cuando se genera el evento ItemDataBound, el DataListItem apenas enlaza los datos es pasado al correspondiente controlador de evento a travs de e.Item (En lugar de e.Row, como en el controlador de evento RowDataBound del GridView). El controlador de evento ItemDataBound del DataList se genera para cada fila agregada al DataList, incluyendo fila de encabezado, filas de pie de pgina y filas de separador. Sin embargo la informacin de los productos solo es enlazada a la filas de datos. Por lo tanto cuando usamos el evento ItemDataBound para inspeccionar los datos enlazados al DataList, debemos asegurarnos primero que estamos trabajando con un tem de datos. Esto puede realizarse verificando la propiedad ItemType del DataListItem, la cual puede tener uno de los siguientes ocho valores: AlternatingItem EditItem

Footer Header Item Pager SelectedItem Separator

Tanto el tem y el AlternatingItem modifican los tem de datos del DataList. Asumiendo que estamos trabajando con un tem o AlternatingItem, accedemos a la instancia ProductsRow que fue enlazada al DataListItem actual. La propiedad DataItem del DataListItem contiene una referencia al objeto DataRowView, cuya propiedad Row proporciona una referencia al objeto ProductsRow actual. Luego, verificamos la propiedad UnitPrice de la instancia ProductsRow. Como el campo de datos UnitPrice de la tabla Products permite valores nulos, antes de intentar acceder a la propiedad UnitPrice debemos verificar si este tiene un valor nulo usando el mtodo IsUnitPriceNull(). Si el UnitPrice no es nulo entonces verificamos si este es menor a $20.00. Si este es menor a $20.00 debemos aplicar el formato personalizado. Paso 3. Resaltar el nombre y precio del producto Una vez que sabemos que el precio del producto es menor a $20.00, todo lo que nos queda es resaltar su nombre y precio. Para realizar esto primero debemos referenciar mediante programacin los controles Label en el Itemtemplate que muestran el nombre del producto y su precio. Luego tenemos que hacer que se muestren con un fondo amarillo. Esta informacin de formato puede ser aplicada directamente modificando las propiedades BackColor de los Labels (LabelID.BackColor=Color.Yellow) sin embargo lo ideal es que todos los asuntos relacionados con la apariencia deben ser expresados por medio de hojas de estilo en cascada. De hecho ya tenemos una hoja de estilo que proporciona el formato deseado definido como AffordablePriceEmphasis en el Styles.css, el cual fue creado y discutido en el tutorial Formato Personalizado Basado en los Datos. Para aplicar el formato sencillamente establecemos las propiedades CssClass de los dos controles web Label en AffordablePriceEmphasis, como se muestra en el siguiente cdigo:

' Highlight the product name and unit price Labels' First, get a reference to the two Label Web controls Dim ProductNameLabel As Label = CType(e.Item.FindControl("ProductNameLabel"), Label) Dim UnitPriceLabel As Label = CType(e.Item.FindControl("UnitPriceLabel"), Label)' Next, set their CssClass properties If ProductNameLabel IsNot Nothing Then ProductNameLabel.CssClass = "AffordablePriceEmphasis" End If If UnitPriceLabel IsNot Nothing Then UnitPriceLabel.CssClass = "AffordablePriceEmphasis" End If

Con el controlador del evento ItemDataBound completo, visitemos nuevamente la pgina Formatting.aspx en un navegador. Como muestra la Figura 2, aquellos productos cuyo precio es inferior a $20.00 tienen su nombre y precio resaltados.

Figura 2. Aquellos productos menores a $20.00 son resaltados Nota: Como el DataList es presentado como un <table> HTML, sus instancias DataListItem tienen propiedades de estilo que pueden ser establecidas para aplicar un

estilo especifico a un tem completo. Por ejemplo, si deseamos resaltar completamente el tem con un color amarillo cuando su precio sea inferior a $20.00, podramos remplazar el cdigo que referencia a los Labels y establecer su propiedad CssClass con la siguiente lnea de cdigo: e.Item.CssClass = "AffordablePriceEmphasis" (Ver Figura 3)> Sin embargo, el RepeaterItem que forma el control Repeater no ofrece tales propiedades a nivel de estilo. Por lo tanto, aplicar formato personalizado al Repeater requiere la aplicacin de propiedades de estilo a los controles web dentro de las plantillas de Repeater, como lo hicimos en la Figura 2.

Figura 3. El tem completo del producto es resaltado para aquellos productos menores de $20.00 Usar funciones de formato dentro de la plantilla En el tutorial Usando TemplateFields en el GridView vimos como usar una funcin de formato dentro del TemplateField del GridView para aplicar un formato personalizado basado en los datos enlazados a las filas del GridView. Una funcin de formato es un

mtodo que puede ser invocado desde una plantilla y devuelve el HTML que debe ser emitido en su lugar. Las funciones de formato pueden residir en la clase de cdigo subyacente de la pgina ASP.Net o pueden ser centralizadas dentro de archivos clases en la carpeta App_Code o en un proyecto de Biblioteca de clases separado. Trasladar la funcin de formato fuera de la clase de cdigo subyacente de la pgina ASP.Net es ideal si planea usar la misma funcin de formato en varias pginas ASP.Net o en otras aplicaciones web ASP.Net. Para demostrar las funciones de formato, haremos que la informacin del producto incluya el texto [descontinuado] cerca al nombre del producto si este est descontinuado. Por otra parte, tendremos el precio resaltado en amarillo si es menor a $20.00 (como hicimos en el ejemplo del controlador de evento ItemDataBound); si el precio es mayor o igual a $20.00, no mostraremos el precio, en su lugar aparecer el texto Por favor llame para solicitar una cotizacin. La figura 4 muestra un pantallazo de la lista de productos con las reglas de formato aplicadas.

Figura 4. Para los productos costosos, el precio es remplazado por el texto, Por favor llame para solicitar una cotizacin

Paso 1. Crear las funciones de formato Para este ejemplo, necesitamos dos funciones de formato, una que muestre el nombre del producto junto con el texto [descontinuado] si es necesario y otra que muestre el precio resaltado si es menor que $20.00 o el texto Por favor llame para solicitar una cotizacin si es igual o mayor a $20.00. Crearemos estas funciones en la clase de cdigo subyacente de la pgina ASP.Net y los nombraremos DisplayProductNameAndDiscontinedStatus y DisplayPrice. Ambos mtodos necesitan devolver el HTML para presentar como un String y ambos necesitan ser marcados como Protected (o Public) con el fin que sean invocados desde la parte de sintaxis declarativa de la pgina ASP.Net. El cdigo para esos dos mtodos es el siguiente:
Protected Function DisplayProductNameAndDiscontinuedStatus _ (productName As String, discontinued As Boolean) As String ' Return just the productName if discontinued is false If Not discontinued Then Return productName Else ' otherwise, return the productName appended with the text "[DISCONTINUED]" Return String.Concat(productName, " [DISCONTINUED]") End If End Function Protected Function DisplayPrice(product As Northwind.ProductsRow) As String ' If price is less than $20.00, return the price, highlighted If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 20 Then Return String.Concat("<span class="AffordablePriceEmphasis">", _ product.UnitPrice.ToString("C"), "</span>") Else ' Otherwise return the text, "Please call for a price quote" Return "<span>Please call for a price quote</span>" End If End Function

Note que el mtodo DisplayProductNameAndDiscontinuedStatus acepta los valores de los campos de datos ProductName y Discontinued como valores escalares, mientras que el mtodo DisplayPrice acepta una instancia PorductsRow (en lugar de un valor escalar UnitPrice). Cualquiera de estos enfoques funciona, sin embargo si la funcin de formato est trabajando con valores escalares que pueden contener valores de base de

datos NULL (como UnitPrice, ni ProductName ni Discontinued permiten valores NULL), debemos tener un especial cuidado en el manejo de estas entradas escalares. En particular, los parmetros de entrada deben ser de tipo Object, ya que el valor de entrada puede ser una instancia DBNull en lugar del tipo de datos esperado. Adicionalmente debe realizarse una verificacin para determinar si el valor entrante es o no un valor de base de datos NULL. Es decir si deseamos que el mtodo DisplayPrice acepte los precios como un valor escalar, se tendra que utilizar el siguiente cdigo:
Protected Function DisplayPrice(ByVal unitPrice As Object) As String ' If price is less than $20.00, return the price, highlighted If Not Convert.IsDBNull(unitPrice) AndAlso CType(unitPrice, Decimal) < 20 Then Return String.Concat("<span class="AffordablePriceEmphasis">", _ CType(unitPrice, Decimal).ToString("C"), "</span>") Else ' Otherwise return the text, "Please call for a price quote" Return "<span>Please call for a price quote</span>" End If End Function

Note que el parmetro de entrada UnitPrice es de tipo Object y que la sentencia condicional ha sido modificada para determinar si UnitPrice es un DBNull o no. Adems como el parmetro de entrada UnitPrice es pasado como un Object, se debe convertir en un valor decimal. Paso 2. Llamar a la funcin de formato desde el ItemTemplate del DataList Con las funciones de formato agregadas a la clase de cdigo subyacente de la pgina ASP.Net, todo lo que queda es invocar estas funciones de formato desde el ItemTemplate del DataList. Para llamar una funcin de formato desde una plantilla, coloque el llamado a la funcin dentro de la sintaxis de enlace de datos:
<%# MethodName(inputParameter1, inputParameter2, ...) %>

En el ItemTemplate del DataList, el control web Label ProductNameLabel actualmente muestra el nombre del producto asignando su propiedad Text al resultado de <%# Eval("ProductName") %>. Con el fin de mostrar el nombre ms el texto [descontinuado] (si es necesario), actualice la sintaxis declarativa para que en su lugar se asigne a la

propiedad Text el valor del mtodo DisplayProductNameAndDiscontinuedStatus. Al hacer esto debemos pasar el nombre del producto y los valores descontinuado usando la sintaxis Eval("columnName"). Eval devuelve un valor de tipo Object, pero el mtodo DisplayProductNameAndDiscontinuedStatus espera parmetros de entrada de tipo String y Boolean; por lo tanto debemos convertir los valores devueltos por el mtodo Eval a los tipos de parmetros de entrada esperados, as:
<h4> <asp:Label ID="ProductNameLabel" runat="server" Text='<%# DisplayProductNameAndDiscontinuedStatus((string) Eval("ProductName"), (bool) Eval("Discontinued")) %>'> </asp:Label> </h4>

Para mostrar el precio, sencillamente establecemos la propiedad Text del Label UnitPriceLabel al valor devuelto por el mtodo DisplayPrice, tal como hicimos para mostrar el nombre del producto y el texto [Descontinuado]. Sin embargo, en lugar de pasar el UnitPrice como un parmetro de entrada escalar, en su lugar pasamos una instancia ProductsRow:
<asp:Label ID="UnitPriceLabel" runat="server" Text='<%# DisplayPrice((Northwind.ProductsRow) ((System.Data.DataRowView) Container.DataItem).Row) %>'> </asp:Label>

Con las llamadas a las funciones de formato en su lugar, nos tomaremos un momento para ver nuestro progreso a travs del navegador. Su pantalla debe ser similar a la figura 5, con los productos descontinuados incluyendo el texto [Descontinuado] y aquellos productos cuyo precio es mayor a $20.00 con su precio remplazado por el texto Por favor llame para una cotizacin

Figura 5. En los productos ms costosos, se ha remplazado el precio con el texto Por favor llame para una cotizacin Resumen Dar formato a los contenidos del control DataList o Repeater basados en los datos puede realizarse usando dos tcnicas. La primera tcnica es crear un controlador de eventos para el evento ItemDataBound, el cual se genera cuando cada registro en el origen de datos es enlazado a un nuevo DataListItem o RepeaterItem. En el controlador de eventos del ItemDataBound, los datos del elemento actual pueden ser examinados y luego puede aplicarse formato a los contenidos de la plantilla, o completamente al elemento mismo del DataListItem. De forma alternativa, el formato personalizado puede realizarse por medio de funciones de formato. Una funcin de formato es un mtodo que puede ser invocado desde las plantillas del DataList o Repeater para que devuelva el HTML que debe ser emitido. A menudo el HMTL devuelto por una funcin de formato es definido por los valores que estn siendo enlazados al elemento actual. Estos valores pueden pasarse a la funcin de formato, ya sea como valores escalares o pasndolos en el objeto que est siendo enlazado al elemento (como una instancia ProductsRow).

31. MOSTRAR VARIOS REGISTROS POR FILA CON EL CONTROL DATALIST


En este corto tutorial exploraremos como personalizar el diseo del DataList por medio de sus propiedades RepeatColumns y RepeatDirection. Introduccin Los ejemplos del DataList que hemos observado en los dos ltimos tutoriales presentaron cada registro de su fuente de datos como una fila en una <table> HTML de una sola columna. Aunque este es el comportamiento por defecto del DataList, es muy fcil personalizar el aspecto del DataList de forma que los elementos del origen de datos sean presentados en una tabla de mltiples columnas y mltiples filas. Por otra parte es posible mostrar todos los tems de la fuente de datos en una sola fila de un DataList de mltiples columnas. Podemos personalizar el diseo del DataList por medio de sus propiedades RepeatColumns y RepeatDirection, las cuales indican cuantas columnas son presentadas y si esos tems se colocan horizontal o verticalmente, respectivamente. Por ejemplo, La figura 1 muestra un DataList que muestra la informacin de los productos en una tabla con tres columnas.

Figura 1. El DataList muestra tres productos por fila

Mostrando mltiples tems de la fuente de datos por fila, el DataList puede utilizar ms eficientemente el espacio horizontal de la pantalla. En este corto tutorial exploraremos estas dos propiedades del DataList. Paso 1. Mostrar la informacin de los productos en un DataList Antes de explorar las propiedades RepeatColumns y RepeatDirection, crearemos primero un DataList en nuestra pgina que muestre la informacin de los productos usando el diseo estndar de una tabla de una sola columna con mltiples filas. Para este ejemplo, mostraremos el nombre del producto, categora y precio con el siguiente marcado:
<h4>Product Name</h4> Available in the Category Name store for Price

En tutoriales anteriores, hemos visto como enlazar datos a un DataList, por lo cual nos moveremos rpidamente por estos pasos. Comience abriendo la pgina RepeatColumnAndDirection.aspx en la carpeta DataListRepeaterBasics y arrastre un DataList desde la caja de herramientas hasta el diseador. En la etiqueta inteligente del DataList seleccione la opcin crear nuevo ObjectDataSource y configrelo para que tome sus datos desde el mtodo GetProducts de la clase ProductsBLL, adems seleccione la opcin Ninguno en las pestaas INSERT, UPDATE y DELETE del asistente. Despus de crear y enlazar el ObjectDataSource al DataList, Visual Studio

automticamente crea un ItemTemplate que muestre el nombre y valor de cada uno de los campos de datos. Ajuste el ItemTemplate ya sea directamente en el marcado declarativo o por medio de la opcin Editar Plantillas en la etiqueta inteligente del control DataList para que utilice el marcado mostrado anteriormente, remplazando el texto ProductName, CategoryName y Price por controles Label que usan la sintaxis de enlace de datos apropiada para asignar valores a sus propiedades Text. Despus de actualizar el ItemTemplate, el marcado declarativo de su pgina deber lucir de la siguiente manera:
<asp:DataList ID="DataList1" runat="server" DataKeyField="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <ItemTemplate> <h4> <asp:Label runat="server" ID="ProductNameLabel" Text='<%# Eval("ProductName") %>'></asp:Label>

</h4> Available in the <asp:Label runat="server" ID="CategoryNameLabel" Text='<%# Eval("CategoryName") %>' /> store for <asp:Label runat="server" ID="UnitPriceLabel" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> </ItemTemplate></asp:DataList> <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL"> </asp:ObjectDataSource>

Note que hemos incluido un especificador de formato en la sintaxis de enlace de datos para el UnitPrice, el formato devolver el valor como una moneda Eval(UnitPrice, {0:c}). Tmese un momento para visitar su pgina por medio de un navegador. Como muestra la figura 2, el DataList se presenta como una tabla de productos de una sola columna y mltiples filas.

Figura 2.Por defecto el DataList se presenta como una tabla de nica columna y mltiples filas.

Paso 2. Cambiar la Direccin del diseo del DataList Aunque el comportamiento predeterminado del DataList es exponer sus tems en una tabla de una nica fila y mltiples columnas, este comportamiento puede cambiarse fcilmente por medio de la propiedad RepeatDirection del DataList. La propiedad RepeatDirection puede aceptar uno de estos dos valores posibles: Horizontal y Vertical (por defecto). Cambiando la propiedad RepeatDirection de Vertical a Horizontal, el DataList presenta sus registros en una sola fila, creando una columna por cada tem de la fuente de datos. Para ilustrar este efecto, hacemos clic en el diseador del DataList y luego desde la ventana propiedades, cambiamos la propiedad RepeatDirection de Vertical a Horizontal. Inmediatamente realizado esto, el diseador ajusta el diseo del DataList, creando una interface de una sola columna y mltiples columnas (Ver Figura 3).

Figura 3. La propiedad RepeatDirection dicta la direccin de cmo los Items del DataList son presentados Cuando mostramos pequeas cantidades de datos, una tabla de una fila y mltiples columnas puede ser la forma ideal de maximizar el espacio en la pantalla. Sin embargo, para grandes cantidades de datos una sola fila puede necesitar demasiadas columnas empujando a los tems hacia la derecha fuera de la pantalla. La figura 4 muestra los productos cuando son presentados en un DataList de una sola fila. Como hay muchsimos productos (mas de 80) el usuario tendr que desplazarse hacia la derecha para ver la informacin de cada producto.

Figura 4. Para fuentes de datos muy grandes, un DataList de una sola fila requiere desplazamiento horizontal Paso 3. Mostrar datos en un DataList de mltiples filas y mltiples columnas Para crear un DataList de mltiples filas y mltiples columnas necesitamos establecer en la propiedad RepeatColumns el nmero de columnas que debemos mostrar. Por defecto la propiedad RepeatColumns est establecida en 0, por lo cual el DataList muestra todos sus tems en una sola fila o columna (dependiendo del valor de la propiedad RepeatDirection). Para nuestro ejemplo mostraremos tres productos por fila. Por lo tanto establezca la propiedad RepeatColumns en 3. Despus de hacer este cambio, tmese un momento para ver los resultados en un navegador. Como muestra la figura 5, los productos ahora son listados en una tabla de tres columnas y mltiples filas.

Figura 5. Tres productos se muestran por fila

La propiedad RepeatDirection afecta como son presentados los tems en el DataList. La figura 5 muestra los resultados con la propiedad RepeatDirection establecida en Horizontal. Note que los tres primeros productos Chai, Chan y Anissed Syrup son presentados de izquierda derecha y de arriba abajo. Los siguientes tres productos (Comenzando con Chefs Anton Cajun Seasoning) aparecen en una fila debajo de los tres primeros. Sin embargo, cambiando la propiedad RepeatDirection a Vertical, la presentacin de estos productos va de arriba debajo y de izquierda a derecha, como muestra la figura 6.

Figura 6. Aqu los productos son presentados verticalmente El nmero de filas mostradas en la tabla resultante depende del nmero total de registros enlazados al DataList. Precisamente este es el resultado de dividir el total de tems del origen de datos por el valor de la propiedad RepeatColumns. Como la tabla Products actualmente tiene 84 productos, el cual es divisible por 3, son 28 filas. Si el numero de tems en el origen de datos y el valor de la propiedad RepeatColumns no son divisibles exactos, entonces la ltima fila o columna tendr celdas vacas. Si la propiedad RepeatDirection est establecida en Vertical, entonces la ltima columna tendr celdas vacas, si la propiedad RepeatDirection es Horizontal entonces la ltima fila tendr espacios vacios. Resumen

Por defecto el DataList muestra sus tems en una tabla de una sola columna y mltiples filas, lo cual imita el diseo del GridView con un solo TemplateField. Aunque este diseo es aceptable, podremos maximizar el espacio de la pantalla para mostrar mltiples tems del origen de datos por fila. Realizando esto sencillamente es cuestin de establecer la propiedad RepeatDataColumns del DataList en el nmero de columnas a mostrar por fila. Adicionalmente, la propiedad RepeatDirection del DataList puede ser usada para indicar si los contenido de una tabla de mltiples columnas o mltiples filas debe ser presentado horizontalmente de izquierda a derecha y de arriba abajo o verticalmente de arriba a abajo y de izquierda a derecha.

32. CONTROLES DE DATOS WEB ANIDADOS


En este tutorial exploraremos como usar un Repeater anidado dentro de otro Repeater. Este ejemplo ilustra como poblar el Repeater interno mediante declaracin y programacin. Introduccin Adems de HTML esttico y la sintaxis de enlace de datos, las plantillas tambin pueden incluir controles Web y controles de usuario. Estos controles web pueden tener asignadas sus propiedades por medio de la sintaxis declarativa, por enlace de datos o pueden ser accesibles mediante programacin en los controladores de eventos apropiados en el lado del servidor Integrando los controles en una plantilla, la apariencia y la experiencia de usuario puede ser personalizada y mejorada. Por ejemplo, en el tutorial Usando TemplateFields en el control GridView, vimos como personalizar la apariencia del GridView agregando un control Calendar en un TemplateField para mostrar la fecha de contratacin de un empleado, en los tutoriales de Agregar botones de validacin a las interfaces de Edicin e Insercin y Personalizacin de las interfaces de modificacin de datos, vimos como personalizar la interfaz de edicin e insercin agregando controles de validacin, TextBox, DropDownList, y otros controles web. Las plantillas tambin pueden contener otros controles web de datos. Es decir, podemos tener un DataList que contenga otro DataList (o Repeater o GridView o DetailsView y as sucesivamente) dentro de sus plantillas. El desafo con este tipo de interfaz es el enlace de los datos apropiados al control web de datos interno. Hay unas pocas opciones disponibles, que van desde opciones declarativas usando el ObjectDataSource hasta unos programas. En este tutorial exploraremos como usar un Repeater anidado dentro de otro Repeater. El Repeater externo contendr un tem para cada categora en la base de datos, mostrando el nombre de la categora y su descripcin. Cada tem del Repeater interno mostrara informacin de cada producto perteneciente a la categora (ver figura 1) en una lista con vietas. Nuestro ejemplo ilustrara como poblar el Repeater interno declarativamente y mediante programacin.

Figura 1. Se enumeran cada categora junto con sus productos Paso 1. Creacin de la lista de Categoras Cuando construimos una pgina que utiliza controles web anidados, resulta til disear, crear y probar en primer lugar el control de datos externo, sin ni siquiera preguntarnos por el control anidado interno. Por lo tanto comenzaremos yendo a travs de los pasos necesarios para agregar un Repeater a una pgina para que muestre el nombre y la descripcin de cada categora. Comience abriendo la pgina NestedControls.aspx en la carpeta DataListRepeaterBasics y agregue un control a la pgina, establezca su propiedad ID en CategoryList. Desde la etiqueta inteligente del Repeater, seleccione agregar un nuevo ObjectDataSource llamado CategoriesDataSource.

Figura 2. Nombre el nuevo ObjectDataSource CategoriesDataSource Configure el ObjectDataSource para que tome sus datos del mtodo GetCategories de la clase CategoriesBLL.

Figura 3. Configure el ObjectDataSource para que utilice el mtodo GetCategories de la clase CategoriesBLL Para especificar el contenido de la plantilla del Repeater necesitamos ir a la vista de cdigo fuente e introducir manualmente la sintaxis declarativa. Agregue un ItemTemplate que muestre el nombre de la categora en un elemento <h4> y la descripcin de la categora en un elemento prrafo (<p>). Adems separaremos cada categora con una lnea horizontal (<hr>). Despus de realizar estos cambios su pgina

deber contener una sintaxis declarativa para el Repeater y ObjectDataSource similar a la siguiente:
<asp:Repeater ID="CategoryList" DataSourceID="CategoriesDataSource" EnableViewState="False" runat="server"> <ItemTemplate> <h4><%# Eval("CategoryName") %></h4> <p><%# Eval("Description") %></p> </ItemTemplate> <SeparatorTemplate> <hr /> </SeparatorTemplate> </asp:Repeater><asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource>

La Figura 4 muestra nuestro progreso visto desde un navegador.

Figura 4. Se muestra cada nombre de categora y su descripcin, separadas por una lnea horizontal Paso 2. Agregar el Repeater anidado Product Con el listado de las categoras completo, nuestra prxima tarea es aadir un Repeater al ItemTemplate de CategoryList que muestre la informacin sobre los productos que

pertenecen a la categora correspondiente. Hay un nmero de formas en la que se pueden recuperar los datos para este Repeater interno, dos de las cuales vamos a explorar en breve. Por ahora solo crearemos el Repeater de productos dentro del ItemTemplate del Repeater CategoryList. Especficamente haremos que el Repeater de productos muestre cada producto en una lista de vietas con cada elemento de la lista incluyendo el nombre del producto y precio. Para crear este Repeater necesitamos agregar manualmente las plantillas y sintaxis declarativa del Repeater interno en el ItemTemplate del CategoryList. Agreguemos el siguiente marcado dentro del ItemTemplate del Repeater CategoryList:
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False" runat="server"> <HeaderTemplate> <ul> </HeaderTemplate> <ItemTemplate> <li><strong><%# Eval("ProductName") %></strong> (<%# Eval("UnitPrice", "{0:C}") %>)</li> </ItemTemplate> <FooterTemplate> </ul> </FooterTemplate> </asp:Repeater>

Paso

3.

Enlazar

los

productos

especficos

de

la

categora

al

Repeater

ProductsByCategoryList Hasta este momento si visita la pagina desde un navegador, su pantalla lucir igual que en la figura 4, porque aun no hemos enlazado ningn dato al Repeater, Hay algunas maneras de grabar los registros de los productos apropiados y luego enlazarlos al Repeater, algunas ms eficientes que otras. El principal desafo aqu es recuperar los productos apropiados para la categora especificada. Los datos enlazados al control Repeater interno pueden ser accesibles de forma declarativa por medio de un ObjectDataSource en el ItemTemplate del Repeater CategoryList, o mediante programacin desde la pgina de cdigo subyacente de la pgina ASP.Net. De forma similar, estos datos pueden ser enlazados con el Repeater

interno mediante declaracin a travs de la propiedad DataSourceID del Repeater interno, por medio de la sintaxis de enlace declarativa o mediante programacin referenciando el Repeater interno en el controlador del evento ItemDataBound del Repeater CategoryList, estableciendo mediante programacin su propiedad DataSource y llamando al mtodo DataBind(). Veremos cada uno de estos enfoques. Acceder a los datos de forma declarativa con un ObjectDataSource y el controlador de eventos ItemDataBound Como hemos usado extensivamente el ObjectDataSource a travs de esta serie de tutoriales, la opcin ms natural para acceder a los datos de este ejemplo es continuar con el ObjectDataSource. La clase ProductsBLL tiene un mtodo GetProductsByCategoryID(categoryID) que recupera la informacin de los productos que pertenecen al CategoryID especificado. Por lo tanto, agregue un ObjectDataSource al ItemTemplate del Repeater CategoryList y configrelo para que acceda a sus datos desde el mtodo de la clase. Desafortunadamente, el Repeater no permite que sus plantillas sean editadas por medio de la vista diseador por lo que necesitamos agregar manualmente la sintaxis declarativa para este control ObjectDataSource. La siguiente sintaxis muestra el ItemTemplate del Repeater CategoryList despus de agregar este nuevo ObjectDataSource (ProductsByCategoryDataSource):
<h4><%# Eval("CategoryName") %></h4> <p><%# Eval("Description") %></p> <asp:Repeater ID="ProductsByCategoryList" EnableViewState="False" DataSourceID="ProductsByCategoryDataSource" runat="server"> <HeaderTemplate> <ul> </HeaderTemplate> <ItemTemplate> <li><strong><%# Eval("ProductName") %></strong> sold as <%# Eval("QuantityPerUnit") %> at <%# Eval("UnitPrice", "{0:C}") %></li> </ItemTemplate> <FooterTemplate> </ul> </FooterTemplate> </asp:Repeater>

<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server" SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL"> <SelectParameters> <asp:Parameter Name="CategoryID" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Cuando usamos el enfoque de ObjectDataSource necesitamos establecer la propiedad DataSourceID del Repeater ProductsByCategoryList al ID del ObjectDataSource (ProductsByCategoryDataSource). Tambin note que nuestro ObjectDataSource tiene un elemento <asp:Parameter> que especifica el valor de CategoryID que ser pasado dentro del mtodo GetProductsBycategoryID( categoryID). Pero como especificamos este valor? Idealmente seriamos capaces de establecer la propiedad DefaultValue del elemento <asp:Parameter> usando la sintaxis de enlace de datos, del siguiente modo:
<asp:Parameter Name="CategoryID" Type="Int32" DefaultValue='<%# Eval("CategoryID")' />

Por desgracia, la sintaxis de enlace de datos slo es vlida en los controles que tienen el evento DataBinding. La clase Parameter carece de tal evento y por lo tanto la sintaxis anterior es ilegal y origina un error en tiempo de ejecucin. Para establecer este valor, necesitamos crear un controlador de eventos para el ItemDataBound del Repeater CategoryList. Recordemos que el evento ItemDataBound se genera para cada tem enlazado al Repeater. Por lo tanto, cada vez que el evento se genera para el Repeater externo podemos asignar el valor CategoryId actual al parmetro CategoryId del ObjectDataSource ProductsCategoryDataSource. Cree un controlador de evento para el evento ItemDataBound del Repeater CategoryList con el siguiente cdigo:
Protected Sub CategoryList_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) _ Handles CategoryList.ItemDataBound If e.Item.ItemType = ListItemType.AlternatingItem _ OrElse e.Item.ItemType = ListItemType.Item Then ' Reference the CategoriesRow object being bound to this RepeaterItem Dim category As Northwind.CategoriesRow = _ CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _ Northwind.CategoriesRow)

' Reference the ProductsByCategoryDataSource ObjectDataSource Dim ProductsByCategoryDataSource As ObjectDataSource = _ CType(e.Item.FindControl("ProductsByCategoryDataSource"), _ ObjectDataSource) ' Set the CategoryID Parameter value ProductsByCategoryDataSource.SelectParameters("CategoryID").DefaultValue = _ category.CategoryID.ToString() End If End Sub

Este controlador de evento comienza asegurndose que estamos tratando con un tem de datos en lugar de un tem de encabezado, pie de pgina o separador. Luego hace referencia a la instancia CategoriesRow actual que acaba de ser enlazada al RepeaterItem actual. Finalmente referencia el ObjectDataSource en el ItemTemplate y asigna el valor de su parmetro CategoryID al CategoryId del RepeaterItem actual. Con este controlador de eventos, el Repeater ProductsByCategorylist en cada RepeaterItem esta enlazado a los productos en la categora del RepeaterItem. La figura 5 muestra una pantalla de la salida resultante.

Figura 5. El Repeater interior muestra cada categora, el interno muestra los productos para las categoras Acceder a los datos de los productos por categora mediante programacin En lugar de usar un ObjectDataSource para recuperar los productos de la categora actual, creamos un mtodo en la clase de cdigo subyacente de la pgina ASP.Net (o en la carpeta App_Code o en un proyecto de biblioteca de clases separados) que devuelva el conjunto de productos apropiados cuando pasamos un CategoryId. Imagine que tenemos un mtodo en nuestra clase de cdigo subyacente de la pgina ASP.Net y que fue nombrado GetProductsInCategory( categoryID ).Con este mtodo en su lugar podramos enlazar los productos para la categora actual al Repeater interno usando la siguiente sintaxis declarativa:
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False" DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'> ... </asp:Repeater>

La propiedad DataSource del Repeater usa la sintaxis de enlace de datos para indicar que sus datos vienen del mtodo GetProductsInCategory(categoryID). Como Eval(CategoryID) devuelve un valor de tipo Object, convertimos el Object a un Integer antes de pasarlo al mtodo GetProductsInCategory(categoryID). Tenga en cuenta que el CategoryID al cual accedimos aqu por medio de la sintaxis de enlace de datos es el CategoryID en el Repeater externo (CategoryList) que es enlazado a los registros en la tabla Categories. Por lo tanto sabemos que categoryID no puede ser un valor de base de datos Null, que es por lo que a ciegas podemos convertir el mtodo Eval sin comprobar si estamos tratando con un DBNull. Con este enfoque, necesitamos crear el mtodo GetProductsInCategory(categoryID) y hacer que recupere el conjunto de productos apropiado dado por el categoryID suministrado. Podemos hacer esto devolviendo el ProductsDataTable devuelto por el mtodo GetProductsByCategoryID(categoryID) de la clase ProductsBLL. Crearemos el mtodo GetProductsInCategory(categoryID) en la clase de cdigo subyacente para nuestra pgina NestedControls.aspx. Hagamos esto usando el siguiente cdigo:
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _ As Northwind.ProductsDataTable

' Create an instance of the ProductsBLL class Dim productAPI As ProductsBLL = New ProductsBLL() ' Return the products in the category Return productAPI.GetProductsByCategoryID(categoryID) End Function

Este mtodo solo crea una instancia del mtodo ProductsBLL y devuelve los resultados del mtodo GetProductsByCategoryID( categoryID ). Tenga en cuenta que el mtodo debe ser marcado como Public o Protected; si el mtodo es marcado como Private, este no ser accesible desde el marcado declarativo de la pgina ASP.Net. Despus de realizar estos cambios para usar esta nueva tcnica, tommonos un momento para ver la pgina desde un navegador. La salida debe ser idntica a la salida cuando usamos el enfoque ObjectDataSource y controlador del evento ItemDataBound (refirase a la figura 5 para ver una captura de pantalla). Nota: Podra parecer demasiado trabajo crear el mtodo

GetProductsInCategory(categoryID) en la clase de cdigo subyacente de la pgina ASP.Net. Despus de todo este mtodo solamente crea una instancia de la clase ProductsBLL y devuelve los resultados de su mtodo GetProductsBycategoryID(categoryID). Porque no solamente llamar a este mtodo directamente desde la sintaxis de enlace de datos del Repeater interno, como DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'. Aunque esta sintaxis no trabaja con nuestra implementacin actual de la clase ProductsBLL (ya que el mtodo GetProductsByCategoryID(categoryId) es mtodo de instancia) podramos modificar ProductsBLL para incluir un mtodo GetProductsByCategoryID(categoryId) static o hacer que la clase incluya mtodo de instancia static() que devuelva una nueva instancia de la clase ProductsBLL. Si bien con las modificaciones podramos eliminar la necesidad de, mtodo GetProductsInCategory(categoryId) en la clase de cdigo subyacente de la pagina ASP.Net, el mtodo de la clase de cdigo subyacente nos ofrece mayor flexibilidad en el trabajo con los datos recuperados. Como se ver en breve. Recuperar a la vez toda la informacin del producto Las dos tcnicas anteriores examinaron como grabar estos productos de la categora actual hacienda un llamado al mtodo GetProductsByCategoryID(categoryId) de la clase

ProductsBLL (el primer enfoque lo hizo por medio del ObjectDataSource, el segundo enfoque a travs del mtodo GetProductsInCategory(categoryId) en la clase de cdigo subyacente). Cada vez que el mtodo es invocado, la capa lgica de negocios llama a la capa de acceso a datos, que consulta en la base de datos con una sentencia SQL que devuelve filas desde la tabla Products cuyo campo CategoryID coincida con el parmetro de entrada suministrado. Teniendo N Categoras en el sistema, este enfoque hace N+1 llamadas a la base de datos, una consulta de base de datos obtener todas las categoras y luego N llamadas para obtener los productos especficos de cada categora. Sin embargo podemos recuperar todos los datos necesarios en solo dos llamadas a la base de datos, una llamada para obtener todas las categoras y otra para obtener todos los productos. Una vez que tengamos todos los productos, podemos filtrarlos para que solamente los productos que concuerden con el CategoryID actual sean enlazados al Repeater interno de las categoras. Para proporcionar esta funcionalidad, necesitamos hacer una ligera modificacin al mtodo GetProductsInCategory(categoryId) en nuestra clase de cdigo subyacente de la pgina ASP.Net. En vez de devolver ciegamente los resultados del mtodo GetProductsByCategoryID(categoryId) de la clase ProductsBLL, podemos en su lugar acceder a todos los productos (si aun no se ha accedido a ellos) y luego devolver solo la vista de los productos filtrada basados en el CategoryId que se ha pasado.
Private allProducts As Northwind.ProductsDataTable = Nothing Protected Function GetProductsInCategory(ByVal categoryID As Integer) _ As Northwind.ProductsDataTable ' First, see if we've yet to have accessed all of the product information If allProducts Is Nothing Then Dim productAPI As ProductsBLL = New ProductsBLL() allProducts = productAPI.GetProducts() End If ' Return the filtered view allProducts.DefaultView.RowFilter = "CategoryID = " & categoryID Return allProducts End Function

Note la adicin de la variable a nivel de pagina, allProducts. Esta almacena informacin relacionada de todos los productos y es poblada la primera vez que se invoca el mtodo GetProductsInCategory(categoryId). Despus de asegurarnos que el objeto allProducts ha sido creado y poblado, el mtodo filtra los resultados del DataTable de tal forma que solo son accesibles aquellas filas cuyo CategoryId coincida con el CategoryId especificado. Este enfoque reduce el nmero de veces que se accede a la base de datos de N+1 a solo dos llamadas. Esta mejora no introduce ningn cambio en el marcado que se presenta en la pagina, ni tampoco lleva los registros menos veces que el otro enfoque. Solamente reduce el nmero de llamadas a la base de datos. Nota: Una razn intuitiva para reducir el nmero de accesos a la base de datos e mejorar el rendimiento. Sin embargo este podra no ser el caso. Si tiene un gran nmero de productos cuyos CategoryID sean Nulos, por ejemplo luego de llamar al mtodo GetProducts devuelve un numero de productos que nunca sern mostrados. Por otra parte, devolver todos los productos puede ser un desperdicio si solo estamos mostrando un subconjunto de las categoras, que podra ser el caso si se ha implementado paginacin. Como siempre cuando se trata de analizar el rendimiento de las dos tcnicas, la nica medida segura es ejecutar pruebas controladas para los escenarios de los casos comunes de su aplicacin. Resumen En este tutorial vimos como anidar un control de datos web dentro de otro, especficamente examinamos como tener un Repeater externo que muestre un tem para cada categora con un Repeater interno que muestre los productos para cada categora en una lista con vietas. EL principal desafo en la construccin de una interfaz de usuario anidada es anidar el acceso y el enlace de datos correcto al control de datos web interno. Hay varias tcnicas disponibles, dos de las cuales fueron examinadas en este tutorial. El primer enfoque examinado utilizo un ObjectDataSource en el ItemTemplate del control de datos web externo que enlazo el control de datos web interno por medio de su propiedad DataSourceID. La segunda tcnica accedi a los datos por medio de un mtodo en la clase de cdigo subyacente de la pgina ASP.Net.

Este mtodo puede luego ser enlazado a la propiedad DataSource del control de datos web interno por medio de la sintaxis de enlace de datos. Aunque la interfaz examinada en este tutorial utiliza un Repeater anidado dentro de otro Repeater, estas tcnicas pueden ser ampliadas a otros controles de datos web. Podemos anidar un Repeater dentro de un GridView, o un GridView dentro de un DataList y as sucesivamente.

ESCENARIOS DE FILTRADO CON EL DATALIST Y REPEATER

33. FILTRADO MAESTRO/ DETALLE CON UN DROPDOWNLIST


En este tutorial veremos cmo mostrar informes maestro/detalle en una sola pgina web usando DropDownList para mostrar los registros maestros y un DataList para mostrar los detalles. Introduccin El informe Maestro/Detalle que creamos inicialmente usamos un GridView en el tutorial anterior Filtrado Maestro/Detalle usando un DropDownList, que comienza mostrando un conjunto de registros maestros. Luego el usuario puede profundizar en uno de estos registros maestros y as visualizar los detalles del registro maestro. Los reportes Maestro/Detalle son una opcin ideal para visualizar relaciones uno a muchos y para mostrar informacin detallada de tablas muy grandes (es decir aquellas que tienen muchas columnas). Hemos explorado como implementar reportes Maestros/Detalles usando los controles GridView y DetailsView en tutoriales anteriores. En este y los prximos dos tutoriales, examinaremos nuevamente estos conceptos, pero enfocndonos en su lugar en el uso de controles DataList y Repeater. En este tutorial observaremos como usar un DropDownList que contiene los registros maestros con los registros detalles mostrados en un DataList. Paso 1. Agregar las pginas web de los tutoriales Maestro/Detalle Antes de empezar este tutorial, primero nos tomaremos un momento para agregar una carpeta y las pginas web ASP.Net que necesitaremos para este y los prximos dos tutoriales en los que nos ocuparemos de los reportes Maestro/Detalle usando controles DataList y Repeater. Comencemos creando una nueva carpeta en el proyecto llamada DataListRepeaterFiltering. Luego agregamos cinco pginas web ASP.Net a esta carpeta, configurndolas para que utilicen la pgina maestra Site.master: Default.aspx FilterByDropDownList.aspx CategoryListMaster.aspx ProductsForCategoryDetails.aspx CategoriesAndProducts.aspx

Figura 1. Crear una carpeta DataListRepeaterFiltering y agregar las pginas tutoriales ASP.Net Luego abra la pgina Default.aspx y arrastre el control de usuario

SetionLevelTutorialListing.ascx desde la carpeta UserControls hasta la superficie de diseo. Este control de usuario, el cual creamos en el tutorial P ginas Maestras y Navegacin del sitio, enumera el mapa del sitio y muestra los tutoriales de la seccin actual en una lista con vietas.

Figura 2. Agregar el control de usuario SectionLevelTutorialListing.ascx a Default.aspx

Con el fin de tener una lista de vietas que muestre los tutoriales Maestro/Detalle que hemos creado, necesitamos agregarlos al mapa del sitio. Abra el archivo Web.sitemap y agregue el siguiente marcado despus del nodo del mapa del sitio Visualizacin de datos con el DataList y Repeater:
<siteMapNode title="Master/Detail Reports with the DataList and Repeater" description="Samples of Reports that Use the DataList and Repeater Controls" url="~/DataListRepeaterFiltering/Default.aspx"> <siteMapNode title="Filter by Drop-Down List" description="Filter results using a drop-down list." url="~/DataListRepeaterFiltering/FilterByDropDownList.aspx" /> <siteMapNode title="Master/Detail Across Two Pages" description="Master records on one page, detail records on another." url="~/DataListRepeaterFiltering/CategoryListMaster.aspx" /> <siteMapNode title="Maser/Detail on One Page" description="Master records in the left column, details on the right, both on the same page." url="~/DataListRepeaterFiltering/CategoriesAndProducts.aspx" /> </siteMapNode>

Figura 3. Actualizar el SiteMap para incluir las nuevas pginas ASP.Net Paso 2. Mostrar las categoras en un DropDownList

Nuestro reporte Maestro/Detalle mostrara las categoras en un DropDownList, con la lista de los productos del elemento seleccionado mostrada posteriormente en la pgina en un control DataList. La primera tarea que tenemos por delante es mostrar las categoras en un DropDownList. Comenzamos abriendo la pgina FilterByDropDownList.aspx de la carpeta DataListRepeaterFiltering y arrastrando un DropDownList desde la caja de herramientas hasta el diseador de la pgina. Luego, establezca la propiedad ID del DropDownList en Categories. Haga clic en el enlace Seleccionar origen de datos en la etiqueta inteligente del DropDownList y cree un nuevo ObjectDataSource llamado CategoriesDataSource.

Figura 4. Agregue un nuevo ObjectDataSource llamado CategoriesDataSource Configure el nuevo ObjectDataSource de tal forma que invoque el mtodo

GetCategories() de la clase CategoriesBLL. Despus de configurar el ObjectDataSource aun necesitamos especificar qu campo del origen de datos debe ser mostrado en el DropDownList y cul debe ser asociado como el valor de cada elemento de la lista. Hagamos de CategoryName el campo para mostrar y de CategoryID el valor para cada elemento de la lista.

Figura 5. Haga que el DropDownList muestre el campo CategoryName y use CategoryID como valor Hasta este punto tenemos un control DropDownList poblado con los registros de la tabla Categories (todo esto realizado en menos de seis segundos). La figura 6 muestra nuestro progreso hasta el momento visto a travs de un navegador.

Figura 6. Un DropDownList muestra las categoras actuales

Paso 3. Agregar el DataList Products El ltimo paso en nuestro reporte Maestro/Detalle es mostrar la lista de productos asociados con la categora seleccionada. Para realizar esto, agregue un DataList a la pgina y cree un nuevo ObjectDataSource llamado ProductsByCategoryDataSource. Haga que el control ProductsByCategoryDataSource recupere sus datos del mtodo GetProductsBycategoryID(categoryID) de la clase ProductsBLL. Como este informe Maestro/Detalle es de solo-lectura, seleccione la opcin Ninguno en las pestaas INSERT, UPDATE y DELETE.

Figura 7. Seleccione el mtodo GetProductsByCategoryID (categoryID) Despus haga clic en siguiente, el asistente del ObjectDataSource nos solicita el origen del valor para el parmetro categoryID del mtodo GetProductsByCategoryID(categoryID). Para usar el valor del elemento seleccionado en el DropDownList Categories establezca el origen del Parmetro en Control y el ControlID en Categories.

Figura 8. Establezca el parmetro categoryID como el valor del DropDownList Categories Una vez finalizado el asistente de Configuracin del origen de datos, Visual Studio genera automticamente un ItemTemplate para el DataList que muestra el nombre y el valor de cada campo de datos. Mejoraremos el DataList para que en su lugar use un ItemTemplate que muestre solo el nombre del producto, categora, proveedor, cantidad por unidad y precio junto con un SeparatorTemplate que inserte un elemento <hr> entre cada tem. Usaremos el ItemTemplate del ejemplo mostrado en el tutorial Visualizacin de datos con los controles DataList y Repeater, pero sintase libre de usar cualquier marcado de plantilla que encuentre visualmente ms atractivo. Despus de realizar estos cambios, el marcado del DataList y de su ObjectDataSource lucir similar al siguiente:
<asp:DataList ID="DataList1" runat="server" DataKeyField="ProductID" DataSourceID="ProductsByCategoryDataSource" EnableViewState="False"> <ItemTemplate> <h4> <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>' /> </h4> <table border="0">

<tr> <td class="ProductPropertyLabel">Category:</td> <td><asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>' /></td> <td class="ProductPropertyLabel">Supplier:</td> <td><asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>' /></td> </tr> <tr> <td class="ProductPropertyLabel">Qty/Unit:</td> <td><asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Eval("QuantityPerUnit") %>' /></td> <td class="ProductPropertyLabel">Price:</td> <td><asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>' /></td> </tr> </table> </ItemTemplate> <SeparatorTemplate> <hr /> </SeparatorTemplate></asp:DataList> <asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL"> <SelectParameters> <asp:ControlParameter ControlID="Categories" Name="categoryID" PropertyName="SelectedValue" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Tmese un momento para verificar nuestro progreso en un navegador. Cuando visitamos por primera vez la pgina, se muestran los productos pertenecientes a la categora seleccionada (Beverages) como se ve en la Figura 9, pero si cambiamos el DropDownList no se actualizan los datos. Esto es debido a que debe ocurrir una devolucin de datos para que el DataList se actualice. Para realizar esto establecemos la propiedad AutoPostBack=True del DropDownList o agregamos un control web Button a la pgina. Para este tutorial optamos por establecer la propiedad AutoPostBack del DropDownList a True.

La figura 9 y 10 muestran el reporte Maestro/Detalle en accin.

Figura 9. Cuando visitamos la pgina por primera vez, los productos de Beverages son mostrados

Figura 10. Seleccionando una nueva categora (Produce) se origina automticamente una devolucin de datos y se actualiza el DataList Agregar un tem de Lista Seleccione una categora-- Cuando visitamos la pgina FilterByDropDownList.aspx el primer tem de la lista (Beverages) del DropDownList Categories es seleccionado por defecto, mostrando los productos de la categora Beverages en el DataList. En el tutorial Filtrado Maestro/Detalle con un DropDownList agregamos una opcin Choose a Category-- al DropDownList que es seleccionada por defecto, cuando se seleccionaba, mostraba todos los productos en la base de datos. Este enfoque es manejable cuando se enumeran los productos en un GridView, ya que cada fila toma una pequea cantidad de espacio del tamao de la pantalla. Sin embargo con el DataList cada informacin de los productos toma un espacio mucho mayor en la pantalla. Aadiremos la opcin Choose a Category-- y haremos que sea seleccionada por defecto, pero en lugar de mostrar todos los productos cuando este seleccionada, la configuraremos para que no muestre productos. Para agregar un nuevo tem a la lista del DropDownList, vaya a la venta Propiedades y seleccione la elipse en la propiedad tems. Agregue un nuevo tem de lista con el Text Choose a Category-- y el Value=0.

Figura 11. Agregue un tem de lista Choose a Category-- De forma alternativa, puede agregar un tem a la lista agregando el siguiente marcado al DropDownList:

<asp:DropDownList ID="categories" runat="server" AutoPostBack="True" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID" EnableViewState="False"> <asp:ListItem Value="0"> -- Choose a Category --</asp:ListItem> </asp:DropDownList>

Adicionalmente

necesitamos

establecer

el

AppendDataBoundItems

del

control

DropDownList en True porque si se establece en False (por defecto), cuando las categoras sean enlazadas al DropDownList desde el ObjectDataSource, estas sobrescriben a los tems de lista agregados manualmente.

Figura 12. Establezca el AppendDataBoundItems en True La razn por la cual escogimos el valor de 0 para el tem de lista Choose a Category- es porque no existen categoras en el sistema con un valor de 0, por lo tanto no se devolvern registros cuando el tem de lista Choose a Category es seleccionado. Para confirmar esto, nos tomaremos un momento para visitar la pgina a travs del navegador. Como muestra la figura 13, inicialmente cuando visualizamos la pagina, la opcin Choose a Category-- es seleccionada y no se muestra ningn producto.

Figura 13. Cuando el tem de lista Choose a Category-- es seleccionado, no se muestran productos Si en su lugar desea mostrar todos los productos cuando la opcin Choose a Category-- sea seleccionada, utilice en su lugar el valor de -1. Un lector astuto recordara que en el tutorial Filtrado Maestro/Detalle con un DropDownList actualizamos el mtodo GetProductsByCategoryID(categoryID) de la clase ProductsBLL para que si se pasaba un valor categoryID de -1, se devolvieran todos los productos. Resumen Cuando mostramos datos relacionados jerrquicamente, a menudo ayuda presentar los datos usando informes Maestro/Detalle, desde los cuales el usuario puede iniciar leyendo la parte superior de la jerarqua y profundizar en los detalles. En este tutorial examinamos la construccin de un reporte Maestro/Detalle sencillo que muestra los productos de una categora seleccionada. Para realizar esto usamos un DropDownList para mostrar la lista de categoras y un DataList para los productos pertenecientes a la categora seleccionada. En el prximo tutorial veremos cmo separar los registros maestros y detalles a travs de dos pginas. En la primera pgina, se mostrar una lista de registros Maestros con

un enlace para ver los detalles. Haciendo clic en el enlace el usuario ser dirigido a una segunda pgina, que mostrara los detalles para el registro maestro seleccionado.

34. FILTRADO MAESTRO/DETALLE A TRAVES DE DOS PGINAS


En este tutorial veremos cmo separar un reporte Maestro/Detalle en dos pginas. En la pgina Maestra usaremos un Repeater para presentar una lista de categoras que cuando son seleccionadas llevara al usuario a una pgina Detalles donde un DataList mostrara los productos pertenecientes a la categora seleccionada. Introduccin En el tutorial Filtrado Maestro/Detalle a travs de dos pginas, examinamos este enfoque usando un GridView para mostrar todos los proveedores en el sistema. El GridView incluia un HiperLinkField que presenta un enlace a una segunda pgina, pasando el SuplierID en la cadena de consulta. La segunda pgina uso un GridView para mostrar los productos suministrados por el proveedor seleccionado. Estas dos pginas del reporte Maestro/Detalle tambin pueden ser realizadas usando controles DataList y Repeater. La nica diferencia es que ni el DataList ni el Repeater proporcionan soporte para el control HiperLinkField. En su lugar debemos agregar un control HiperLink o un elemento ancla HTML (<a>) dentro del ItemTemplate de los controles. La propiedad NavigateUrl del HiperLink o el atributo href del elemento ancla pueden ser personalizados usando los enfoques declarativos o mediante programacin. En este tutorial exploraremos un ejemplo que muestra las categoras en una lista de vietas en una pgina usando un control Repeater. Cada tem de la lista incluye el nombre de la categora y su descripcin, con el nombre de la categora mostrado como un enlace a la segunda pgina. Haciendo clic en este enlace el usuario ser dirigido a una segunda pgina, donde un DataList muestra aquellos productos que pertenecen a la categora seleccionada. Paso 1. Mostrar las categoras en una lista de vietas. El primer paso en la creacin de cualquier reporte Maestro/Detalle es empezar mostrando los registros Maestros. Por lo tanto nuestra primera tarea consiste en mostrar las categoras en la pgina Maestro. Abra la pgina CategoryListMaster.aspx de la carpeta DataListRepeaterFiltering, agregue un control Repeater y desde su etiqueta inteligente, seleccione la opcin aadir un nuevo ObjectDataSource. Configure el nuevo ObjectDataSource para que acceda a sus datos desde el mtodo GetCategories() de la clase CategoriesBLL (Ver Figura 1).

Figura 1. Configure el ObjectDataSource para que utilice el mtodo GetCategories de la clase CategoriesBLL Luego defina los Templates del Repeater de tal forma que muestre el nombre de cada categora y su descripcin como un elemento en la lista de vietas. Aun no nos preocuparemos por el enlace de cada categora a la pgina Detalles. A continuacin se muestra el marcado declarativo para el Repeater y ObjectDataSource:
<asp:Repeater ID="Repeater1" runat="server" DataSourceID="ObjectDataSource1" EnableViewState="False"> <HeaderTemplate> <ul> </HeaderTemplate> <ItemTemplate> <li><%# Eval("CategoryName") %> - <%# Eval("Description") %></li> </ItemTemplate> <FooterTemplate> </ul> </FooterTemplate> </asp:Repeater> <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"></asp:ObjectDataSource>

Con este marcado completo, tmese un momento para ver nuestro progreso por medio del navegador. Como muestra la figura 2. El Repeater se presenta como una lista de vietas mostrando el nombre y descripcin de cada categora.

Figura 2. Cada categora es mostrada como un elemento de la lista de vietas Paso 2. Convertir el nombre de Categora en un enlace a la pagina Detalles Para permitir que el usuario vea los detalles de la informacin para una categora dada, necesitamos agregar un enlace a cada tem de la lista de vietas, luego al hacer clic en el, dirigiremos al usuario a una segunda pgina (ProductsForCategoryDetails.aspx). Esta segunda pgina mostrara los productos para la categora seleccionada usando un DataList. Con el fin de determinar que categora fue seleccionada, necesitamos pasar el CategoryID de la categora seleccionada a la segunda pgina por medio de algn mecanismo. Lo forma ms simple de transferir un dato escalar desde una pgina a otra es por medio de una cadena de consulta, que es la opcin que utilizaremos en este tutorial. En particular, la pgina ProductsForCategoryDetails.aspx espera el valor de categoryID seleccionado que ser pasado en un campo de cadena de consulta llamado CategoryID. Por ejemplo, para ver los productos de la categora Beverages, que tiene un CategoryID de 1, un usuario deber visitar ProductsForCategoryDetails.aspx?CategoryID=1. Para crear un enlace para cada tem de la lista de vietas en el Repeater necesitamos agregar un control web HiperLink o un elemento ancla HTML (<a>) al ItemTemplate. En escenarios donde el mismo HiperLink es mostrado para cada fila, uno u otro enfoque

ser suficiente. Para Repeaters, prefiero usar un elemento ancla. Para usar un elemento ancla, actualizamos el ItemTemplate del Repeater as:
<li> <a href='ProductsForCategoryDetails.aspx?CategoryID=<%# Eval("CategoryID") %>'> <%# Eval("CategoryName") %> </a> - <%# Eval("Description") %> </li>

Note que el CategoryID puede ser ingresado directamente en el atributo href del elemento ancla, sin embargo, para hacer esto debe delimitar el valor del atributo href con apostrofes ya que el mtodo Eval dentro del atributo href delimita su cadena (CategoryID) con comillas dobles. De forma alternativa, en su lugar podemos usar un control web HiperLink:
<li> <asp:HyperLink runat="server" Text='<%# Eval("CategoryName") %>' NavigateUrl='<%# "ProductsForCategoryDetails.aspx?CategoryID=" & Eval("CategoryID") %>'> </asp:HyperLink> - <%# Eval("Description") %> </li>

Note

como

la

parte

esttica se le

de agrega

la el

URL resultado

de

ProductsForCategoriesDetails.aspx?CategoryIDconcatenador de cadenas.

Eval(CategoryID) directamente dentro de la sintaxis de enlace a datos usando un

Uno de los beneficios de usar el control HiperLink es que puede accederse a l mediante programacin desde el controlador de evento ItemDataBound del Repeater si es necesario. Por ejemplo podramos desear mostrar el nombre de la categora como un texto en lugar de cmo un enlace para aquellas categoras que no tengan productos asociados. Tal verificacin debe ser realizada mediante programacin en el controlador de evento ItemDataBound, para las categoras que no tengan productos asociados, la propiedad NavigateUrl del HiperLink puede ser establecida como una cadena en blanco, por lo tanto el resultado del nombre de la categora en particular se presenta como un texto plano (en lugar de cmo un enlace). Refirase de nuevo al tutorial Formato del

DataList y Repeater basado en los datos par mayor informacin sobre el formato de los contenidos del DataList y Repeater basado en la lgica de programacin por medio del controlador de evento ItemDataBound. Si estas siguiendo el tutorial, sintase libre de usar el enfoque de elemento ancla o control HiperLink en su pgina. Independientemente del enfoque, cuando visualizamos la pgina a travs de un navegador cada nombre de categora debe ser presentado como un enlace a ProductsForCategoryDetails.aspx pasando el valor CategoryId del caso (Ver Figura 3).

Figura 3. Los nombres de las categoras ahora enlazan la ProductsForCategoryDetails.aspx Paso 3. Mostrar los productos que pertenecen a la categora seleccionada Con la pgina CategoryListMaster.aspx completa, estamos listos para poner nuestra atencin en la implementacin de la pgina detalles ProductsForCategoryDetails.aspx. Abra esta pgina, arrastre un DataList desde la caja de herramientas hasta el diseador y establezca su propiedad ID en ProductsInCategory. Luego desde la etiqueta inteligente del DataList, seleccione agregar un nuevo ObjectDataSource, nombrndolo ProductsInCategoryDataSource. Configure este para que llame al mtodo GetProductsByCategoryID(categoryID) de la clase ProductsBLL, establezca las listas desplegables de las pestaas INSERT,UPDATE y DELETE en Ninguno.

Figura 4. Configure el ObjectDataSource para que utilice el mtodo GetProductsBycategoryID(categoryID) de la clase ProductsBLL Como el mtodo GetProductsByCategoryID(categoryID) acepta un parmetro de entrada, el asistente de origen de datos le ofrece la oportunidad de especificar el origen del parmetro. Establezca el origen de parmetro en QueryString definiendo el QueryStringField en CategoryID.

Figura 5. Use el QueryString Field CategoryID como origen del parmetro

Como vimos en los tutoriales anteriores, despus de completar el asistente de configuracin del origen de datos, Visual Studio crea automticamente un ItemTemplate para el DataList que muestra cada nombre de los campos de datos y su valor. Remplace esta plantilla con una que muestre solamente el nombre del producto, proveedor y precio. Adems establezca la propiedad RepeatColumns en 2. Despus de estos cambios, el marcado declarativo de su DataList y ObjecDataSource deber ser similar al siguiente:
<asp:DataList ID="ProductsInCategory" DataKeyField="ProductID" RepeatColumns="2" DataSourceID="ProductsInCategoryDataSource" EnableViewState="False" runat="server"> <ItemTemplate> <h5><%# Eval("ProductName") %></h5> <p> Supplied by <%# Eval("SupplierName") %><br /> <%# Eval("UnitPrice", "{0:C}") %> </p> </ItemTemplate> </asp:DataList> <asp:ObjectDataSource ID="ProductsInCategoryDataSource" OldValuesParameterFormatString="original_{0}" runat="server" SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL"> <SelectParameters> <asp:QueryStringParameter Name="categoryID" QueryStringField="CategoryID" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Para ver esta pgina en accin, comience en la pgina CategoryListMaster.aspx, luego haga clic sobre un enlace en la lista de vietas de las categoras. Haciendo esto nos dirigiremos CategoryID a la pgina medio ProductsForCategoryDetails.aspx de la en cadena de consulta. pasando El tambin solo el por ObjectDataSource recibir

ProductsInCategoryDataSource

ProductsForCategoryDetails.aspx

aquellos productos de la categora especificada y luego los mostrara en el DataList, que presentara dos productos por fila. La figura 6, muestra una captura de pantalla de ProductsForcategoryDetails.aspx cuando visualizamos los productos de Beverages.

Figura 6. La Bebidas son mostradas dos por fila Paso 4. Mostrar la informacin de la categora en ProductsForCategoryDetails.aspx Cuando un usuario hace clic sobre una categora en CategoryListMaster.aspx, es llevado a ProductsForCategoryDetails.aspx mostrando los productos que pertenecen a la categora seleccionada, sin embargo en ProductsForCategoryDetails.aspx no hay seales visuales que indiquen que categora fue seleccionada. Un usuario que desee hacer clic sobre Beverages pero accidentalmente haga clic en Condiments, no tiene forma de darse cuenta de su error una vez que se encuentra en ProductsForCategoryDetails.aspx. Para aliviar este potencial problema, podemos mostrar la informacin acerca de la categora seleccionada, como su nombre y descripcin en la parte superior de la pgina ProductsForCategoryDetails.aspx. Para realizar esto, agregamos un FormView encima del control Repeater en ProductsForCategoryDetails.aspx. Luego agregamos un nuevo ObjectDataSource a la pgina desde la etiqueta inteligente del FormView llamado CategoryDataSource y lo configuramos para que utilice el mtodo GetCategoryByCategoryID(categoryID) de la clase CategoriesBLL.

Figura 7. Acceder a la informacin de la Categora por medio del mtodo GetCategoryByCategoryID(CategoryID) de la clase CategoriesBLL Al igual que el ObjectDataSource ProductsInCategoryDataSource agregado en el paso 3, el asistente de configuracin del origen de datos del CategoryDataSource nos solicita agregar la fuente para el parmetro de entrada del mtodo GetCategoryByCategoryID(categoryID). Use exactamente las mismas definiciones de antes, establezca el origen del parmetro a QueryString y el valor de QueryStringField a CategoryID (Refirase de nuevo a la figura 5). Despus de completar el asistente, Visual Studio crea automticamente un

ItemTemplate, EditItemTemplate e InsertItemTemplate para el FormView. Como solo estamos proporcionando una interfaz de solo lectura, sintase libre de remover el EditItemTemplate y el InsertItemTemplate. Tambin sintase libre de personalizar el ItemTemplate del FormView. Despus de remover estas plantillas y personalizar el ItemTemplate, el marcado declarativo de su FormView y ObjectDataSource debe ser similar al siguiente:
<asp:FormView ID="FormView1" runat="server" DataKeyNames="CategoryID" DataSourceID="CategoryDataSource" EnableViewState="False" Width="100%"> <ItemTemplate> <h3> <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Bind("CategoryName") %>' />

</h3> <p> <asp:Label ID="DescriptionLabel" runat="server" Text='<%# Bind("Description") %>' /> </p> </ItemTemplate> </asp:FormView> <asp:ObjectDataSource ID="CategoryDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategoryByCategoryID" TypeName="CategoriesBLL"> <SelectParameters> <asp:QueryStringParameter Name="categoryID" Type="Int32" QueryStringField="CategoryID" /> </SelectParameters> </asp:ObjectDataSource>

La figura 8 muestra una captura de pantalla cuando visualizamos esta pgina desde un navegador. Nota: Adems del FormView, se agrego un control HiperLinkField encima del FormView que regresa al usuario a la lista de categoras (CategoryListMaster.aspx). Sintase libre de colocar este enlace donde guste o de omitirlo.

Figura 8. La informacin de la categora ahora es mostrada en la parte superior de la pgina Paso 5. Mostrar un mensaje si no existen productos pertenecientes a la categora seleccionada La pgina CategoryListMaster.aspx muestra todas las categoras en el sistema, independientemente de si tiene o no productos asociados. Si un usuario hace clic sobre una categora que no tenga productos asociados, no se mostrara el DataList en ProductsforCategoryDetails.aspx, ya que su origen de datos no tiene ningn tem. Como vimos en tutoriales, el GridView proporciona una propiedad EmptyDataText que puede ser usada para especificar un mensaje de texto y mostrarlo si no existen registros en su origen de datos. Desafortunadamente, el DataList y el Repeater no tienen esta propiedad. Con el fin de mostrar un mensaje informando al usuario que no existen productos para la categora seleccionada, necesitamos agregar un control Label a la pgina; uno cuya propiedad Text se asigne al mensaje a mostrar en el evento de que no existan productos. Luego necesitamos establecer mediante programacin su propiedad Visible basados en si el DataList contiene o no tems. Para realizar esto comenzamos agregando un Label debajo del DataList. Establezca su propiedad ID en NoProductsMesage y su propiedad Text en No existen productos para la categora seleccionada. Luego necesitamos establecer mediante programaci n la propiedad Visible del Label basados en si hay o no datos enlazados al DataList ProductsInCategoryList. Esta asignacin debe realizarse luego de que los datos han sido enlazados al DataList. Para el GridView, DetailsView y FormView, podramos crear un controlador de evento para el evento DataBound del control, el cual se genera luego que el enlace de datos ha sido completado. Sin embargo, ni el DataList ni el Repeater tienen un evento DataBound valido. Para este ejemplo en particular podemos asignar la propiedad Visible del Label en el controlador de evento Page_Load, ya que los datos han sido asignados al DataList antes del evento Load de la pgina. Sin embargo, este enfoque podra no funcionar en un caso general, ya que los datos del ObjectDataSource podran ser enlazados al DataList posteriormente durante el ciclo de vida de la pgina. Por ejemplo, si los datos mostrados estn basados en el valor de otro control, como cuando mostramos un

reporte Maestro/Detalle usando un DropDownList que almacena los registros maestros, los datos podran no ser enlazados nuevamente al control web hasta la etapa PreRender en el ciclo de vida de la pgina. Una solucin con la cual podemos trabajar en todos los casos es asignar la propiedad Visible en False en el controlador de eventos ItemDataBound (o ItemCreated) del DataList cuando enlazamos un tem de tipo tem o AlternatingItem. En tal caso sabemos que hay al menos un tem de datos en el origen de datos y por lo tanto podemos esconder el Label NoProductsMessage. Adicional a este controlador de evento, necesitamos un controlador de evento para el evento DataBinding del DataList, donde iniciemos la propiedad Visible del Label en True. Como el evento DataBinding se genera antes del evento ItemDataBound, la propiedad Visible del Label inicialmente se establecer en True, sin embargo si no existen tems de datos se establecer en False. El siguiente cdigo implementa esta lgica:
Protected Sub ProductsInCategory_DataBinding(sender As Object, e As EventArgs) _ Handles ProductsInCategory.DataBinding 'Show the Label NoProductsMessage.Visible = True End Sub Protected Sub ProductsInCategory_ItemDataBound(s As Object, e As DataListItemEventArgs) _ Handles ProductsInCategory.ItemDataBound 'If we have a data item, hide the Label If e.Item.ItemType = ListItemType.Item OrElse e.Item.ItemType = _ ListItemType.AlternatingItem Then NoProductsMessage.Visible = False End If End Sub

Todas las categoras en la base de datos Northwind estn asociadas con uno o ms productos. Para probar esta funcin, ajustaremos manualmente la base de datos para este tutorial, reasignando todos los productos asociados con la categora Produce (CategoryID=7) a la categora Seafood (CategoryID=8). Esto puede realizarse desde el Explorer de servidores seleccionando Nueva Consulta y usando la siguiente sentencia UPDATE:
UPDATE Products SET CategoryID = 8

WHERE CategoryID = 7

Despus de actualizar correctamente la base de datos, regrese a la pgina CategoryListMaster.aspx y haga clic en el enlace Produce. Como no hay ningn producto perteneciente a la categora Produce, deber verse el mensaje No existen productos para la categora seleccionada, como se muestra en la Figura 9.

Figura 9. Se muestra un mensaje si no hay productos pertenecientes a la categora seleccionada Resumen Aunque los reportes Maestro/Detalle pueden mostrar ambos registros maestros y detalles en una misma pgina, en muchos sitios web estn separados en dos pginas web. En este tutorial vimos como implementar un reporte Maestro/Detalle que tenga las categoras mostradas en una lista de vietas usando un Repeater en la pgina web maestra y los productos asociados a ellas mostrados en la pgina detalles. Cada tem de la lista en la pgina web maestra contiene un enlace a la pgina detalles que pasa el valor del CategoryID del tem seleccionado. En la pgina detalles se recuperan aquellos productos para la categora especificado por medio del mtodo GetProductsByCategoryID(categoryID). El valor del parmetro categoryID fue especificado mediante declaracin usando el valor de la cadena de consulta CategoryID como el origen del parmetro. Tambin vimos como mostrar los

detalles de la categora en la pgina Detalles usando un FormView y como mostrar un mensaje si no existen productos que pertenezcan a la categora seleccionada.

35. FILTRADO MAESTRO/DETALLE USANDO UNA LISTA CON VIETAS DE LOS REGISTROS MAESTROS Y UN DATALIST DE DETALLES
En este tutorial comprimiremos los dos reportes Maestro/Detalle de los tutoriales anteriores en una sola pgina, mostrando en una lista con vietas los nombres de las categoras en el lado izquierdo de la pantalla y los productos de la categora seleccionada a la derecha de la pantalla. Introduccin En el tutorial anterior vimos como separar el Reporte Maestro/Detalle en dos pginas. En la pgina Maestra usamos un control Repeater para presentar una lista con vietas de las categoras. Cada nombre de las categoras era un enlace, que al ser seleccionado, llevaba al usuario a la pgina detalles, donde un DataList con dos columnas mostraba los productos que pertenecan a la categora seleccionada. En este tutorial comprimiremos las dos pginas de los tutoriales anteriores en una sola, mostrando en una lista con vietas los nombres de las categoras en la parte izquierda de la pantalla con el nombre de cada categora presentado como un LinkButton. Dando clic en el nombre de una categora los LinkButtons generan una devolucin de datos y enlazan los productos de la categora seleccionada a un DataList de dos columnas en la derecha de la pantalla. Aparte de mostrar el nombre de la categora, el Repeater a la izquierda mostrara el total de productos que pertenecen a la categora dada (Ver Figura 1).

Los nombres de las categoras y el total de sus productos se muestran a la izquierda Paso 1. Mostrar un Repeater en la parte izquierda de la pantalla Para este tutorial necesitamos hacer que la lista de vietas de las categoras aparezca a la izquierda de los productos de la categora seleccionada. El contenido dentro de una pgina web puede ser posicionado usando elementos HTML estndar como tags de prrafos, espacios sin saltos, <table>s y otros o por medio de las tcnicas de hojas de estilo en cascada (CSS). Hasta ahora todos nuestros tutoriales han usado tcnicas de posicionamiento usando CSS. Cuando construimos la interfaz de navegacin de los usuarios en nuestra pgina maestra en el tutorial Pginas Maestras y Navegacin del sitio, usamos posicionamiento absoluto, indicando los pixeles precisos de desplazamiento para la lista de navegacin y el contenido principal. De forma alternativa CSS puede ser usado para posicionar un elemento a la derecha o a la izquierda de otro por medio de floating. Podemos hacer que la lista de vietas de las Categoras aparezca a la izquierda de los productos de la categora seleccionada haciendo flotar el Repeater a la izquierda del DataList. Abra la pgina CategoriesAndProducts.aspx en la carpeta DataListRepeaterFiltering y arrastre a la pgina un DataList y un Repeater. Establezca la propiedad ID del Repeater en Categories y la del DataList en CategoryProducts. Vaya a la vista fuente y coloque los controles Repeater y DataList dentro de sus propios elementos <div>. Esto es primero encierre el Repeater dentro de un elemento <div> y luego el DataList en su propio elemento <div> directamente despus del Repeater. Hasta este momento su marcado deber lucir similar al siguiente:

<div> <asp:Repeater ID="Categories" runat="server"> </asp:Repeater> </div> <div> <asp:DataList ID="CategoryProducts" runat="server"> </asp:DataList> </div>

Para hacer flotar el Repeater a la izquierda del DataList, necesitamos usar el atributo de estilo CSS float de la siguiente forma:
<div> Repeater </div> <div> DataList </div>

El float:left, hace flotar el primer elemento <div> a la izquierda del segundo. El width y el padding-rigth indican el ancho del primer <div> y cuanto espacio debe ser agregado entre el contenido de los elementos <div> y su margen derecha. Para mayor informacin sobre elementos flotantes en CSS verifique el tutorial de floating. En lugar de especificar el estilo directamente a travs del atributo style del elemento <p>, crearemos en su lugar una nueva clase CSS en Style.css llamada FloatLeft:
.FloatLeft { float: left; width: 33%; padding-right: 10px; }

Luego puede remplazar el elemento <div> por <div class=FloatLeft>

Despus

de

agregar

la

clase

CSS

configurar

el

marcado

en

la

pgina

CategoriesAndProducts.aspx, vaya al diseador. Deber ver el Repeater flotando a la izquierda del DataList (aunque por ahora a la derecha aparece una caja gris ya que aun no se ha configurado sus fuentes de datos y las plantillas).

Figura 2. El Repeater est flotando a la izquierda del DataList Paso 2. Determinar el nmero de productos de cada categora Con el marcado de posicin del Repeater y el DataList completo, estamos listos para enlazar los datos de las categoras al control Repeater. Sin embargo, como se muestra en la lista de vietas de las categoras de la Figura 1, adems del nombre de cada categora necesitamos mostrar el nmero de productos asociados con la categora. Para acceder a esta informacin podemos: Determinar esta informacin desde la clase de cdigo subyacente de la pagina ASP.Net, Dado un CategoryID en particular, podemos determinar el nmero de productos asociados llamando al mtodo GetProductsByCategoryID(categoryID) de la clase ProductsBLL. Este mtodo devuelve un objeto ProductsDataTable cuya propiedad Count nos indica cuantas ProductsRows existen, que es el nmero de productos para el CategoryID especificado. Podemos crear un controlador de evento ItemDataBound del Repeater, para cada categora enlazada al Repeater llame al mtodo GetProductsByCategoryID(categoryID) e incluya su Count como salida. Actualizar el CategoriesDataTable en el DataSet tipado para incluir la columna NumberOfProducts. Podemos actualizar el mtodo GetCategories() en el CategoriesDataTable para que incluya esta informacin o de forma alternativa

dejar

GetCategories()

como

esta

crear

un

nuevo

mtodo

al

CategoriesDataTable llamado GetCategoriesAndNumberOfProducts(). Exploraremos ambas tcnicas. El primer enfoque es sencillo de implementar ya que no necesitamos actualizar la capa de acceso a datos, sin embargo requiere muchas ms comunicaciones con la base de datos La llamada al mtodo GetProductsByCategoryID(categoryID) de la clase ProductsBLL en el controlador de evento ItemDataBound agrega una llamada extra a la base de datos por cada categora mostrada en el Repeater. Con esta tcnica hay N+1 llamadas a la base de datos, donde N es el nmero de categora mostradas en el Repeater. Con el segundo enfoque, el product count es devuelto junto con la informacin de cada categora desde el mtodo GetCategories() (GetCategoriesAndNumberOfProducts()) de la clase CategoriesBLL, lo que resulta en un solo viaje a la base de datos. Determinacin del nmero de Productos en el controlador de eventos ItemDataBound Determinar el nmero de productos para cada categora en el controlador de eventos ItemDataBound del Repeater no requiere ninguna modificacin a nuestra capa de acceso a datos existente. Todas las modificaciones pueden hacerse directamente en la pgina CategoriesAndProducts.aspx. Comenzamos agregando un nuevo ObjectDataSource llamado CategoriesDataSource por medio de la etiqueta inteligente del Repeater. Luego configuramos el ObjectDataSource CategoriesDataSource para que recupere sus datos desde el mtodo GetCategories() de la clase CategoriesBLL.

Figura 3. Configurar el ObjectDataSource para que use el mtodo GetCategories() de la clase CategoriesBLL Cada tem en el Repeater Categories debe ser seleccionable y cuando se presione hara que el DataList muestre los productos para la categora seleccionada. Esto puede realizarse haciendo cada categora como un HiperLink, enlazndonos de nuevo a la misma pgina (CategoriesAndProducts.aspx), pero pasando el CategoryID a travs de la cadena de consulta, como vimos en el tutorial anterior. La ventaja de este enfoque es que la pgina muestra los productos de una categora en particular puede ser marcada e indexada por el motor de bsqueda. De forma alternativa, podemos hacer de cada categora un LinkButton, que es el enfoque que usaremos para este tutorial. El LinkButton se presenta al usuario como un HiperLink, pero al ser seleccionada induce una devolucin de datos; en la devolucin de datos el ObjectDataSource del DataList debe ser actualizado para mostrar aquellos productos que pertenecen a la categora seleccionada. Para este tutorial tendra ms sentido usar un HiperLink que usar un LinkButton: sin embargo en muchos escenarios usar un LinkButton es mucho ms ventajoso. Como veremos usar un LinkButton introduce nuevos desafos que podran no presentarse con un HiperLink. Por lo tanto, usando un LinkButton en este tutorial superaremos estos desafos y ayudaremos a proporcionar soluciones para aquellos escenarios donde podriamos desear usar un LinkButton en lugar de un HiperLink. Nota: Puedes repetir este tutorial usando un control HiperLink o un elemento <a> en lugar del LinkButton. El siguiente marcado muestra la sintaxis declarativa para el Repeater y el ObjectDataSource. Note que las plantillas del Repeater presentan una lista con vietas con cada tem como un LinkButton:
<asp:Repeater ID="Categories" runat="server" DataSourceID="CategoriesDataSource"> <HeaderTemplate> <ul> </HeaderTemplate> <ItemTemplate> <li><asp:LinkButton runat="server" ID="ViewCategory"></asp:LinkButton></li> </ItemTemplate>

<FooterTemplate> </ul> </FooterTemplate> </asp:Repeater> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource>

Nota: Para este tutorial el Repeater debe tener su ViewState habilitado (note la omisin de EnableViewState=False en la sintaxis declarativa del Repeater). En el paso 3, crearemos un controlador de eventos para el evento ItemCommand del Repeater que actualice la coleccin SelectParameters del ObjectDataSource del DataList. Sin embargo, el ItemCommand del Repeater no se genera si su ViewState esta deshabilitado. El LinkButton con el valor de la propiedad ID de ViewCategories no tiene establecida su propiedad Text aun. Si solo deseamos mostrar el nombre de la categora, debemos establecer la propiedad Text de forma declarativa, a travs de la sintaxis de enlace de datos as:
<asp:LinkButton runat="server" ID="ViewCategory" Text='<%# Eval("CategoryName") %>' />

Sin embargo deseamos mostrar el nombre de la categora y el nmero de productos que pertenecen a la categora. Esta informacin puede ser recuperada desde el controlador de eventos ItemDataBound del Repeater haciendo una llamada al mtodo GetCategoriesByproductID(categoryID) de la clase ProductsBLL y determinando cuantos registros son devueltos en el ProductsDataTable resultante, como muestra el siguiente cdigo:
Protected Sub Categories_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) ' Make sure we're working with a data item... If e.Item.ItemType = ListItemType.Item OrElse _ e.Item.ItemType = ListItemType.AlternatingItem Then ' Reference the CategoriesRow instance bound to this RepeaterItem Dim category As Northwind.CategoriesRow = _ CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _ Northwind.CategoriesRow)

' Determine how many products are in this category Dim productsAPI As New NorthwindTableAdapters.ProductsTableAdapter Dim productCount As Integer = _ productsAPI.GetProductsByCategoryID(category.CategoryID).Count ' Reference the ViewCategory LinkButton and set its Text property Dim ViewCategory As LinkButton = _ CType(e.Item.FindControl("ViewCategory"), LinkButton) ViewCategory.Text = _ String.Format("{0} ({1:N0})", category.CategoryName, productCount) End If End Sub

Comenzamos asegurndonos que estamos trabajando con un tem de datos (cuyo ItemType es tem o AlternatingItem) y luego referenciamos la instancia CategoriesRow que ha sido enlazada al RepeaterItem actual. Luego determinamos el nmero de productos para esta categora creando una instancia de la clase ProductsBLL, llamando a su mtodo GetCategoriesByProductsID(categoryID) y determinando el nmero de registros devueltos usando la propiedad Count. Finalmente el LinkButton ViewCategory en el ItemTemplate es referenciado y su propiedad Text est establecida en CategoryName (NumberOfProductsInCategory), donde NumberOfProductsInCategory esta formateado como un nmero sin lugares decimales. Nota: Alternativamente, podramos agregar una funcin de formato a la clase de cdigo subyacente de la pgina ASP.Net que acepte valores CategoryName y CategoryID de la categora y devuelva el CategoryName concatenado con el nmero de productos en la categora (que determinamos llamando al mtodo GetCategoriesByProductID(categoryID)). Los resultados de la funcin de formato pueden ser asignados mediante declaracin a la propiedad Text del LinkButton remplazando lo necesario en el controlador de evento ItemDataBound. Despus de agregar este controlador de evento, tommonos un momento para probar la pgina desde un navegador. Ahora cada categora es mostrada en una lista con vietas, mostrando el nombre de la categora y el nmero de productos asociados con la categora (Ver Figura 4).

Figura 4. Se muestra el nombre de cada categora y el nmero de productos Actualizar el CategoriesDataTable y CategoriesTableAdapter para incluir el nmero de productos de cada categora En lugar de determinar el nmero de productos para cada categora enlazada al Repeater, podemos agilizar este proceso ajustando el CategoriesDataTable y CategoriesTableAdapter en la capa de acceso a datos para incluir esta informacin de forma nativa. Para lograr esto, agregamos una nueva columna al CategoriesDataTable para almacenar el nmero de productos asociados. Para agregar una nueva columna al DataTable, abra el DataSet tipado (App_Code\DAL\Northwind.xsd), haga clic derecho sobre el DataTable para modificarlo y seleccione Add/Column. Agregue una nueva columna al CategoriesDataTable (Ver Figura 5).

Figura 5.Agregar una nueva columna a CategoriesDataTable Esto agrega una nueva columna llamada Column1, lo cual podemos cambiar simplemente escribiendo un nombre diferente. Cambiemos el nombre de esta nueva

columna a NumberOfProducts. Luego necesitamos configurar las propiedades de esta columna. Haga clic sobre la nueva columna y vaya a la ventana propiedades. Cambie la propiedad DataType de la columna de System.String a System.Int32 y defina la propiedad ReadOnly a True como muestra la figura 6.

Figura 6. Defina las propiedades DataType y ReadOnly de la nueva columna Aunque el CategoriesDataTable ahora tiene una columna NumberOfProducts, su valor no est definido por ninguna consulta del TableAdapter correspondiente. Podemos actualizar el mtodo GetCategories() para devolver esta informacin si deseamos que la informacin sea devuelta cada vez que la informacin de categoras es recuperada. Sin embargo, en raras ocasiones si solo necesitamos grabar el numero de productos asociados a las categoras (como solo para este tutorial), podemos dejar el mtodo GetCategories() como esta y creamos un nuevo mtodo para recuperar esta informacin. Usaremos este ultimo enfoque, creando un nuevo mtodo llamado GetCategoriesAndNumberOfProducts(). Para agregar este nuevo mtodo GetCategoriesAndNumberOfProducts(), haga clic derecho en el CategoriesTableAdapter y seleccione Nueva Consulta. Esto abrir el asistente de configuracin de consultas del TableAdapter, que fue usado numerosas veces en tutoriales anteriores. Para este mtodo, comenzamos el asistente indicando que la consulta usa una sentencia SQL ad-hoc que devuelve filas.

Figura 7. Crear un nuevo mtodo usando sentencias SQL ad-hoc

Figura 8. La sentencia SQL devuelve filas La siguiente pantalla del asistente nos pide la consulta a usar. Para devolver los campos CategoryID, CategoryName y Description de cada categora, junto con el nmero de productos asociados con la categora, usamos la siguiente sentencia SQL:

SELECT CategoryID, CategoryName, Description, (SELECT COUNT(*) FROM Products p WHERE p.CategoryID = c.CategoryID) as NumberOfProductsFROM Categories c

Figura 9. Especifique la consulta a utilizar Tenga en cuenta que la subconsulta que calcula el nmero de productos asociados con las categoras es nombrada NumberOfProducts. Este nombre hace que coincida el valor devuelto por esta subconsulta para que sea asociado con la columna NumberOfProducts del CategoriesDataTable. Despus de ingresar esta consulta, el ltimo paso es seleccionar el nombre del nuevo mtodo. Utilice FillWithNumberOfProducts y GetCategoriesAndNumberOfProducts para los enfoques de Llenar un DataTable o Devolver un DataTable, respectivamente.

Figura 10. Nombre los mtodos del TableAdapter FillWithNumberOfProducts y GetCategoriesAndNumberOfProducts Hasta el momento la capa de acceso a datos ha sido ampliada para incluir el nmero de productos por categora. Como nuestra capa de presentacin enruta todas las llamadas a la DAL por medio de una capa de lgica de negocios separada, necesitamos agregar el correspondiente mtodo GetCategoriesAndNumberOfProducts a la clase CategoriesBLL: <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetCategoriesAndNumberOfProducts() As Northwind.CategoriesDataTable Return Adapter.GetCategoriesAndNumberOfProducts() End Function Con la DAL y BLL completas, estamos listos para enlazar sus datos al Repeater Categories en CategoriesAndProducts.aspx. Si ya ha creado un ObjectDataSource para el Repeater en la seccin de Determinacin del nmero de productos en el controlador de evento ItemDataBound, borramos este ObjectDataSource y removemos la definicin de la propiedad DataSourceID del Repeater, desligamos el evento ItemDataBound del

Repeater

del

controlador

de

eventos

removiendo

la

sintaxis

Handles

CategoriesOnItemDataBound en la clase de cdigo subyacente de ASP.Net. Con el Repeater de nuevo en su estado original, agregamos un nuevo ObjectDataSource llamado CategoriesDataSource a travs de la etiqueta inteligente del Repeater. Configure el ObjectDataSource para que utilice la clase CategoriesBLL, pero en lugar de utilizar el mtodo GetCategories(), usaremos GetCategoriesAndNumberOfProducts() (Ver Figura 11).

Figura 11. Configure el ObjectDataSource para que utilice el mtodo GetCategoriesAndNumberOfProducts Luego actualice el ItemTemplate para que la propiedad Text del LinkButon este asignada mediante declaracin, usando la sintaxis de enlace de datos e incluya los campos de datos CategoryName y NumberOfProducts. El marcado declarativo completo para el Repeater y el ObjectDataSource CategoriesDataSource es el siguiente:
<asp:Repeater ID="Categories" runat="server" DataSourceID="CategoriesDataSource"> <HeaderTemplate> <ul> </HeaderTemplate> <ItemTemplate> <li><asp:LinkButton runat="server" ID="ViewCategory" Text='<%# String.Format("{0} ({1:N0})", _

Eval("CategoryName"), Eval("NumberOfProducts")) %>' /> </li> </ItemTemplate> <FooterTemplate> </ul> </FooterTemplate> </asp:Repeater> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategoriesAndNumberOfProducts" TypeName="CategoriesBLL"> </asp:ObjectDataSource>

La salida presentada por la actualizacin de la DAL para incluir una columna NumberOfProducts es la misma que usamos en el enfoque del controlador de evento ItemDataBound (refirase de Nuevo a la figura 4 para ver una captura de pantalla del Repeater mostrando los nombres de las categoras y el numero de productos). Paso 3. Mostrar los productos de la categora seleccionada Hasta el momento tenemos el Repeater Categories mostrando una lista de las categoras junto con el nmero de productos en cada categora. El Repeater utiliza un LinkButton para cada categora, que al ser presionado, origina una devolucin de datos, momento en el cual necesitamos mostrar los productos para la categora seleccionada en el DataList CategoryProducts Uno de los desafos a los que nos enfrentamos es hacer que el DataList muestre solo aquellos productos para la categora seleccionada. En el tutorial Maestro/Detalle usando un GridView Maestro seleccionable con un DetailsView de detalles vimos como construir un GridView cuyas filas pueden ser seleccionadas, con los detalles de la fila seleccionada mostrados en un DetailsView en la misma pgina. El ObjectDataSource del GridView devuelve informacin sobre todos los productos usando el mtodo GetProducts() de ProductsBLL, mientras que el ObjectDataSource del DetailsView devuelve la informacin del producto seleccionado usando el mtodo GetProductsByProductID(productID). El valor del parmetro productID fue suministrado mediante declaracin asocindolo con el valor de la propiedad SelectedValue del GridView. Desafortunadamente, el Repeater no tiene la propiedad SelectedValue y no puede servir como origen de parmetros.

Nota: Este es uno de los desafos que aparecen cuando usamos el LinkButton en un Repeater. Si en su lugar usamos un HiperLink para pasar el CategoryID en una cadena de consulta, podramos usarlo para que el campo QueryString sea la fuente para el valor del parmetro. Antes de preocuparnos por la carencia de la propiedad SelectedValue en el Repeater, primero enlazaremos el DataList a un ObjectDataSource y especificaremos ItemTemplate. Desde la etiqueta inteligente del DataList, opte por agregar un nuevo ObjectDataSource llamado CategoryProductsDataSource y configrelo para que use el mtodo GetProductsByCategoryID(categoryID) de la clase ProductsBLL. Como el DataList en este tutorial solo ofrece una interfaz de solo lectura, sintase libre de establecer las listas desplegables de las pestaas INSERT, UPDATE y DELETE en Ninguno.

Figura 2. Configure el ObjectDataSource para que utilice el mtodo GetProductsBycategoryID(categoryID) de la clase ProductsBLL Como el mtodo GetProductsByCategoryID(categoryID) espera un parmetro de entrada (categoryID), el asistente de configuracin del Origen de datos nos permite especificar el origen del parmetro. Haga que las categoras sean mostradas en un DataList o GridView, debemos establecer la lista desplegable del origen del parmetro en Control y el ControlID en el ID del control de datos web. Sin embargo, como el Repeater carece de una propiedad SelectedValue, este no puede ser usado como fuente del parmetro.

Si verifica, encontrara que la lista desplegable ControlID solo contiene un ID del control CategoryProducts, el ID del DataList. Por ahora, establezca la lista desplegable del origen del Parmetro en Ninguno. Finalizaremos asignando mediante programacin el valor de este parmetro cuando el LinkButton Category es presionado en el Repeater.

Figura 13. No especificar un origen de parmetro para el parmetro categoryID Despus de completar el asistente de configuracin del origen de datos, Visual Studio genera automticamente el ItemTemplate del DataList. Remplace este ItemTemplate por defecto con el template usado en el tutorial anterior, tambin establezca la propiedad RepeatColumns del DataList en 2. Despus de realizar estos cambios el marcado declarativo para el DataList y su ObjectDataSource lucir como el siguiente:
<asp:DataList ID="CategoryProducts" runat="server" DataKeyField="ProductID" DataSourceID="CategoryProductsDataSource" RepeatColumns="2" EnableViewState="False"> <ItemTemplate> <h5><%# Eval("ProductName") %></h5> <p> Supplied by <%# Eval("SupplierName") %><br /> <%# Eval("UnitPrice", "{0:C}") %> </p> </ItemTemplate>

</asp:DataList> <asp:ObjectDataSource ID="CategoryProductsDataSource" OldValuesParameterFormatString="original_{0}" runat="server" SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL"> <SelectParameters> <asp:Parameter Name="categoryID" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Actualmente

el

parmetro

categoryID

del

ObjectDataSource

CategoryProductsDataSource no est definido, por lo cual no se muestra ningn producto cuando visitamos la pagina. Lo que necesitamos hacer es tener el valor de este parmetro basados en el CategoryID de la categora presionada en el Repeater. Esto introduce dos desafos: primero, como determinar cuando un LinkButton en el ItemTemplate del Repeater ha sido presionado y el segundo, como determinar el CategoryID correspondiente a la categora cuyo LinkButton ha sido presionado. El LinkButton al igual que los controles Button o ImageButton tienen un evento Click y Command. El evento Click est diseado sencillamente para hacer notar que un LinkButton ha sido presionado. Sin embargo, en ocasiones, adicionalmente a hacer notar que el LinkButton ha sido presionado necesitamos pasar alguna informacin extra al controlador de eventos. Si este es el caso, las propiedades CommandName y CommandArgument del LinkButton pueden ser asignadas a esta informacin extra. Luego cuando el LinkButton es presionado, se genera su evento Command (en lugar del evento Click) y el controlador de evento pasa los valores de las propiedades CommandName y CommandArgument. Con el evento Command generado desde dentro de una plantilla en el Repeater, se genera el evento ItemCommand del Repeater y este pasa los valores de CommandName y CommandArgument del LinkButton que ha sido presionado (o Button o ImageButton). Por lo tanto para determinar cuando un LinkButton Categora en el Repeater ha sido presionado, necesitamos hacer lo siguiente: 1. Establezca la propiedad CommandName del LinkButton en el ItemTemplate del Repeater a algn valor (Yo use ListProducts). Definiendo este valor CommandName, el evento Command del LinkButton se genera cuando el LinkButton es presionado.

2. Defina la propiedad CommandArgument del LinkButton al valor del CategoryId del tem actual. 3. Cree un controlador de evento para el evento ItemCommand del Repeater. En el controlador de evento, establezca el parmetro CategoryID del ObjectDataSource CategoryProductsDataSource al valor pasado en el CommandArgument. El siguiente marcado del ItemTemplate para el Repeater Categories implementa los pasos 1 y 2. Note como el valor CommandArgument es asignado al CategoryID del tem de datos usando la sintaxis de enlace de datos:
<ItemTemplate> <li> <asp:LinkButton CommandName="ListProducts" runat="server" CommandArgument='<%# Eval("CategoryID") %>' ID="ViewCategory" Text='<%# string.Format("{0} ({1:N0})", _ Eval("CategoryName"), Eval("NumberOfProducts")) %>'> </asp:LinkButton> </li></ItemTemplate>

Cada vez que creamos un controlador de eventos ItemCommand es prudente verificar que el valor CommandName entrante porque cualquier evento Command generado por cualquier Button, LinkButton o ImageButton dentro del Repeater hace que el comando ItemCommand se genere. Aunque actualmente solo tenemos LinkButton, en un futuro podramos (u otro desarrollador de nuestro equipo) agregar controles web Button adicionales al Repeater, que al ser presionados generen el mismo controlador de evento ItemCommand. Por lo tanto lo mejor es siempre asegurarnos verificando la propiedad CommandName y solo proceder con nuestra lgica de programacin si coincide con el valor esperado. Despus de asegurarnos que el valor de CommandName pasado es igual a ListProducts, el controlador de evento entonces asigna el parmetro CategoryID del ObjectDataSource CategoryProductsDataSource al valor pasado en el CommandArgument. Esta modificacin al SelectParameters del ObjectDataSource origina automticamente que el DataList se enlace nuevamente al origen de datos, mostrando los productos de la categora seleccionada recientemente.
Protected Sub Categories_ItemCommand(source As Object, e As RepeaterCommandEventArgs) _

Handles Categories.ItemCommand ' If it's the "ListProducts" command that has been issued... If String.Compare(e.CommandName, "ListProducts", True) = 0 Then ' Set the CategoryProductsDataSource ObjectDataSource's CategoryID parameter ' to the CategoryID of the category that was just clicked (e.CommandArgument)... CategoryProductsDataSource.SelectParameters("CategoryID").DefaultValue = _ e.CommandArgument.ToString() End If End Sub

Con estas adiciones, nuestro tutorial esta completo. Tmese un momento para probar la pgina en un navegador. La figura 14 muestra la pantalla cuando visitamos la pagina por primera vez. Como ninguna categora ha sido seleccionada, no se muestra ningn producto. Haciendo clic sobre una categora como Produce, se muestran los productos de la categora Produce en una vista de dos columnas (Ver Figura 15).

Figura 14. No se muestra ningn product cuando visitamos la pgina por primera vez

Figura 15. Presionando la categora Produce mostramos sus productos a la derecha Resumen Como vimos en este y el anterior tutorial, los reportes Maestros/Detalles pueden ser distribuidos en dos pginas o consolidados en una sola. Sin embargo, mostrar el reporte Maestro/Detalle en una sola pgina introduce algunos desafos sobre cmo mejorar el diseo de los registros maestros y detalles en la pgina. En el tutorial Maestro/Detalle usando un GridView Maestro seleccionable con un DetailsView de detalles hicimos que los registros detalles aparecieran debajo del los registros maestros, en este tutorial usamos tcnicas CSS para hacer que los registros maestros aparezcan flotando a la izquierda de los detalles. Junto con los reportes Maestro/Detalle tuvimos la oportunidad de explorar como recuperar el nmero de productos asociados con cada categora as como realizar una lgica del lado del servidor cuando un LinkButton (o Button o ImageButton) es presionado dentro de un Repeater. Con este tutorial completamos nuestro examen de los reportes Maestros/Detalles con el DataList y Repeater. Nuestro prximo grupo de tutoriales mostrara como agregar capacidades de edicin y eliminacin al control DataList.

EDICION Y ELIMINACION DE DATOS CON EL DATALIST

36. RESUMEN DE EDICIN Y ELIMINACIN DE DATOS EN EL DATALIST


Aunque el DataList carece de capacidades de insercin y actualizacin, en este tutorial veremos cmo crear un DataList que soporte la edicin y eliminacin de sus datos subyacentes. Introduccin En el tutorial Resumen sobre la insercin, actualizacin y eliminacin de datos vimos como insertar, actualizar y eliminar datos usando la arquitectura de aplicacin, un ObjectDataSource y los controles GridView, DetailsView y FormView. Con el ObjectDataSource y estos tres controles web de datos, implementar una interfaz sencilla de modificacin de datos que los capture y envuelva seleccionando una casilla de verificacin desde su etiqueta inteligente. No es necesario escribir ningn cdigo. Desafortunadamente, el DataList carece de las capacidades de edicin y eliminacin predefinidas inherentes en el control GridView. Esta funcionalidad se pierde debido en parte al hecho que el control DataList es una reliquia de la versin anterior de ASP.Net, cuando la declaracin de controles de origen de datos y la modificacin de pginas de datos libres de cdigo no era vlida. Aunque el DataList en ASP.Net 2.0 no ofrece la misma funcionalidad de modificacin de datos de fuera de caja que el GridView, podemos usar tcnicas de ASP.Net 1.x para incluir esta funcionalidad. Este enfoque requiere un poco de cdigo, como veremos en este tutorial, en su lugar el DataList tiene algunos eventos y propiedades para agregar este proceso. En este tutorial veremos cmo crear un DataList que soporte la edicin y eliminacin de sus datos subyacentes. En futuros tutoriales examinaremos escenarios de edicin y eliminacin ms avanzados, incluyendo la validacin de entradas de campo y el manejo adecuado de excepciones generadas desde la capa de acceso a datos o la capa lgica de negocio, entre otros. Nota: Al igual que el DataList, el control Repeater carece de la funcionalidad predeterminada de insercin, actualizacin o eliminacin. Aunque esta funcionalidad puede ser agregada; el DataList incluye propiedades y eventos que no se encuentran en el Repeater y pueden simplificar la adicin de esta capacidad. Paso 1. Crear las pginas web de tutoriales de edicin e insercin

Antes de comenzar a explorar como actualizar y eliminar datos desde el DataList, primero nos tomaremos un momento para crear las pginas ASP.Net en nuestro proyecto de sitio web que necesitaremos para este y algunos de los siguientes tutoriales. Comenzamos agregando una nueva carpeta llamada EditDeleteDataList. Luego agregamos las siguientes pginas ASP.Net a la carpeta, asegurndonos de asociar cada una con la pgina maestra Site.master: Default.aspx Basics.aspx BatchUpdate.aspx ErrorHandling.aspx UIValidation.aspx CustomizedUI.aspx OptimisticConcurrency.aspx ConfirmationOnDelete.aspx UserLevelAcces.aspx

Figura 1. Agregar pginas ASP.Net para los tutoriales

Al igual que las otras carpetas, Default.aspx en la carpeta EditDeleteDataList muestra los tutoriales de esa seccin. Recordemos que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Por lo tanto agregue este control de usuario a Default.aspx arrastrndolo desde el Explorador de Soluciones hasta la vista diseo de la pgina.

Figura 2. Agregar el control de usuario SectionLevelTutorialListing.ascx a Default.aspx Finalmente agregue las pginas como entradas al archivo Web.sitemap.

Especficamente agregue el siguiente marcado despus del <sitemapnode> Reportes Maestro/Detalle con DataList y Repeater:
<siteMapNode title="Editing and Deleting with the DataList" description="Samples of Reports that Provide Editing and Deleting Capabilities" url="~/EditDeleteDataList/Default.aspx" > <siteMapNode title="Basics" description="Examines the basics of editing and deleting with the DataList control." url="~/EditDeleteDataList/Basics.aspx" /> <siteMapNode title="Batch Update" description="Examines how to update multiple records at once in a fully-editable DataList."

url="~/EditDeleteDataList/BatchUpdate.aspx" /> <siteMapNode title="Error Handling" description="Learn how to gracefully handle exceptions raised during the data modification workflow." url="~/EditDeleteDataList/ErrorHandling.aspx" /> <siteMapNode title="Adding Data Entry Validation" description="Help prevent data entry errors by providing validation." url="~/EditDeleteDataList/UIValidation.aspx" /> <siteMapNode title="Customize the User Interface" description="Customize the editing user interfaces." url="~/EditDeleteDataList/CustomizedUI.aspx" /> <siteMapNode title="Optimistic Concurrency" description="Learn how to help prevent simultaneous users from overwritting one another s changes." url="~/EditDeleteDataList/OptimisticConcurrency.aspx" /> <siteMapNode title="Confirm On Delete" description="Prompt a user for confirmation when deleting a record." url="~/EditDeleteDataList/ConfirmationOnDelete.aspx" /> <siteMapNode title="Limit Capabilities Based on User" description="Learn how to limit the data modification functionality based on the user s role or permissions." url="~/EditDeleteDataList/UserLevelAccess.aspx" /> </siteMapNode>

Despus de actualizar Web.sitemap tomese un momento para ver los tutoriales del sitio web por medio de un navegador. El men de la izquierda ahora incluye tems para los tutoriales Edicin y Eliminacin con DataList.

Figura 3.El mapa del sitio ahora incluye entradas para los tutoriales de Edicin y Eliminacin con el DataList Paso 2. Examinar tcnicas para actualizar y eliminar datos La Edicin y Eliminacin de datos con el GridView es muy fcil porque debajo de las cubiertas, el GridView y el ObjectDataSource trabajan en concierto. Como discutimos en el tutorial Examen de los eventos asociados con la insercin, actualizacin y eliminacin, cuando el botn Actualizar de una fila es presionado, el GridView asigna automticamente sus campos para usar el enlace de datos de dos vas a la coleccin UpdateParameters de su ObjectDataSource y luego invoca el mtodo Update() del ObjectDataSource: Lamentablemente el DataList no proporciona ninguna de las funcionalidades

predefinidas. Es nuestra responsabilidad asegurarnos que los valores del usuario sean asignados a los parmetros del ObjectDataSource y que sea llamado su mtodo Update(). Para ayudarnos en esta tarea, el DataList proporciona las siguientes propiedades y eventos: La propiedad DataKeyField cuando actualizamos o eliminamos, necesitamos ser capaces de identificar de forma nica cada tem en el DataList. Establezca esta propiedad al campo de llave primaria de los datos mostrados. Haciendo esto

poblamos la coleccin DataKeys del DataList con el valor DataKeyField especificado para cada tem del DataList. El evento EditCommand se genera cuando es presionado un Button, LinkButton o ImageButton cuya propiedad CommandName est definida en Edit. El evento CancelCommand se genera cuando es presionado un Button, LinkButton o ImageButton cuya propiedad CommandName est definida en Cancel. El evento UpdateCommand se genera cuando es presionado un Button, LinkButton o ImageButton cuya propiedad CommandName es establecida en Update. El evento DeleteCommand se dispara cuando es presionado un Button, LinkButton o ImageButton cuya propiedad CommandName es definida en Delete. Usando estas propiedades y eventos, hay cuatro enfoques que pueden ser usados para actualizar y eliminar datos desde el DataList: 1. Usando tcnicas ASP.Net 1.x el DataList existi antes de ASP.Net 2.0 y el ObjectDataSource y era capaz de actualizar y eliminar datos completamente a travs de medios programados. Esta tcnica abandona el ObjectDataSource y requiere que enlacemos los datos al DataList directamente desde la capa lgica de negocios, para recuperar los datos a visualizar y cuando actualicemos o eliminemos un registro. 2. Usando un solo control ObjectDataSource en la pgina para Seleccionar, actualizar e eliminar aunque el DataList carece de las capacidades de insercin, actualizacin y eliminacin del GridView, no es razn para que no podamos agregarlas nosotros. Con este enfoque, usamos un ObjectDataSource como en los ejemplos del GridView, pero debemos crear un controlador de evento para el evento UpdateCommand del DataList donde establecemos los parmetros del ObjectDataSource y llamamos su mtodos Update(). 3. Usando un control ObjectDataSource para seleccionar, pero actualizamos y eliminamos directamente de la BLL cuando usamos la opcin 2, necesitamos escribir un poco de cdigo en el evento UpdateCommand, asignando valores a los parmetros y as sucesivamente. En su lugar podemos usar el ObjectDataSource para seleccionar, pero hacemos la actualizacin y eliminacin llamando directamente a la BLL (como en la opcin 1). En mi opinin, actualizar

datos directamente con la BLL permite un cdigo mas legible que asignar los UpdateParameters del ObjectDataSource y llamar su mtodo Update(). 4. Usando medios declarativos a travs de varios ObjectDataSource los anteriores tres enfoques requieren un poco de cdigo. Si desea usar tanta sintaxis declarativa como sea posible, una opcin final es incluir mltiples ObjectDataSource en la pgina. El primer ObjectDataSource recupera los datos desde la BLL y los enlaza al DataList. Para la actualizacin se agrega otro ObjectDataSource, pero lo agregamos directamente en el EditItemTemplate del DataList. Para incluir soporte de eliminacin, agregamos otro ObjectDataSource en el ItemTemplate. Con este enfoque, el ObjectDataSource integrado usa ControlParameters para enlazar mediante declaracin los parmetros del ObjectDataSource a los controles de entrada del usuario (en lugar de tener que especificarlos mediante programacin en el controlador de evento UpdateCommand del DataList) Este enfoque aun requiere un poco de cdigo, necesitamos llamar a los command Update() y Delete() del ObjectDataSource integrado, pero requiere mucho menos que con los otros tres enfoques. El inconveniente aqu es que diversos ObjectDataSource enredan la pgina, afectando su legibilidad en general. Si se ve forzado a solamente usar uno de esos enfoques, escoja la primera opcin porque es la que proporciona mayor flexibilidad y porque el DataList fue originalmente diseado para acomodarse a este enfoque. Aunque el DataList fue ampliado para trabajar con los controles de origen de datos de ASP.Net 2.0, no tiene todas las funciones o puntos extensibles de los controles de datos web oficiales de ASP.Net 2.0 (el GridView, FormView y DetailsView). Las opciones 2 a 4 no tienen ningn merito. Este y los futuros tutoriales de edicin y eliminacin usaran un ObjectDataSource para recuperar los datos a mostrar y llamara directamente a la BLL para actualizar y eliminar datos (opcin 3). Paso 3. Agregar el DataList y configurar su ObjectDataSource En este tutorial crearemos un DataList que muestre la informacin de los productos y que cada producto proporcione al usuario la capacidad de editar el nombre y precio y tambin eliminar el producto. En concreto, recuperaremos los registros a mostrar usando un ObjectDataSource, pero realizaremos las acciones de actualizacin y eliminacin directamente con la BLL. Antes de preocuparnos sobre la implementacin

de las capacidades de edicin y eliminacin para el DataList, primero haremos que la pgina muestre los productos en una interfaz de solo-lectura. Como hemos examinado estos pasos en tutoriales anteriores, procederemos a travs de ellos rpidamente. Comience abriendo la pgina Basics.aspx en la carpeta EditDeleteDataList y desde la vista diseo agregue un DataList a la pgina. Luego desde la etiqueta inteligente del DataList, cree un nuevo ObjectDataSource. Como estamos trabajando con datos de productos, configrelo para que use la clase ProductsBLL. Para recuperar todos los productos, seleccione el mtodo GetProducts() en el pestaa SELECT.

Figura 4. Configure el ObjectDataSource para que use la clase ProductsBLL

Figura 5. Recupere la informacin de los productos usando el mtodo GetProducts()

El DataList al igual que el GridView no est diseado para insertar datos, por lo tanto seleccione la opcin (Ninguno) en la lista desplegable de la pestaa INSERT. Tambin seleccione (Ninguno) en las pestaas UPDATE y DELETE ya que las actualizaciones y eliminaciones sern realizadas mediante programacin por medio de la BLL.

Figura 6. Confirme que las listas desplegables de las pestanas INSERT, UPDATE y DELETE estn definidas en (Ninguno) Despus de configurar el ObjectDataSource, presione Finalizar, regresando al diseador. Como del vimos en los ejemplos Visual pasados, Studio cuando completamos crea la un configuracin ObjectDataSource, automticamente

ItemTemplate para los DropDownList, mostrando cada uno de los campos de datos. Remplace este ItemTemplate con uno que muestre solamente el nombre y precio del producto. Tambin establezca la propiedad RepeatColumns en 2. Nota: Como discutimos en el tutorial Resumen sobre Insercin, actualizacin y eliminacin de datos, cuando modificamos datos usando el ObjectDataSource, nuestra arquitectura requiere que removamos la propiedad OldValuesParameterFormatString del marcado declarativo del ObjectDataSource (o reiniciarlo a su valor por defecto {0}). Sin embargo, en este tutorial usamos el ObjectDataSource solamente para recuperar datos. Por lo tanto no necesitamos modificar el valor de la propiedad OldValuesParameterFormatString del ObjectDataSource (aunque no nos har dao no hacerlo).

Despus de remplazar el ItemTemplate del DataList con uno personalizado, el marcado declarativo en nuestra pgina lucir similar al siguiente:
<asp:DataList ID="DataList1" runat="server" DataKeyField="ProductID" DataSourceID="ObjectDataSource1" RepeatColumns="2"> <ItemTemplate> <h5> <asp:Label runat="server" ID="ProductNameLabel" Text='<%# Eval("ProductName") %>'></asp:Label> </h5> Price: <asp:Label runat="server" ID="Label1" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> <br /> <br /> </ItemTemplate> </asp:DataList> <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProducts" TypeName="ProductsBLL" OldValuesParameterFormatString="original_{0}"> </asp:ObjectDataSource>

Tmese un momento para ver el progreso por medio de un navegador. La figura 7 muestra el DataList mostrando el nombre y precio para cada producto en dos columnas.

Figura 7. Los nombres y precios de los productos son mostrados en un DataList de dos columnas

Nota: El DataList tiene un nmero de propiedades que son requeridas para el proceso de actualizacin y eliminacin, y estos valores son almacenados en el view state. Por lo tanto cuando construimos un DataList para que soporte la edicin y eliminacin de datos, es esencial que el view state del DataList este habilitado. El lector ms astuto podr recordar que es capaz de deshabilitar el View State cuando creamos GridViews, DetailsView y FormView editables. Esto es porque los controles web ASP.Net 2.0 pueden incluir un control state, cuyo estado persiste a travs de las devoluciones de datos como el view state, pero considrelo esencial. Deshabilitar el View State en el GridView solamente omite informacin de estado trivial, pero mantiene el control state (el cual incluye el estado necesario para la edicin y eliminacin). El DataList, ha sido creado en el timeframe de ASP.Net 1.x, que no utiliza control state y por lo tanto debe tener el view state habilitado. Vea Control State Vs View State para mayor informacin sobre el propsito del control state y como difiere del view state. Paso 4. Agregar una interfaz de usuario de edicin El control GridView est compuesto de una coleccin de campos (BoundFields, CheckBoxFields, TemplateFields y as sucesivamente). Estos campos pueden ajustar su marcado presentado dependiendo de su modo. Por ejemplo en un modo de solo lectura, un BoundField muestra el valor de su campo de datos como un texto, en modo de edicin, este se presenta un control web TextBox cuya propiedad Text es asignada al valor del campo de datos. Por otra parte el DataList presenta sus tems usando templates. tems de solo lectura son presentados usando el Itemtemplate mientras que los tems en modo de edicin son presentados por medio del EditItemTemplate. Hasta el momento nuestro DataList tiene solo un ItemTemplate. Para soportar la funcionalidad de edicin a nivel de tem, necesitamos agregar un EditItemTemplate que contenga el marcado que ser mostrado para un tem editable. Para este tutorial, usaremos controles web TextBox para la edicin del nombre y precio unitario del producto. El EditItemTemplate puede ser creado mediante declaracin o por medio del diseador (seleccionando la opcin Edit. templates en la etiqueta inteligente del DataList). Para

usar la opcin Edit Templates, primero presione el enlace Edit Templates en la pestaa inteligente y luego seleccione el tem EditItemTemplate en la lista desplegable.

Figura 8. Opte por trabajar con el EditItemTemplate del DataList Luego escriba Product Name: y Price: y arrastre dos controles web TextBox desde la caja de herramientas hasta la interfaz EditItemTemplate en el diseador. Establezca las propiedades ID del los TextBoxes en ProductName y UnitPrice.

Figura 9. Agregar un TextBox para el nombre del producto y el precio

Necesitamos enlazar los valores de los campos de datos correspondientes a las propiedades Text de los dos TextBoxes. Desde la etiqueta inteligente de los TextBoxes, presione el enlace Edit DataBindings y luego asocie el campo de datos apropiado con la propiedad Text, como se muestra en la figura 10. Nota: Cuando enlazamos el campo de datos UnitPrice al campo Text del TextBox Price, podemos formatear su valor como moneda ({0:c}), como un numero en general ({0:N}), o dejarlo sin formato.

Figura 10. Enlace los campos de datos ProductName y UnitPrice a las propiedades Text de los TextBoxes Note que el cuadro de dialogo de la Figura 10 no incluye la casilla de verificacin del enlace de dos vas que es presentado cuando editamos un TemplateField en el GridView o DetailsView; o una plantilla en el FormView. La funcin de enlace de datos de doble va permite que el valor ingresado en la entrada del control web sea asignado automticamente a los InsertParameters o UpdateParameters correspondientes del ObjectDataSource cuando insertamos o actualizamos datos. El DataList no soporta el enlace de datos de dos vas como veremos luego en este tutorial, despus el usuario hace sus cambios y est listo para actualizar los datos, necesitaremos acceder mediante programacin a las propiedades Text de los TextBoxes y pasar sus valores al mtodo UpdateProducts apropiado en la clase ProductsBLL. Finalmente necesitamos agregar botones Update y Cancel al EditItemTemplate. Como vimos en el tutorial Maestro/Detalle usando una lista de registros maestros con un DataList detalles, cuando un Button, LinkButton o ImageButton cuyo CommandName

est definida, es presionado dentro de un Repeater o DataList, el evento ItemCommand del Repeater o DataList es generado. Para el DataList, si la propiedad CommandName es definida a cierto valor, un evento adicional tambin podra generarse. Los valores especiales de la propiedad CommandName, incluyen entre otros a: Cancel, genera el evento CancelCommand Edit, genera el evento EditCommand Update, genera el evento UpdateCommand

Tenga en mente que estos eventos son generados adems del evento ItemCommand. Agregue al EditItemTemplate dos controles web Button, uno cuyo CommandName se establezca en Update y otro que se establezca en Cancel. Despus de agregar estos dos controles web Button, el diseador lucir similar al siguiente:

Figura 11. Agregue los botones Update y Cancel al EditItemTemplate Con el EditItemTemplate completo, el marcado declarativo de su DataList lucir similar al siguiente:
<asp:DataList ID="DataList1" runat="server" DataKeyField="ProductID" DataSourceID="ObjectDataSource1" RepeatColumns="2"> <ItemTemplate> <h5> <asp:Label runat="server" ID="ProductNameLabel" Text='<%# Eval("ProductName") %>' />

</h5> Price: <asp:Label runat="server" ID="Label1" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> <br /> <br /> </ItemTemplate> <EditItemTemplate> Product name: <asp:TextBox ID="ProductName" runat="server" Text='<%# Eval("ProductName") %>' /><br /> Price: <asp:TextBox ID="UnitPrice" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>' /><br /> <br /> <asp:Button ID="UpdateProduct" runat="server" CommandName="Update" Text="Update" /> <asp:Button ID="CancelUpdate" runat="server" CommandName="Cancel" Text="Cancel" /> </EditItemTemplate> </asp:DataList>

Paso 5. Agregar el enlace para ingresar al modo de edicin Hasta este punto nuestro DataList tiene una interfaz de edicin definida en su EditItemTemplate, sin embargo actualmente no hay una forma en que el usuario que visita nuestra pgina indique que desea editar la informacin de un producto. Necesitamos agregar un Button Edit a cada producto; para que al ser presionado muestre el tem del DataList en modo de edicin. Comience agregando un Button Edit al ItemTemplate, ya sea a travs del diseador o mediante declaracin. Adems defina la propiedad CommandName del Button Edit en Edit. Despus que haya agregado este Button Edit, tmese un momento para ver la pgina por medio del navegador. Con esta adicin, cada producto mostrado debe incluir un Button Edit.

Figura 12. Agregar botones Update y Cancel al EditItemTemplate Haciendo clic en el button se origina una devolucin de datos, pero no se muestra el producto de la lista en modo de edicin. Para hacer un producto editable, necesitamos: 1. Establezca la propiedad EditItemIndex del DataList en el ndice del DataListItem cuyo Button Edit fue presionado. 2. Enlace nuevamente los datos al DataList. Cuando el DataList en enlazado nuevamente, el DataList cuyo ItemIndex corresponde con el EditItemIndex del DataList ser presentado usando su EditItemTemplate. Como el evento EditCommand del DataList es generado cuando el button Edit es presionado, creemos un controlador de evento EditCommand con el siguiente cdigo:
Protected Sub DataList1_EditCommand(source As Object, e As DataListCommandEventArgs) _ Handles DataList1.EditCommand ' Set the DataList's EditItemIndex property to the ' index of the DataListItem that was clicked DataList1.EditItemIndex = e.Item.ItemIndex ' Rebind the data to the DataList DataList1.DataBind() End Sub

El

controlador

de

evento

EditCommand

es

pasado

en

un

objeto

de

tipo

DataListCommandEventArgs como su Segundo parmetro de entrada, el cual incluye

una referencia al DataListItem cuyo button Edit fue presionado (e.Item). El controlador de evento primero establece el EditItemIndex del DataList al ItemIndex del DataListItem editable y luego enlaza de nuevo los datos al DataList llamando al mtodo DataBind() del DataList. Despus de agregar este controlador de evento, visite nuevamente esta pgina en un navegador. Presionando el button Edit ahora hacemos que el producto presionado sea editable.

Figura 13. Presionando el Button Edit hacemos que el producto sea editable Paso 6. Guardar los cambios del Usuario Presionando los botones Update y Cancel del producto editable hasta este punto no hacen nada, para agregar esta funcionalidad necesitamos crear un controlador de evento para los eventos UpdateCommand y CancelCommand del DataList. Comenzamos creando un controlador de evento para CancelCommand, el cual se ejecutara cuando el button Cancel del producto editado es presionado y este regresara el DataList al estado previo a la edicin. Para hacer que el DataList presente todos sus tems en el modo de solo lectura, necesitamos: 1. Establecer la propiedad EditItemIndex del DataList en el ndice de un ndice del DataListItem no existente. -1 es una opcin segura, ya que los ndices del DataListItem comienzan en 0.

2. Enlace nuevamente los datos al DataList. Como no hay un ItemIndex del DataListItem que corresponda en el EditItemIndex del DataList, el DataList completo ser presentado en modo de solo lectura. Estos pasos pueden ser realizados con el siguiente cdigo en el controlador de evento:
Protected Sub DataList1_CancelCommand(source As Object, e As DataListCommandEventArgs) _ Handles DataList1.CancelCommand ' Set the DataList's EditItemIndex property to -1 DataList1.EditItemIndex = -1 ' Rebind the data to the DataList DataList1.DataBind() End Sub

El ultimo controlador de evento que necesitamos completar es el controlador de evento UpdateCommand. Este controlador de evento necesita: 1. Acceder mediante programacin al nombre del producto y precio que el usuario ha ingresado as como al ProductID del producto editado. 2. Iniciar el proceso de actualizacin llamando a la recarga UpdateProduct apropiada en la clase ProductsBLL. 3. Establezca la propiedad EditItemIndex del DataList en el ndice de un ndice del DataListItem no existente. -1 es una opcin segura, ya los ndices del DataListitem comienzan en 0. 4. Enlace nuevamente los datos al DataList. Como no hay un ItemIndex del DataListItem que corresponda en el EditItemIndex del DataList, el DataList completo ser presentado en modo de solo lectura. Los pasos 1 y 2 son responsables de guardar los cambios del usuario, los pasos 3 y 4 devuelven el DataList a su estado previo a la edicin despus que los cambios han sido guardados y son idnticos a los pasos realizados en el controlador de evento CancelCommand. Para conseguir actualizar el nombre y precio del producto, necesitamos usar el mtodo FindControl para referenciar mediante programacin los controles web TextBox dentro del EditItemTemplate. Tambin necesitamos el valor ProductID del producto editado. Cuando enlazamos inicialmente el ObjectDataSource al DataList, Visual Studio asigno la

propiedad DataKeyField del DataList al valor de la llave primaria de su origen de datos (ProductID). Este valor puede ser recuperado desde la coleccin DataKeys del DataList. Tmese un momento para asegurarse que la propiedad DataKeyField est establecida en efecto en ProductID. El siguiente cdigo implementa los cuatro pasos:
Protected Sub DataList1_UpdateCommand(source As Object, e As DataListCommandEventArgs) _ Handles DataList1.UpdateCommand ' Read in the ProductID from the DataKeys collection Dim productID As Integer = Convert.ToInt32(DataList1.DataKeys(e.Item.ItemIndex)) ' Read in the product name and price values Dim productName As TextBox = CType(e.Item.FindControl("ProductName"), TextBox) Dim unitPrice As TextBox = CType(e.Item.FindControl("UnitPrice"), TextBox) Dim productNameValue As String = Nothing If productName.Text.Trim().Length > 0 Then productNameValue = productName.Text.Trim() End If Dim unitPriceValue As Nullable(Of Decimal) = Nothing If unitPrice.Text.Trim().Length > 0 Then unitPriceValue = Decimal.Parse(unitPrice.Text.Trim(), NumberStyles.Currency) End If ' Call the ProductsBLL's UpdateProduct method... Dim productsAPI As New ProductsBLL() productsAPI.UpdateProduct(productNameValue, unitPriceValue, productID) ' Revert the DataList back to its pre-editing state DataList1.EditItemIndex = -1 DataList1.DataBind() End Sub

El controlador de evento comienza leyendo en la coleccin DataKeys el ProductID del producto editado. Luego los dos TextBoxes en el EditItemTemplate son referenciados y sus propiedades Text son almacenadas en variables locales, productNameValue y unitPriceValue. Usamos el mtodo Decimal.Parse() para leer el valor desde el TextBox UnitPrice para que si el valor ingresado tiene un smbolo moneda, este pueda ser convertido correctamente en un valor Decimal. Nota: Los valores de los TextBoxes ProductName y UnitPrice solamente son asignados a las variables productNameValue y unitPriceValue si las propiedades Text de los TextBoxes tienen un valor especificado. De lo contrario, un valor de Nothing es usado

para las variables, que tiene el efecto de actualizar los datos con un valor Null de base de datos. Si es as nuestro cdigo trata de convertir las cadenas vacas en valores Null de base de datos, que es el comportamiento por defecto de la interfaz de edicin en los controles GridView, DetailsView y FormView. Con los controladores de evento EditCommand, UpdateCommand y CancelCommand completos, un visitante puede editar el nombre y precio de un producto. Las figuras de la 14 a la 16 muestran en accin el flujo de trabajo de la edicin.

Figura 14. Cuando visitamos por primera vez la pgina, todos los productos estn en modo de solo lectura

Figura 15. Para actualizar el nombre o precio del Producto, presionamos el Button Edit

Figura 16. Despus de cambiar el valor, presionando el Button Update regresamos al modo de solo lectura Paso 7. Agregar capacidades de eliminacin Los pasos para agregar capacidades de eliminacin al DataList son similares a los de agregar funcionalidades de edicin. En concreto, necesitamos agregar un Button Delete al ItemTemplate, que al ser presionado: 1. Lea el ProductID del producto correspondiente por medio de la coleccin DataKeys. 2. Realiza la ProductsBLL. 3. Enlaza nuevamente los datos al DataList Comience agregando un button Delete al ItemTemplate Cuando presionamos un Button cuyo CommandName es Edit, Update o Cancel se genera el evento ItemCommand del DataList junto con un evento adicional (por ejemplo, cuando usamos Edit se genera el evento EditCommnad). De forma similar, cualquier Button, LinkButton o ImageButton en el DataList cuya propiedad CommandName se establezca en Delete hace que el evento DeleteCommand se genere (junto con ItemCommand). eliminacin llamando al mtodo DeleteProduct del la clase

Agregando un button Delete al lado del button Edit en el ItemTemplate, establecemos su propiedad CommandName en Delete. Despus de agregar este control Button el marcado declarativo del ItemTemplate del DataList lucir similar a:
<ItemTemplate> <h5> <asp:Label runat="server" ID="ProductNameLabel" Text='<%# Eval("ProductName") %>' /> </h5> Price: <asp:Label runat="server" ID="Label1" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> <br /> <asp:Button runat="server" id="EditProduct" CommandName="Edit" Text="Edit" /> <asp:Button runat="server" id="DeleteProduct" CommandName="Delete" Text="Delete" /> <br /> <br /> </ItemTemplate>

Despus de crear un controlador de evento para el evento DeleteCommand del DataList, usamos el siguiente cdigo:
Protected Sub DataList1_DeleteCommand(source As Object, e As DataListCommandEventArgs) _ Handles DataList1.DeleteCommand ' Read in the ProductID from the DataKeys collection Dim productID As Integer = Convert.ToInt32(DataList1.DataKeys(e.Item.ItemIndex)) ' Delete the data Dim productsAPI As New ProductsBLL() productsAPI.DeleteProduct(productID) ' Rebind the data to the DataList DataList1.DataBind() End Sub

Presionando el Button Delete originamos una devolucin de datos y generamos el evento DeleteCommand del DataList. En el controlador de evento, el valor ProductID del producto presionado es accedido desde la coleccin DataKeys. Luego el producto es eliminado llamando al mtodo DeleteProduct de la clase ProductsBLL.

Despus de eliminar el producto, es muy importante que enlacemos los datos al DataList (DataList1.DataBind()) de lo contrario el DataList continuara mostrando el producto que recin ha sido eliminado. Resumen Aunque el DataList carece hasta el momento del soporte de edicin y eliminacin disfrutado con el GridView, con un poco de cdigo este puede ser mejorado para incluir estas funciones. En este tutorial vimos como crear un listado de dos columnas de los productos que pueden ser eliminados y cuyos nombres y precios pueden ser editados. Agregar soporte de edicin y eliminacin es cuestin de incluir los controles web apropiados en el ItemTemplate y EditItemTemplate y de crear los controladores de evento correspondientes, leemos los valores de las llaves primarias y los ingresados por el usuario y la interfaz con la capa lgica de negocios. Aunque hemos agregado capacidades de edicin y eliminacin al DataList, este carece de funciones ms avanzadas. Por ejemplo, no hay validacin de las entradas de los campos si un usuario ingresa un precio de Muy costoso, se generara una excepcin cuando Decimal.Parse intente convertir Muy Costoso en un Decimal. De forma similar, si hay un problema en actualizar los datos en la capa lgica de negocios o en la de acceso a datos se presentara al usuario una pantalla de error estndar. Sin ninguna confirmacin en el botn Delete, eliminar un producto accidentalmente es muy probable. En futuros tutoriales veremos cmo mejorar la experiencia de edicin del usuario.

37. REALIZACION DE ACTUALIZACIONES POR LOTES


Aprenderemos como crear un DataList completamente editable donde todos sus tems estn modo de edicin y los valores puedan ser guardados presionando un botn Update All en la pgina. Introduccin En el tutorial anterior examinamos como crear un DataList editable a nivel de tem. Al igual que un GridView editable estndar, cada tem en el DataList incluyo un botn Edit, que al ser presionado, hacia el tem editable. Mientras que esta edicin a nivel de tem funciona bien para datos que son actualizados ocasionalmente, en ciertos escenarios el usuario requiere editar muchos registros a la vez. Si un usuario necesita editar docenas de registros y es forzado a presionar Edit, hacer sus cambios y presionar Update para cada uno de ellos, la cantidad de veces que presiona un botn puede afectar su productividad. En aquellas situaciones, una mejor opcin es proporcionar un DataList completamente editable, uno donde todos los tems estn en modo de edicin y cuyos valores pueden ser editados presionando el botn Update All en la pagina (Ver Figura 1).

Figura 1. Cada tem en el DataList completamente editable puede ser modificado

En este tutorial examinaremos como permitir a los usuarios actualizar la informacin de la direccin de los proveedores usando un DataList completamente editable. Paso 1. Crear la interfaz de usuario editable en el ItemTemplate del DataList En el tutorial anterior, donde creamos un DataList editable a nivel de tem, usamos dos plantillas: ItemTemplate contena la interfaz de usuario de solo lectura (los controles web Label para mostrar el nombre y precio de cada producto) EditItemTemplate contena la interfaz de usuario en modo de edicin (los dos controles web TextBox) La propiedad EditItemIndex del DataList indica que DataListItem (si lo hay) es presentado usando el EditItemTemplate. En concreto, el DataListItem cuyo valor ItemIndex coincida con la propiedad EditItemIndex del DataList es presentado usando el EditItemTemplate. Este modelo funciona bien cuando solamente puede ser editado un tem a la vez, pero no funciona cuando creamos un DataList completamente editable. Para un DataList completamente editable, deseamos que todos los DataListItem sean presentados usando la interfaz editable. La forma ms sencilla de realizar esto es definiendo la interfaz editable en el ItemTemplate. Para modificar la informacin de la direccin de los proveedores, la interfaz editable contiene el nombre del proveedor como texto y luego TextBoxes para los valores de direccin, ciudad y pas. Comience abriendo la pgina BatchUpdate.aspx, agregue un control DataList y establezca su propiedad ID en Suppliers. Desde la etiqueta inteligente del DataList, opte por agregar un nuevo ObjectDataSource llamado SuppliersDataSource.

Figura 2. Crear un nuevo ObjectDataSource llamado SuppliersDataSource Configure anterior, el en ObjectDataSource lugar de para la recuperar los datos del usando el mtodo el

GetSuppliers() de la clase SuppliersBLL (Ver Figura 3). Al igual que con el tutorial actualizar informacin proveedor usando ObjectDataSource, trabajaremos directamente con la capa de acceso a datos. Por lo tanto establezca la lista desplegable de la pestaa UPDATE en (Ninguna) (Ver Figura 4).

Figura 3. Recuperar la informacin del proveedor usando el mtodo GetSuppliers()

Figura 4. Establezca la lista desplegable de la pestaa UPDATE en (Ninguno) Despus de completar el asistente, Visual Studio genera automticamente el ItemTemplate del DataList para mostrar cada campo devuelto por el origen de datos en un control web Label. Necesitamos modificar este template para que proporcione en su lugar la interfaz de edicin. El ItemTemplate puede ser personalizado por medio del diseador usando la opcin Edit Templates en la etiqueta inteligente del DataList o directamente por medio de la sintaxis declarativa. Tmese un momento para crear la interfaz de edicin que muestre el nombre del proveedor como un texto, pero que incluya TextBoxes para los valores de direccin, ciudad y pas del proveedor. Despus de hacer estos cambios, el marcado declarativo de su pgina lucir similar al siguiente:
<asp:DataList ID="Suppliers" runat="server" DataKeyField="SupplierID" DataSourceID="SuppliersDataSource"> <ItemTemplate> <h4><asp:Label ID="CompanyNameLabel" runat="server" Text='<%# Eval("CompanyName") %>' /></h4> <table border="0"> <tr> <td class="SupplierPropertyLabel">Address:</td> <td class="SupplierPropertyValue"> <asp:TextBox ID="Address" runat="server" Text='<%# Eval("Address") %>' /> </td>

</tr> <tr> <td class="SupplierPropertyLabel">City:</td> <td class="SupplierPropertyValue"> <asp:TextBox ID="City" runat="server" Text='<%# Eval("City") %>' /> </td> </tr> <tr> <td class="SupplierPropertyLabel">Country:</td> <td class="SupplierPropertyValue"> <asp:TextBox ID="Country" runat="server" Text='<%# Eval("Country") %>' /> </td> </tr> </table> <br /> </ItemTemplate> </asp:DataList> <asp:ObjectDataSource ID="SuppliersDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetSuppliers" TypeName="SuppliersBLL"> </asp:ObjectDataSource>

Nota: Al igual que el tutorial anterior, el DataList de este tutorial debe tener su view state habilitado En el ItemTemplate usamos dos nuevas clases CSS, SupplierPropertyLabel y

SupplierPropertyValue, que han sido agregadas a la clase Styles.css y configuradas para usar las mismas definiciones de estilo que las clases CSS ProductPropertyLabel y ProductPropertyValue.
.ProductPropertyLabel, .SupplierPropertyLabel { font-weight: bold; text-align: right; } .ProductPropertyValue, .SupplierPropertyValue {

padding-right: 35px; }

Despus de realizar estos cambios, visite esta pgina por medio de un navegador. Como muestra la figura 5, cada tem del DataList muestra el nombre del proveedor como texto y usa TextBoxes para mostrar la direccin, ciudad y pas.

Figura 5. Cada proveedor en el DataList es editable Paso 2. Agregar un botn Update All Aunque cada proveedor en la figura 5 tiene sus campos direccin, ciudad y pas mostrados en un TextBox, actualmente el botn Update All no esta habilitado. En lugar de tener un botn Update por cada tem, en un DataList completamente editable es comn tener un solo botn UpdateAll en la pgina; que cuando es presionado actualiza todos los registros en el DataList. Para este tutorial, agregaremos dos botones UpdateAll, uno en la parte superior de la pgina y otro en la parte inferior (aunque presionar cualquiera de los dos botones tendr el mismo efecto) Comenzamos agregando un control web Button encima del DataList y estableciendo su propiedad ID en UpdateAll. Luego agregue el segundo control web Button debajo del DataList, estableciendo su propiedad ID en UpdateAll2. Establezca las propiedades Text

para los dos Button el UpdateAll. Por ltimo creamos controladores de eventos para el evento Click en ambos botones. En lugar de duplicar la lgica de actualizacin en cada uno de los controladores de eventos, agruparemos la lgica en un tercer mtodo, UpdateAllSuppliersAddresses, haciendo que los controladores de evento simplemente invoquen este tercer mtodo.
Protected Sub UpdateAll1_Click(sender As Object, e As EventArgs) _ Handles UpdateAll1.Click UpdateAllSupplierAddresses() End Sub Protected Sub UpdateAll2_Click(sender As Object, e As EventArgs) _ Handles UpdateAll2.Click UpdateAllSupplierAddresses() End Sub Private Sub UpdateAllSupplierAddresses() ' TODO: Write code to update _all_ of the supplier addresses in the DataList End Sub

La figura 6 muestra la pgina despus que el botn UpdateAll ha sido agregado

Figura 6. Dos botones UpdateAll han sido agregados a la pgina

Paso 3. Actualizar la informacin de la direccin de todos los proveedores Con todos los tems del DataList mostrados en la interfaz de edicin y con la adicin de los botones UpdateAll, todo lo que nos resta es escribir el cdigo para realizar la actualizacin por lotes. Especficamente necesitamos ir a travs de los tems del DataList y llamar al mtodo UpdateSupplierAddress de la clase SuppliersBLL para cada uno. La coleccin de instancias DataListItem hace que el DataList sea accesible por medio de la propiedad tems del DataList. Con una referencia al DataListItem, podemos grabar el correspondiente SupplierID desde la coleccin DataKeys y referenciar mediante programacin los controles web TextBox dentro del ItemTemplate como muestra el siguiente cdigo:
Private Sub UpdateAllSupplierAddresses() ' Create an instance of the SuppliersBLL class Dim suppliersAPI As New SuppliersBLL() ' Iterate through the DataList's items For Each item As DataListItem In Suppliers.Items ' Get the supplierID from the DataKeys collection Dim supplierID As Integer = Convert.ToInt32(Suppliers.DataKeys(item.ItemIndex)) ' Read in the user-entered values Dim address As TextBox = CType(item.FindControl("Address"), TextBox) Dim city As TextBox = CType(item.FindControl("City"), TextBox) Dim country As TextBox = CType(item.FindControl("Country"), TextBox) Dim addressValue As String = Nothing, _ cityValue As String = Nothing, _ countryValue As String = Nothing If address.Text.Trim().Length > 0 Then addressValue = address.Text.Trim() End If If city.Text.Trim().Length > 0 Then cityValue = city.Text.Trim() End If If country.Text.Trim().Length > 0 Then countryValue = country.Text.Trim() End If ' Call the SuppliersBLL class's UpdateSupplierAddress method suppliersAPI.UpdateSupplierAddress _

(supplierID, addressValue, cityValue, countryValue) Next End Sub

Cuando

el

usuario

presiona

uno

de

los

botones

UpdateAll,

el

mtodo

UpdateSupplierAddresses itera cada DataListItem en el DataList Suppliers y llama al mtodo UpdateSupplierAddress de la clase SuppliersBLL, pasando los valores correspondientes. Un valor no ingresado para la direccin, ciudad o pas es pasado como un valor de Nothing al UpdateSupplierAddress (en lugar de una cadena en blanco), que resultara en un valor NULL de base de datos para los campos de los registros subyacentes. Nota: Como una mejora, deseamos agregar un control web Label de estado a la pgina y proporcionar algn mensaje de confirmacin despus que la actualizacin por lotes es realizada. Actualizar solamente aquellas direcciones que hayan sido modificadas El algoritmo de actualizacin por lotes usado en este tutorial llama al mtodo UpdateSupplierAddress para todos los proveedores en el DataList, independientemente de si la informacin de direccin ha sido cambiada. Aunque aquellas actualizaciones a ciegas usualmente no son un problema de rendimiento, pueden provocar registro superfluos si esta auditando cambios para la tabla de la base de datos. Por ejemplo si usa disparadores para grabar todas las actualizaciones de la tabla Suppliers para una tabla auditada, cada vez que el usuario presione el botn UpdateAll un nuevo registro auditado ser creado por cada proveedor en el sistema, independientemente de si el usuario realizo cualquier cambio. Las clases DataTable y DataAdapter de ADO.Net son diseadas para soportar actualizaciones de lotes donde solamente modificamos, eliminamos y agregamos nuevos registros en cualquier comunicacin con la base de datos. Cada fila en el DataTable tiene una propiedad RowState que indica si la fila ha sido agregada al DataTable, eliminada de el, modificada o permanece sin cambios. Cambiando el valor de cualquier columna de la fila marcamos la fila como modificada. En la clase SuppliersBLL actualizamos la informacin de direccin de un proveedor especifico, leyendo primero el registro del proveedor en el SuppliersDataTable y luego

estableciendo los valores de las columnas Address, City y Country usando el siguiente cdigo:
Public Function UpdateSupplierAddress _ (supplierID As Integer, address As String, city As String, country As String) _ As Boolean Dim suppliers As Northwind.SuppliersDataTable = _ Adapter.GetSupplierBySupplierID(supplierID) If suppliers.Count = 0 Then ' no matching record found, return false Return False Else Dim supplier As Northwind.SuppliersRow = suppliers(0) If address Is Nothing Then supplier.SetAddressNull() Else supplier.Address = address End If If city Is Nothing Then supplier.SetCityNull() Else supplier.City = city End If If country Is Nothing Then supplier.SetCountryNull() Else supplier.Country = country End If ' Update the supplier Address-related information Dim rowsAffected As Integer = Adapter.Update(supplier) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End If End Function

Este cdigo asigna inocentemente los valores de direccin, ciudad y pas pasndolos al SuppliersRow en el SuppliersDataTable independientemente de si los valores fueron cambiados. Estas modificaciones hacen que la propiedad RowState del SuppliersRow sea marcado como modificado. Cuando el mtodo Update de la capa de acceso a datos

es llamado, este ve que el SupplierRow ha sido modificado y por lo tanto enva un comando UPDATE a la base de datos. Sin embargo, imagina que agregamos cdigo a este mtodo para que solamente asigne los valores de direccin, ciudad y pas y luego los pase si son diferentes a los valores existentes en el SuppliersRow. En el caso que la direccin, ciudad y pas sean iguales a los datos existentes, no se hicieron cambios y el RowState del SupplierRow ser marcado como unchanged. El resultado final es que cuando el mtodo Update de la DAL es llamado, no se llamara a la base de datos porque el SuppliersRow no ha sido modificado. Para presentar este cambio, remplazamos las sentencias que asignan ciegamente los valores de direccin, ciudad y pas pasados con el siguiente cdigo:
' Only assign the values to the SupplierRow's column values if they differ If address Is Nothing AndAlso Not supplier.IsAddressNull() Then supplier.SetAddressNull() ElseIf (address IsNot Nothing AndAlso supplier.IsAddressNull) _ OrElse (Not supplier.IsAddressNull() AndAlso _ String.Compare(supplier.Address, address) <> 0) Then supplier.Address = address End If If city Is Nothing AndAlso Not supplier.IsCityNull() Then supplier.SetCityNull() ElseIf (city IsNot Nothing AndAlso supplier.IsCityNull) _ OrElse (Not supplier.IsCityNull() AndAlso _ String.Compare(supplier.City, city) <> 0) Then supplier.City = city End If If country Is Nothing AndAlso Not supplier.IsCountryNull() Then supplier.SetCountryNull() ElseIf (country IsNot Nothing AndAlso supplier.IsCountryNull) _ OrElse (Not supplier.IsCountryNull() AndAlso _ String.Compare(supplier.Country, country) <> 0) Then supplier.Country = country End If

Con este cdigo agregado, el mtodo Update de la DAL enva una sentencia UPDATE a la base de datos solamente para aquellos registros cuyos valores relacionados con la direccin han sido cambiados. De forma alternativa, podemos realizar el seguimiento independientemente de si hay diferencias entre los campo de direccin pasados y los datos de la base de datos, si no hay ninguno, simplemente nos saltamos el llamado al mtodo Update de la DAL. Este enfoque trabaja bien si est usando el mtodo directo DB, ya que el mtodo directo DB no pasa una instancia SuppliersRow cuyo RowState puede ser verificado para determinar si es necesario una llamada a la base de datos. Nota: Cada vez que el mtodo UpdateSupplierAddress es invocado, se hace una llamada a la base de datos para recuperar la informacin relacionada con el registro actualizado. Luego si hay cualquier cambio en los datos, se hace otra llamada a la base de datos para actualizar la tabla de la fila. Este flujo de trabajo puede ser optimizado creando una recarga al mtodo UpdateSupplierAddress que acepte una instancia EmployeesDataTable que tenga todos los cambios de la pgina BatchUpdate.aspx. Luego este puede llamar a la base de datos para recuperar todos los registros de la tabla Suppliers. Los dos conjuntos de resultados luego pueden ser enumerados y solamente aquellos registros donde han ocurrido cambios pueden ser actualizados. Resumen En este tutorial vimos como crear un DataList completamente editable, permitiendole al usuario modificar rpidamente la informacin de la direccin de mltiples usuarios. Comenzamos definiendo la interfaz de edicin, un control TextBox para los valores de direccin, ciudad y pas en el ItemTemplate del DataList. Luego agregamos botones Update All encima y debajo del DataList. Despus un usuario realiza sus cambios y presiona uno de los botones UpdateAll, los DataListItems son enumerados y se hace un llamado al mtodo UpdateSupplierAddress de la clase SuppliersBLL.

38. MANEJO DE EXCEPCIONES A NIVEL DE DAL Y BLL


En este tutorial veremos cmo manejar adecuadamente las excepciones generadas durante el flujo de trabajo de la actualizacin del DataList editable. Introduccin En el tutorial Resumen sobre la edicin y eliminacin de datos en el DataList, creamos un DataList que ofreci capacidades de edicin y eliminacin sencillas. Aunque completamente funcional, era muy poco amigable al usuario, ya que cualquier error que ocurriera durante el proceso de eliminacin o edicin terminaba en una excepcin no manejada. Por ejemplo, omitir el nombre del producto cuando editamos un producto o ingresar un valor de precio como Muy Econmico genera una excepcin. Como esta excepcin no es atrapada en ningn cdigo, esta llega hasta el tiempo de ejecucin de ASP.Net mostrando los detalles de la excepcin en la pgina web. Como vimos en el tutorial Manejo de Excepciones a nivel de DAL y BLL en una pgina ASP.Net, si una excepcin es generada desde la profundidad de la capa lgica de negocios o de acceso a datos, los detalles de la excepcin son devueltos al ObjectDataSource y luego al GridView. Vimos como manejar adecuadamente estas excepciones creando controladores de evento Updated o RowUpdated para el ObjectDataSource o GridView, verificando las excepciones y luego indicando que la excepcin fue manejada. Sin embargo nuestros tutoriales de DataList, no estn utilizando el ObjectDataSource para actualizar o eliminar datos, ya que estamos trabajando directamente con la BLL. Con el fin de detectar las excepciones generadas en la BLL o DAL, necesitamos implementar cdigo de manejo de excepcin dentro del cdigo subyacente de nuestra pgina ASP.Net. En este tutorial veremos cmo manejar adecuadamente las excepciones generadas durante el flujo de trabajo de edicin de un DataList editable. Nota: En el tutorial Resumen sobre la edicin y eliminacin de datos en el DataList discutimos diferentes tcnicas para editar y eliminar datos en el DataList, algunas tcnicas involucraron usar un ObjectDataSource para actualizar y eliminar. Si est empleando estas tcnicas, puede manejar las excepciones de la BLL o DAL por medio de los controladores de evento Updated y Deleted del ObjectDataSource.

Paso 1. Crear un DataList editable Antes de preocuparnos acerca del manejo de excepciones que ocurren durante el flujo de trabajo de la edicin, primero crearemos un DataList editable. Abra la pgina ErrorHandling.aspx de la carpeta EditDeleteDataList, arrastre un DataList al diseador y establezca su propiedad ID en Products, agregue un nuevo ObjectDataSource llamado ProductsDataSource. Configure el ObjectDataSource para que use el mtodo GetProducts() de la clase ProductsBLL para seleccionar los registros; establezca la lista desplegable de las pestaas INSERT, UPDATE y DELETE en (Ninguno)

Figura 1. Recupere la informacin del producto usando el mtodo GetProducts() Despus de completar el asistente del ObjectDataSource, Visual Studio creara automticamente un ItemTemplate para el DataList. Remplace esto con un ItemTemplate que muestre el nombre de cada producto y su precio y que incluya un button Edit. Luego cree un EditItemTemplate con un control web TextBox para el nombre, precio y buttons para Cancel y Update. Finalmente establezca la propiedad RepeatColumns del DataList en 2. Despus de estos cambios, el marcado declarativo de su pgina lucir similar al siguiente. Verifique que las propiedades CommandName de los botones Edit, Cancel y Update estn definidas en Edit, Cancel y Update respectivamente.
<asp:DataList ID="Products" runat="server" DataKeyField="ProductID" DataSourceID="ProductsDataSource" RepeatColumns="2"> <ItemTemplate> <h5> <asp:Label runat="server" ID="ProductNameLabel"

Text='<%# Eval("ProductName") %>' /> </h5> Price: <asp:Label runat="server" ID="Label1" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> <br /> <asp:Button runat="server" id="EditProduct" CommandName="Edit" Text="Edit" /> <br /> <br /> </ItemTemplate> <EditItemTemplate> Product name: <asp:TextBox ID="ProductName" runat="server" Text='<%# Eval("ProductName") %>' /> <br /> Price: <asp:TextBox ID="UnitPrice" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> <br /> <br /> <asp:Button ID="UpdateProduct" runat="server" CommandName="Update" Text="Update" /> <asp:Button ID="CancelUpdate" runat="server" CommandName="Cancel" Text="Cancel" /> </EditItemTemplate> </asp:DataList> <asp:ObjectDataSource ID="ProductsDataSource" runat="server" SelectMethod="GetProducts" TypeName="ProductsBLL" OldValuesParameterFormatString="original_{0}"> </asp:ObjectDataSource>

Nota: Para este tutorial el View State del DataList debe estar habilitado. Tmese un momento para ver nuestro progreso a travs de un navegador (Ver Figura 2).

Figura 2. Cada producto incluye un Button Edit Actualmente, el Button Edit solamente origina una devolucin de datos pero no hace que el producto sea editable. Para habilitar la edicin necesitamos crear controladores de eventos para los eventos EditCommand, CancelCommand y UpdateCommand. Los eventos EditCommand y CancelCommand solamente actualizan la propiedad EditItemIndex del DataList y enlazan nuevamente los datos al DataList:
Protected Sub Products_EditCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.EditCommand ' Set the DataList's EditItemIndex property to the ' index of the DataListItem that was clicked ' Rebind the data to the DataList Products.DataBind() End Sub Protected Sub Products_CancelCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.CancelCommand ' Set the DataList's EditItemIndex property to -1 Products.EditItemIndex = -1 ' Rebind the data to the DataList Products.DataBind() End Sub Products.EditItemIndex = e.Item.ItemIndex

El controlador del evento UpdateCommand est un poco ms involucrado. Este necesita leer el ProductID de los productos editados en la coleccin DataKeys junto con el nombre del producto y el precio de los TextBoxes en el EditItemTemplate; luego llama al mtodo UpdateProduct de la clase ProductsBLL antes de regresar el DataList al estado previo a la edicin. Por ahora, usaremos el mismo cdigo que el controlador del evento UpdateCommand en el tutorial Resumen sobre la edicin y eliminacin de datos en el DataList. Agregaremos el cdigo para manejar adecuadamente las excepciones en el paso 2.
Protected Sub Products_UpdateCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.UpdateCommand ' Read in the ProductID from the DataKeys collection Dim productID As Integer = Convert.ToInt32(Products.DataKeys(e.Item.ItemIndex)) ' Read in the product name and price values Dim productName As TextBox = CType(e.Item.FindControl("ProductName"), TextBox) Dim unitPrice As TextBox = CType(e.Item.FindControl("UnitPrice"), TextBox) Dim productNameValue As String = Nothing If productName.Text.Trim().Length > 0 Then productNameValue = productName.Text.Trim() End If Dim unitPriceValue As Nullable(Of Decimal) = Nothing If unitPrice.Text.Trim().Length > 0 Then unitPriceValue = Decimal.Parse(unitPrice.Text.Trim(), _ System.Globalization.NumberStyles.Currency) End If ' Call the ProductsBLL's UpdateProduct method... Dim productsAPI As New ProductsBLL() productsAPI.UpdateProduct(productNameValue, unitPriceValue, productID) ' Revert the DataList back to its pre-editing state Products.EditItemIndex = -1 Products.DataBind() End Sub

Hay que ver que puede haber una entrada invlida en el formulario debido a un precio unitario mal formateado, un valor de UnitPrice ilegal como -$5.00 o la omisin del nombre de un producto que generaran una excepcin. Como el controlador de evento UpdateCommand no incluye hasta el momento ningn cdigo de manejo de

excepciones, la excepcin ira hasta el tiempo de ejecucin de ASP.Net donde la mostrara al usuario final (Ver Figura 3) Paso 2. Manejar adecuadamente las excepciones en el controlador del evento UpdateCommand Durante el flujo de trabajo de edicin, las excepciones pueden ocurrir en el controlador del evento UpdateCommand, la BLL o la DAL. Por ejemplo, si un usuario ingresa un precio de Muy Costoso, la sentencia Decimal.Parse en el controlador del evento UpdateCommand generara una excepcin FormatException. Si el usuario omite el nombre del producto o si el precio tiene un valor negativo, la DAL genera una excepcin. Cuando ocurre una excepcin, deseamos mostrar un mensaje informativo dentro de la misma pgina. Agregamos un control web Label a la pagina cuyo ID este definido en ExceptionDetails. Configure el texto del Label para mostrar la fuente en rojo, negrita, itlica y extra grande asignando su propiedad CssClass a la clase CSS Warning, que est definida en el archivo Styles.css. Cuando ocurre un error, solamente deseamos mostrar el Label una vez. Esto quiere decir que en las devoluciones de datos posteriores, el mensaje de advertencia del Label debe desaparecer. Esto puede ser realizado limpiando la propiedad Text del Label o estableciendo su propiedad Visible a False en el controlador del evento Page_Load (Regresemos de nuevo al tutorial Manejo de Excepciones a nivel de DAL y BLL en una pgina ASP.Net) o deshabilitando el soporte del View State del Label. Usaremos la ltima opcin.
<asp:Label ID="ExceptionDetails" EnableViewState="False" CssClass="Warning" runat="server" />

Cuando una excepcin es generada, asignaremos los detalles de la excepcin en la propiedad Text del control Label ExceptionDetails. Como su view state esta deshabilitado en una devolucin de datos posterior, se perdern los cambios mediante programacin de la propiedad Text, regresando de nuevo al texto por defecto (una cadena vaca), ocultando por lo tanto el mensaje de advertencia.

Para determinar cuando un error ha sido generado con el fin de mostrar un mensaje de ayuda en la pgina, necesitamos agregar un bloque Try Catch en el controlador del evento UpdateCommand. Las partes Try contienen cdigo que permite manejar una excepcin, mientras que el bloque Catch contiene cdigo que es ejecutado en vista de una excepcin. Verifique la seccin Manejo Fundamental de Excepciones en la documentacin del .NET Framework para mayor informacin sobre el bloque Try Catch.
Protected Sub Products_UpdateCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.UpdateCommand ' Handle any exceptions raised during the editing process Try ' Read in the ProductID from the DataKeys collection Dim productID As Integer = _ Convert.ToInt32(Products.DataKeys(e.Item.ItemIndex)) ... Some code omitted for brevity ... Catch ex As Exception ' TODO: Display information about the exception in ExceptionDetails End Try End Sub

Cuando una excepcin de cualquier tipo es manejada por cdigo dentro del bloque Try, el cdigo del bloque Catch ser ejecutado. El tipo de excepcin que es DbException, NoNullAllowedException, ArgumentException y as sucesivamente; depende exactamente de qu error fue generado en primer lugar. Si hay un problema a nivel de base de datos se genera una DbException. Si un valor ilegal es ingresado en los campos UnitPrice, UnitsInStock, UnitsOnOrder o ReorderLevel, se genera una ArgumentException ya que agregamos cdigo para validar el valor de estos campos en la clase ProductsDataTable (Ver Tutorial Creacin de una capa lgica de negocios) Podemos proporcionar una explicacin de ms ayuda al usuario, basando el texto del mensaje en el tipo de excepcin que atrapamos. El siguiente cdigo usado en un formulario anterior similar en el tutorial Manejo de Excepciones a nivel de DAL y BLL proporciona este nivel de detalle:
Private Sub DisplayExceptionDetails(ByVal ex As Exception) ' Display a user-friendly message ExceptionDetails.Text = "There was a problem updating the product. "

If TypeOf ex Is System.Data.Common.DbException Then ExceptionDetails.Text += "Our database is currently experiencing problems." + _ "Please try again later." ElseIf TypeOf ex Is System.Data.NoNullAllowedException Then ExceptionDetails.Text+="There are one or more required fields that are missing." ElseIf TypeOf ex Is ArgumentException Then Dim paramName As String = CType(ex, ArgumentException).ParamName ExceptionDetails.Text+=String.Concat("The ", paramName, " value is illegal.") ElseIf TypeOf ex Is ApplicationException Then ExceptionDetails.Text += ex.Message End If End Sub

Para completar este tutorial, simplemente llamamos al mtodo DisplayExceptionDetails en el bloque Cath pasando la instancia Exception atrapada (ex). Con el bloque Try Catch en su lugar, se presentara al usuario un mensaje de error ms informativo, como muestra la Figura 4 y 5. Note que en vista de una excepcin el DataList permanece en modo de edicin. Esto es debido a que una vez ocurre una excepcin, el flujo del control es redirigido inmediatamente al bloque Catch, saltando el cdigo que regrese el DataList a su estado previo a la edicin.

Figura 4. Un mensaje de error es mostrado si un usuario omite un campo requerido

Figura 5. Un mensaje de error es mostrado cuando ingresamos un precio negativo Resumen El GridView y el ObjectDataSource proporcionan controladores de eventos posteriores que incluyen informacin acerca de cualquier excepcin generada durante el flujo de trabajo de edicin y actualizacin, ya que sus propiedades pueden ser definidas para indicar si una excepcin fue o no fue manejada. Sin embargo, estas funciones no son validas cuando trabajamos con el DataList y usamos la BLL directamente. En su lugar somos responsables de implementar el manejo de excepciones. En este tutorial vimos como agregar manejo de excepciones para el flujo de trabajo de edicin de un DataList editable agregando un bloque Try Catch al controlador del evento UpdateCommand. Si una excepcin es generada durante el flujo de trabajo de edicin se ejecuta el cdigo el bloque Catch, mostrando informacin de ayuda en el Label ExceptionDetails. Hasta el momento, el DataList no hace ningn esfuerzo por evitar que las excepciones ocurran en primer lugar. Aunque sabemos que un precio negativo generara una excepcin, aun no hemos agregado ninguna funcionalidad proactiva para prevenir que un usuario ingrese una entrada invalida. En nuestro siguiente tutorial veremos cmo ayudar a reducir las excepciones causadas por entradas de usuario invlidas, agregando controles de validacin en el EditItemTemplate.

39. AGREGAR CONTROLES DE VALIDACIN A LA INTERFAZ DE EDICIN DEL DATALIST


En este tutorial veremos lo sencillo que es agregar controles de validacin al EditItemTemplate del DataList con el fin de proporcionar al usuario una interfaz de edicin a prueba de todo. Introduccin Hasta el momento en los tutoriales de edicin del DataList, las interfaces de edicin del DataList no incluyeron una validacin proactiva de las entradas del usuario aunque una entrada de usuario invalida como no escribir el nombre del producto o precios negativos generan una excepcin. En el tutorial anterior vimos como agregar cdigo de manejo de excepciones en el controlador del evento UpdateCommand del DataList con el fin de atrapar y mostrar de forma adecuada la informacin relacionada con las excepciones que han sido generadas. Sin embargo, idealmente la interfaz de edicin debe incluir controles de validacin que prevengan en primer lugar que un usuario ingrese datos no vlidos. En este tutorial veremos lo fcil que es agregar controles de validacin al EditItemTemplate del DataList con el fin de proporcionar al usuario una interfaz de edicin a prueba de todo. Especialmente este tutorial toma el ejemplo creado en el tutorial anterior y mejora la interfaz de edicin incluyendo la validacin apropiada. Paso 1. Replicar el ejemplo del Manejo de Excepciones a nivel de DAL y BLL En el tutorial Manejo de Excepciones a nivel de DAL y BLL creamos una pgina que muestra los nombres y precios de los productos en un DataList editable de dos columnas. Nuestro objetivo para este tutorial es ampliar la interfaz de edicin del DataList para incluir controles de validacin. En concreto nuestra lgica de validacin: Requiere que se ingrese un nombre de producto Asegura que el valor ingresado para el precio est en un formato de moneda valido Se asegura que el valor ingresado para el precio sea mayor o igual que cero, ya que un valor UnitPrice negativo es ilegal.

Antes de ver cmo mejorar el ejemplo anterior para incluir validacin, primero necesitamos replicar el ejemplo desde la pgina ErrorHandling.aspx de la carpeta EditDeleteDataList a la pgina de nuestro tutorial UIValidation.aspx. Para lograr esto necesitamos copiar el marcado declarativo y el cdigo fuente de la pgina ErrorHandling.aspx. Primero copiamos el marcado declarativo realizando los siguientes pasos: 1. Abra la pgina ErrorHandling.aspx en Visual Studio 2. Vaya al marcado declarativo de la pagina (presionando el botn Source en la parte inferior de la pagina) 3. Copie el texto dentro de las etiquetas <asp:Content> y </asp:Content> (lneas de la 3 a la 32) como muestra la figura 1.

Figura 1. Copie el texto dentro del control <asp:Content> 1. Abra la pgina UIValidation.aspx 2. Vaya al marcado declarativo de la pagina 3. Pegue el texto dentro del control <asp:Content> Para copiar el cdigo fuente, abra la pgina ErrorHandling.aspx.vb y copie solo el texto dentro de la clase EditDeleteDataList_ErrorHandling. Copie los tres controladores de eventos (Products_EditCommand, Products_CancelCommand y Products_UpdateCommand) junto con el mtodo DisplayExceptionDetails, pero no

copie la declaracin de la clase o las sentencias using. Pegue el texto copiado dentro de la clase EditDeleteDataList_UIValidation en UIValidation.aspx.vb. Despus de mover el contenido y cdigo desde ErrorHandling.aspx a UIValidation.aspx, tmese un momento para probar la pgina en un navegador. Debe ver la misma salida y experimentar la misma funcionalidad en cada una de las dos pginas (Ver Figura 2).

Figura 2. La pagina UIValidation.aspx imita la funcionalidad de ErrorHandling.aspx Paso 2. Agregar los controles de validacin al EditItemTemplate del DataList Cuando construimos formularios de ingreso de datos, es importante que el usuario ingrese cualquier campo requerido y que todas las entradas proporcionadas sean legales, con valores apropiadamente formateados. Para ayudar a asegurarnos que las entradas de un usuario son validas. Asp.Net proporciona cinco controles de validacin predefinidos que estn diseados para validar el valor de la entrada de un solo control web: RequiredFieldValidator asegura que se proporcione un valor CompareValidator valida un valor control el valor de otro control web o un valor constante, o se asegura que el formato del valor es legal para un tipo de dato especificado RangeValidator se asegura que el valor este dentro de un rango de valores RegularExpressionValidator valida un valor contra una expresin regular CustomValidator valida un valor contra un mtodo personalizado definido por el usuario

Para ms informacin sobre estos cinco controles refirase de nuevo al tutorial de Adicin de controles de validacin para las interfaces de edicin y eliminacin o verifique la seccin de Controles de Validacin de los Tutoriales de Inicio Rpido de ASP.Net. Para nuestro tutorial necesitaremos usar un RequiredFieldValidator que se asegure que el valor para el nombre del producto ha sido suministrado y un CompareValidator para asegurar que el precio ingresado tiene un valor mayor o igual que 0 y es presentado en un formato de moneda valido. Nota: Aunque con ASP.Net 1.x tenemos los mismos cinco controles de validacin, ASP.Net ha agregado un nmero de mejoras, las dos principales son el soporte de script en el lado del cliente para navegadores adems del Internet Explorer y la habilidad de dividir los controles de validacin de una pgina en grupos de validacin. Para mayor informacin sobre las nuevas funciones de los controles de validacin en 2.0, refirase a Discusin sobre los controles de validacin en ASP.Net 2.0. Comenzamos agregando los controles de validacin necesarios al EditItemTemplate del DataList. Esta tarea puede ser realizada por medio del diseador o presionando el enlace Edit Templates desde la etiqueta inteligente del DataList, o por medio de la sintaxis declarativa. Iremos paso a paso en el proceso usando la opcin EditTemplates desde la vista Design. Despus de seleccionar editar el EditItemTemplate del DataList, agregue un RequiredValidator arrastrndolo desde la caja de herramientas hasta la interfaz de edicin de la plantilla, colocndolo despus del TextBox ProductName.

Figura 3. Agregar un RequiredFieldValidator al EditItemTemplate despus del TextBox ProductName Todos los controles de validacin trabajan validando la entrada de un solo control web ASP.Net. Por lo tanto necesitamos indicar que el RequiredFieldValidator recin agregado validara el TextBox ProductName, esto se realiza definiendo la propiedad ControlToValidate del control de validacin en el ID del control web apropiado (ProductName, en este caso). Luego defina la propiedad ErrorMessage en Debe proporcionar el nombre del Producto y la propiedad Text en *. El valor de la propiedad Text, si es proporcionado, es el texto que muestra el control de validacin si la validacin falla. El valor de la propiedad ErrorMessage, el cual es requerido, es usado por el control ValidationSummary; si el valor de la propiedad Text es omitido, el valor de la propiedad ErrorMessage es mostrado por el control de validacin en una entrada no valida. Despus de establecer estas tres propiedades del RequiredFieldValidator, nuestra pantalla lucir similar a de la Figura 4.

Figura 4. Defina las propiedades ControlToValidate, ErrorMessage y Text del RequiredFieldValidator Con el RequiredFieldValidator agregado al EditItemTemplate, todo lo que nos resta es agregar la validacin necesaria para el TextBox Price del producto. Como UnitPrice es opcional cuando editamos un registro, no necesitamos agregar un RequiredFieldValidator. Sin embargo, necesitamos agregar un CompareValidator que se asegure que el UnitPrice si es suministrado, esta formateado apropiadamente como una moneda y es mayor o igual que 0. Agregue el CompareValidator dentro del EditItemTemplate y establezca su propiedad ControlToValidate en UnitPrice, su propiedad ErrorMessage en El Precio debe ser mayor o igual que cero y no puede incluir el smbolo moneda y su propiedad Text en *. Para indicar que el valor de UnitPrice debe ser mayor o igual a 0, establezca la propiedad Operator del CompareValidator en GreaterThanEqual, su propiedad ValueToCompare en 0 y su propiedad Type en Currency. Despus de agregar estos dos controles de validacin, la sintaxis declarativa del EditItemTemplate del DataList lucir similar a la siguiente:
<EditItemTemplate> Product name: <asp:TextBox ID="ProductName" runat="server" Text='<%# Eval("ProductName") %>'></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" ControlToValidate="ProductName"

ErrorMessage="You must provide the product's name" runat="server">*</asp:RequiredFieldValidator> <br /> Price: <asp:TextBox ID="UnitPrice" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:TextBox> <asp:CompareValidator ID="CompareValidator1" ControlToValidate="UnitPrice" ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol" Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0" runat="server">*</asp:CompareValidator><br /> <br /> <asp:Button ID="UpdateProduct" runat="server" CommandName="Update" Text="Update" /> <asp:Button ID="CancelUpdate" runat="server" CommandName="Cancel" Text="Cancel" /> </EditItemTemplate>

Despus de hacer estos cambios, abra la pgina en el navegador. Si intenta omitir el nombre o ingresa un valor de precio invalido cuando editamos un producto, aparecer un asterisco al lado del TextBox. Como muestra la figura 5, un valor de precio que incluye el smbolo de moneda como $19.95 es considerado invlido. El Type Currency del CompareValidator permite separadores de dgitos (como comas o puntos, dependiendo de las definiciones) y permite el smbolo ms o menos, pero no permite un smbolo de moneda. Este comportamiento puede dejar perplejos a los usuarios ya que la interfaz de edicin presenta el UnitPrice usando el formato de moneda.

Figura 5. Un asterisco aparece al lado de los TextBoxes con una entrada no es valida Aunque la validacin est trabajando como es, el usuario tiene que remover manualmente el smbolo moneda cuando editamos un registro, lo cual no es aceptable. Sin embargo si hay entradas no validas en la interfaz de edicin, los botones Update o Cancel al ser presionados no generan una devolucin de datos. Idealmente el button Cancel regresa el DataList a su estado previo a la edicin independientemente de la validez de las entradas del usuario. Tambin necesitamos asegurarnos que los datos de la pgina son validos antes de actualizar la informacin del producto en el controlador de evento UpdateCommand del DataList, ya que los controles de validacin en la lgica del lado del cliente pueden ser evitados por los usuarios cuyos navegadores no soporten JavaScript o tengan su soporte deshabilitado. Remover el smbolo de moneda desde el TextBox UnitPrice del EditItemTemplate Cuando usamos el Current Type del CompareValidator, la entrada que est siendo validada no debe incluir ningn smbolo de moneda. La presencia de cualquier smbolo origina que el CompareValidator marque la entrada como no valida. Sin embargo, nuestra interfaz de edicin actualmente incluye un smbolo de moneda en el TextBox UnitPrice, lo que significa que el usuario explcitamente debe remover el smbolo moneda antes de guardar sus cambios. Para remediar esto tenemos tres opciones: 1. Configurar el EditItemTemplate para que el valor del TextBox UnitPrice no sea formateado como moneda. 2. Permitir que el usuario ingrese un smbolo de moneda removiendo el CompareValidator y remplazndolo con un RegularExpressionValidator que verifique apropiadamente el valor de moneda formateado. El desafo aqu es que la expresin regular para validar un valor moneda no es tan sencilla como el CompareValidator y requiere escribir cdigo si deseamos incorporar diferentes smbolos. 3. Remover el control de validacin y confiar en la lgica personalizada de validacin del lado del servidor en el controlador de evento RowUpdating del GridView. En este tutorial trabajaremos con la opcin 1. Actualmente el UnitPrice est formateado como un valor moneda debido a la expresin de enlace de datos para el TextBox en el EditItemTemplate: <%#Eval(UnitPrice,{0:c})%>. Cambie la sentencia Eval a

Eval(UnitPrice,{0:cn2}), que formatea el resultado como un nmero con dos dgitos de precisin. Esto puede realizarse directamente en la sintaxis declarativa o presionando el enlace Edit DataBindings del TextBox UnitPrice en el EditItemTemplate del DataList. Con este cambio, el precio formateado en la interfaz de edicin incluye comas como separador de grupo y punto como separador decimal, pero no incluye el smbolo moneda. Nota: Cuando removemos el formato moneda de la interfaz editable, encuentro til colocar el smbolo moneda como texto fuera del TextBox. Esto sirve como un aviso al usuario que no necesita proporcionar el smbolo moneda. Arreglar el Button Cancel Por defecto los controles de validacin emiten JavaScript para realizar la validacin en el lado del cliente. Cuando un Button, LinkButton o ImageButton es presionado, los controles de validacin en la pgina son verificados en el lado del cliente antes que ocurra la devolucin de datos. Si hay cualquier dato invlido, la devolucin de datos es cancelada. Aunque en ciertos Buttons, la valides de los datos puede ser inmaterial, en cuyo caso la devolucin de los datos es cancelado porque el dato invalido es cancelado. El Button Cancel es un ejemplo de esto. Imagine que el usuario ingresa datos invlidos, omite el nombre del producto y luego decide que despus de todo no va a guardar el producto y presiona Cancel. Actualmente el button Cancel activa los controles de validacin en la pgina, cuyo reporte tiene el nombre del producto perdido y previene la devolucin de datos. Nuestro usuario tiene que escribir algn tipo de texto en el TextBox ProductName solo para cancelar el proceso de edicin. Afortunadamente el Button, LinkButton e ImageButton tienen una propiedad

CausesValidation que puede indicar si el Button presionado debe o no iniciar la lgica de validacin (Por defecto es True). Establezca la propiedad CausesValidation del Button en False. Asegurar que las entradas son validas en el controlador de evento UpdateCommand Debido al script emitido en el lado del cliente por los controles de validacin, si un usuario ingresa entradas no validas, los controles de validacin cancelan cualquier

devolucin de datos iniciada por los controles Button, LinkButton o ImageButton cuyas propiedades CausesValidation sean True (por defecto). Sin embargo, si el usuario est visitando con un navegador antiguo o uno cuyo soporte de JavaScript haya sido deshabilitado, la validacin en el lado del cliente no se ejecutara. Todos los controles de validacin de ASP.Net repiten la lgica de validacin inmediatamente antes de una devolucin de datos y reportan un resumen de valides de las entradas de la pagina por medio de la propiedad Page.IsValid. Sin embargo el flujo de la pgina no es interrumpido o detenido en ninguna forma basados en el valor de Page.IsValid. Como desarrolladores nuestra responsabilidad es asegurarnos que la propiedad Page.IsValid tiene un valor de True antes de proceder con el cdigo que asume entradas de datos validas. Si un usuario tiene JavaScript deshabilitado, visita nuestra pgina, edita un producto, ingresa un valor de precio de Muy Costoso y presiona el button Update, la validacin en el lado del cliente se omite y se genera una devolucin de datos. En la devolucin de datos la pgina ASP.Net ejecuta el controlador de evento UpdateCommand y genera una excepcin cuando intenta convertir Muy Costoso a Decimal. Como hemos manejado la excepcin, dicha excepcin ser manejada adecuadamente; pero podemos prevenir los datos no validos dejando solamente proceder en primer lugar al controlador de evento UpdateCommand si Page.IsValid tiene un valor de True. Agregamos el siguiente cdigo para iniciar el controlador de evento UpdateCommand, inmediatamente antes del bloque Try:
If Not Page.IsValid Then Exit Sub End If

Con esta adicin el producto intentara ser actualizado solo si los datos enviados son validos. La mayora de los usuarios no son capaces de devolver datos invlidos por el script de los controles de validacin en el lado del cliente, pero los usuarios cuyos navegadores no soporten JavaScript o que el soporte de JavaScript este deshabilitado, pueden saltar las verificaciones del lado del cliente y enviar datos invlidos. Nota: Un lector astuto recordara que cuando actualizamos datos con el GridView, no necesitamos verificar explcitamente la propiedad Page.IsValid en la clase de cdigo

subyacente de nuestra pgina. Esto es porque el GridView consulta la propiedad Page.IsValid por nosotros y solo procede con la actualizacin si este devuelve un valor de True. Paso 3. Resumir los problemas del ingreso de datos Adems de los cinco controles de validacin, ASP.Net incluye el control ValidationSummary, que muestra el ErrorMessage de los controles de validacin que han detectado datos no validos. Este resumen de datos puede ser mostrado como texto en la pgina web o a travs de un cuadro de dialogo en el lado del cliente. Ampliaremos este tutorial para incluir un cuadro de dialogo en el lado del cliente con el resumen de los problemas de validacin. Para realizar esto, arrastre un control ValidationSummary desde la caja de herramientas hasta el diseador. La localizacin del control ValidationSummary realmente no importa, ya que al configurarlo este mostrara el resumen en un cuadro de dialogo. Despus de agregar el control, defina su propiedad ShowSummary en False y su propiedad ShowMessageBox en True Con esta adicin, cualquier error de validacin ser resumido en un cuadro de mensaje en el lado del cliente (Ver Figura 6).

Figura 6. Los errores de validacin son resumidos en un cuadro de mensaje en el lado del cliente Resumen En este tutorial vimos como reducir el riesgo de excepciones usando controles de validacin proactivos para asegurarnos que las entradas de los usuarios sean validas antes de intentar usarlas en el flujo de trabajo de la edicin. ASP.Net proporciona cinco controles web de validacin que estn diseados para inspeccionar la entrada en particular de un control web y reportar la validez de su entrada. En este tutorial usamos

dos de los cinco controles, el RequiredFieldValidator y el CompareValidator para asegurarnos que los nombres de los productos fueron suministrados y que el precio tiene un formato moneda con un valor mayor o igual que cero. Agregar controles de validacin a la interfaz de edicin del DataList es sencillamente arrastrarlos hasta el EditItemTemplate desde la caja de herramientas y definir cuidadosamente sus propiedades. Por defecto los controles de validacin emiten automticamente script en el lado del cliente, proporcionando tambin validacin en el lado del servidor en la devolucin de datos, almacenando el resultado acumulativo en la propiedad Page.IsValid. Para evitar la validacin en el lado del cliente cuando un Button, LinkButton o ImageButton es presionado, establezca la propiedad CausesValidation del button en False. Tambin antes de realizar cualquier tarea con los datos enviados en la devolucin de datos, asegrese que la propiedad Page.IsValid devuelve True. Todos los tutoriales de edicin del DataList que hemos examinado tienen interfaces de edicin sencillas, un TextBox para el nombre del producto y otro para el precio. Sin embargo, la interfaz de edicin puede contener un conjunto de controles diferentes como DropDownLists, Calendars, RadioButtons, CheckBoxes y as sucesivamente, En nuestro prximo tutorial veremos cmo construir una interfaz de edicin que use variedad de controles web.

40. PERSONALIZAR LA INTERFAZ DE EDICION DEL DATALIST


En este tutorial vamos a crear una interfaz de edicin ms compleja para el DataList, una que incluya DropDownLists y un CheckBox. Introduccin El marcado y los controles web en el EditItemTemplate del DataList definen su interfaz editable. En todos los ejemplos de DataList editable que hemos examinado hasta el momento, las interfaces editables han estado compuestas de controles TextBox. En el tutorial anterior mejoramos la experiencia de edicin del usuario agregando controles de validacin. El EditItemTemplate puede ser expandido para que incluya controles web en lugar de TextBox, as como DropDownLists, RadioButtons, Calendars y as sucesivamente. Al igual que con los TextBoxes, cuando personalizamos la interfaz de edicin para incluir otros controles web, empleamos los siguientes pasos: 1. Agregamos el control web al EditItemTemplate 2. Usamos la sintaxis de enlace de datos para asignar el valor del campo de datos correspondiente a la propiedad apropiada. 3. En BLL. En este tutorial crearemos una interfaz de edicin mejorada para el DataList, una que incluya DropDownLists y un CheckBox. En concreto, crearemos un DataList que muestre la informacin de los productos y permita que el nombre del producto, proveedor, categora y estado sean actualizados (Ver Figura 1). el controlador del evento UpdateCommand, accedemos mediante programacin al valor del control web y lo pasamos al mtodo apropiado en la

Figura 1. La interfaz de edicin incluye un TextBox, dos DropDownList y un CheckBox Paso 1. Mostar la informacin del Producto Antes de crear la interfaz editable del DataList, primero necesitamos construir una interfaz de solo lectura. Comenzamos abriendo la pgina CustomizedUI.aspx de la carpeta EditDeleteDataList y desde el diseador, agregamos un DataList a la pgina, estableciendo su propiedad ID en Products. Desde la etiqueta inteligente del DataList, cree un nuevo ObjectDataSource. Nombre este nuevo ObjectDataSource ProductsDataSource y configrelo para que recupere los datos desde el mtodo GetProducts de la clase ProductsBLL. Al igual que en los tutoriales anteriores del DataList editable, actualizaremos la informacin del producto directamente en la capa lgica de negocios. Por lo tanto definimos las listas desplegables de las pestaas INSERT, UPDATE y DELETE en (Ninguno).

Figura 2. Establezca las listas desplegables de las pestaas INSERT, UPDATE y DELETE en (Ninguno) Despus de configurar el ObjectDataSource, Visual Studio creara un ItemTemplate por defecto para el DataList; que muestra el nombre y valor de cada uno de los campos devueltos. Modifique el ItemTemplate para que la plantilla muestre el nombre del producto en un elemento <h4> junto con el nombre de la categora, nombre del proveedor, precio y estado. Adems agregue un Button Edit, asegurndose de establecer su propiedad EditCommand en Edit. El marcado declarativo para mi ItemTemplate es el siguiente:
<ItemTemplate> <h4> <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>' /> </h4> <table border="0"> <tr> <td class="ProductPropertyLabel">Category:</td> <td class="ProductPropertyValue"> <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>' /> </td> <td class="ProductPropertyLabel">Supplier:</td> <td class="ProductPropertyValue"> <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>' /> </td> </tr> <tr> <td class="ProductPropertyLabel">Discontinued:</td> <td class="ProductPropertyValue"> <asp:Label ID="DiscontinuedLabel" runat="server" Text='<%# Eval("Discontinued") %>' /> </td> <td class="ProductPropertyLabel">Price:</td> <td class="ProductPropertyValue"> <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>' />

</td> </tr> <tr> <td colspan="4"> <asp:Button runat="Server" ID="EditButton" Text="Edit" CommandName="Edit" /> </td> </tr> </table> <br /> </ItemTemplate>

El marcado anterior establece la informacin del producto usando un encabezado <h4> para el nombre del producto y un <table> de cuatro columnas para los campos restantes. Las clases CSS ProductPropertyLabel y ProductPropertyValue son definidas en Styles.css, como hemos discutido en los tutoriales anteriores. La figura 3 muestra nuestro progreso cuando lo visualizamos por medio de un navegador.

Figura 3. Se muestra el nombre, proveedor, categora, estado y precio de cada producto Paso 2. Agregar los controles web a la interfaz de edicin El primer paso en la construccin de la interfaz de edicin personalizada del DataList es agregar los controles web necesarios al EditItemTemplate. En concreto, necesitamos un DropDownList para cada categora, otro para cada proveedor y un CheckBox para el

estado. Como el precio del producto no es editable en este ejemplo, podemos continuar mostrndolo usando un control web Label. Para personalizar la interfaz de edicin, presione el enlace Edit Templates en la etiqueta inteligente del DataList y seleccione la opcin EditItemTemplate en la lista desplegable. Agregue un DropDownList al EditItemTemplate y establezca su propiedad ID en Categories.

Figura 4. Agregue un DropDownList para las categoras Luego, desde la etiqueta inteligente del DropDownList, seleccione la opcin Escoger Origen de Datos y cree un nuevo ObjectDataSource llamado CategoriesDataSource. Configure este ObjectDataSource para que use el mtodo GetCategories() de la clase CategoriesBLL (Ver Figura 5). Luego el asistente de configuracin de origen de datos del DropDownList nos solicita los campos de datos a usar en las propiedades Text y Value del ListItem. Haga que el DropDownList muestre el campo de datos CategoryName y use el valor de CategoryID, como muestra la Figura 6.

Figura 5. Crear un nuevo ObjectDataSource llamado CategoriesDataSource

Figura 6. Configure los campos a mostrar y el valor del DropDownList Repita esta serie de pasos para crear un DropDownList para los proveedores. Establezca la propiedad ID de su DropDownList en Suppliers y nombre su ObjectDataSource SuppliersDataSource. Despus agregue dos DropDownLists, agregue un CheckBox para el estado y un TextBox para el nombre del producto. Establezca los IDs del CheckBox y TextBox en Discontinued y ProductName respectivamente. Agregue un RequiredFieldValidator para asegurarnos que el usuario proporcione un valor para el nombre del producto.

Por ltimo agregue los botones Cancel y Update. Recuerde que para estos dos botones es imperativo que sus propiedades CommandName se definan en Cancel y Update respectivamente. Sintase libre de definir la interfaz de edicin como guste. Optamos por usar el mismo diseo de <table> de cuatro columnas de la interfaz de solo lectura, esta es la sintaxis declarativa y como se ve en una captura de pantalla:
<EditItemTemplate> <h4> <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>' /> </h4> <table border="0"> <tr> <td class="ProductPropertyLabel">Name:</td> <td colspan="3" class="ProductPropertyValue"> <asp:TextBox runat="server" ID="ProductName" Width="90%" /> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" ControlToValidate="ProductName" ErrorMessage="You must enter a name for the product." runat="server">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td class="ProductPropertyLabel">Category:</td> <td class="ProductPropertyValue"> <asp:DropDownList ID="Categories" runat="server" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID" /> </td> <td class="ProductPropertyLabel">Supplier:</td> <td class="ProductPropertyValue"> <asp:DropDownList ID="Suppliers" DataSourceID="SuppliersDataSource" DataTextField="CompanyName" DataValueField="SupplierID" runat="server" /> </td> </tr>

<tr> <td class="ProductPropertyLabel">Discontinued:</td> <td class="ProductPropertyValue"> <asp:CheckBox runat="server" id="Discontinued" /> </td> <td class="ProductPropertyLabel">Price:</td> <td class="ProductPropertyValue"> <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>' /> </td> </tr> <tr> <td colspan="4"> <asp:Button runat="Server" ID="UpdateButton" CommandName="Update" Text="Update" /> <asp:Button runat="Server" ID="CancelButton" CommandName="Cancel" Text="Cancel" CausesValidation="False" /> </td> </tr> </table> <br /> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource> <asp:ObjectDataSource ID="SuppliersDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetSuppliers" TypeName="SuppliersBLL"> </asp:ObjectDataSource> </EditItemTemplate>

Figura 7. La interfaz de edicin est definida igual que la interfaz de solo lectura Paso 3. Crear los controladores de eventos EditCommand y CancelCommand Actualmente no hay una sintaxis de enlace de datos en el ItemTemplate (excepto para el UnitPrice Label, que fue copiada desde el ItemTemplate). Agregaremos la sintaxis de enlace de datos momentneamente, pero primero crearemos los controladores para los eventos EditCommand y CancelCommand del DataList. Recuerde que la responsabilidad del controlador de evento EditCommand es presentar la interfaz d edicin para el tem del DataList cuyo botn Edit fue presionado, mientras que el trabajo del CancelCommand es regresar el DataList a su estado previo a la edicin. Cree los dos controladores de eventos y luego use el siguiente cdigo:
Protected Sub Products_EditCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.EditCommand ' Set the DataList's EditItemIndex property to the ' index of the DataListItem that was clicked Products.EditItemIndex = e.Item.ItemIndex ' Rebind the data to the DataList Products.DataBind() End Sub Protected Sub Products_CancelCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.CancelCommand ' Set the DataList's EditItemIndex property to -1 Products.EditItemIndex = -1

' Rebind the data to the DataList Products.DataBind() End Sub

Con estos dos controladores de eventos en su lugar, presionando el botn Edit se muestra la interfaz de edicin y presionando el botn Cancel regresamos el tem editado a su modo de solo lectura. La figura 8 muestra el DataList luego que el botn Edit ha sido presionado para Gumbo Mix del Chef Anton. Como aun no hemos agregado ninguna sintaxis de enlace de datos a la interfaz de edicin, el TextBox ProductName est en blanco, el CheckBox Discontinued est sin seleccionar y esta seleccionado el primer tem de los DropDownList Categories y Suppliers.

Figura 8. Presionando el botn Edit se muestra la interfaz de edicin Paso 4. Agregar la sintaxis de enlace de datos a la interfaz de edicin Para hacer que la interfaz de edicin muestro los valores actuales del producto, necesitamos usar sintaxis de enlace de datos para asignar los valores de los campos de datos a los valores de los controles web apropiados. La sintaxis de enlace de datos puede ser aplicada por medio del diseador en la pantalla EditTemplates y seleccionando el enlace Editar enlace de datos desde la etiqueta inteligente de los controles web. De forma alternativa, la sintaxis de enlace de datos puede ser agregada directamente en el marcado declarativo. Asignamos el valor del campo de datos ProductName a la propiedad Text del TextBox ProductName, los valores de los campos de datos CategoryID y SupplierID a las propiedades SelectedValue de los DropDownLists Categories y Suppliers y el valor del campo de datos Discontinued a la propiedad Checked del CheckBox Discontinued.

Despus de realizar estos cambios, ya sea por medio del diseador o directamente a travs del marcado declarativo, visite nuevamente la pgina usando un navegador y presione el botn Edit del Gumbo Mix del Chef Anton. Como muestra la figura 9, la sintaxis de enlace de datos ha agregado los valores actuales dentro del TextBox, DropDownLists y CheckBox.

Figura 9. Presionando el botn Edit mostramos la interfaz de edicin Paso 5. Guardar los cambios del usuario en el controlador d evento UpdateCommand Cuando el usuario edita un producto y presiona el boton Update, ocurre una devolucin de datos y se genera el evento UpdateCommand del DataList. En el controlador de evento, necesitamos leer los valores de los controles web en el EditItemTemplate y comunicarlos con la BLL para actualizar el producto en la base de datos. Como vimos en los tutoriales anteriores, el ProductID del producto actualizado es accesible por medio de la coleccin DataKeys. Los campos que el usuario ha ingresado son accesibles por medio de programacin referenciando los controles web usando FindControl(controlID), como muestra el siguiente cdigo:
Protected Sub Products_UpdateCommand(source As Object, e As DataListCommandEventArgs) _ Handles Products.UpdateCommand If Not Page.IsValid Then Exit Sub End If ' Read in the ProductID from the DataKeys collection Dim productID As Integer = Convert.ToInt32(Products.DataKeys(e.Item.ItemIndex)) ' Read in the product name and price values Dim productName As TextBox = CType(e.Item.FindControl("ProductName"), TextBox) Dim categories As DropDownList=CType(e.Item.FindControl("Categories"), DropDownList)

Dim suppliers As DropDownList = CType(e.Item.FindControl("Suppliers"), DropDownList) Dim discontinued As CheckBox = CType(e.Item.FindControl("Discontinued"), CheckBox) Dim productNameValue As String = Nothing If productName.Text.Trim().Length > 0 Then productNameValue = productName.Text.Trim() End If Dim categoryIDValue As Integer = Convert.ToInt32(categories.SelectedValue) Dim supplierIDValue As Integer = Convert.ToInt32(suppliers.SelectedValue) Dim discontinuedValue As Boolean = discontinued.Checked ' Call the ProductsBLL's UpdateProduct method... Dim productsAPI As New ProductsBLL() productsAPI.UpdateProduct(productNameValue, categoryIDValue, supplierIDValue, _ discontinuedValue, productID) ' Revert the DataList back to its pre-editing state Products.EditItemIndex = -1 Products.DataBind() End Sub

El cdigo comienza consultando la propiedad Page.IsValid para asegurarse que todos los controles de validacin en la pgina son validos. Si Page.IsValid es True, el valor ProductID del producto editado es ledo desde la coleccin DataKeys y las entradas de los controles web en el EditItemTemplate son referenciadas mediante programacin. Luego los valores de los controles web son ledos en las variables que luego son pasadas a la recarga UpdateProduct apropiada. Despus de actualizar los datos, el DataList regresa a su estado previo a la edicin. Nota: He omitido la lgica de manejo de excepciones agregada en el tutorial Manejo de Excepciones a nivel de DAL y BLL con el fin de mantener el cdigo y este ejemplo enfocado. Como un ejercicio, agregue esta funcionalidad despus de completar este tutorial. Paso 6. Manejo de valores Null para CategoryID y SupplierID La base de datos Northwind permite valores Null para las columnas CategoryID y SupplierID de la tabla Products. Sin embargo, nuestra interfaz de edicin actualmente no permite valores Null. Si intenta editar un producto que tenga un valor Null para sus columnas CategoryID o SupplierID, tendr un ArgumentOutOfRangeException con un mensaje similar a: Categories has a SelectedValue which is invalid because it does not

exist in the list of tems. Tambin, actualmente no hay forma de cambiar el valor de categora o proveedor de un producto de un valor no-Null a un valor Null. Para que los DropDownList soporten valores nulos para la categora y el proveedor, necesitamos agregar un ListItem adicional. Hemos escogido la opcin (Ninguno) como el valor Text de nuestro ListItem, pero podemos cambiarlo a algo mas (como a una cadena vaca) si lo desea. Finalmente recuerde establecer el AppendDataBoundItems de los DropDownList en True, si olvida hacer esto, las categoras y proveedores enlazados a los DropDownList sobrescribirn los ListItem agregados estticamente. Despus de estos cambios, el marcado de los DropDownLists en el EditItemTemplate del DataList lucir similar al siguiente:
<asp:DropDownList ID="Categories" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID" runat="server" SelectedValue='<%# Eval("CategoryID") %>' AppendDataBoundItems="True"> <asp:ListItem Value=" Selected="True">(None)</asp:ListItem> </asp:DropDownList> ... <asp:DropDownList ID="Suppliers" DataSourceID="SuppliersDataSource" DataTextField="CompanyName" DataValueField="SupplierID" runat="server" SelectedValue='<%# Eval("SupplierID") %>' AppendDataBoundItems="True"> <asp:ListItem Value=" Selected="True">(None)</asp:ListItem> </asp:DropDownList>

Nota: Los ListItem estticos pueden ser agregados al DropDownList por medio del diseador o directamente por medio de la sintaxis declarativa. Cuando agregamos un tem al DropDownList para representar un valor Null de base de datos, es seguro agregar el ListItem por medio de la sintaxis declarativa. Si utiliza el Editor de coleccin ListItem en el diseador, la sintaxis declarativa generada omitir la definicin de Value cuando le asignamos una cadena en blanco, creando un marcado declarativo como <asp:ListItem> (None) </asp:ListItem>. Aunque esto no parece perjudicial, el Value perdido origina que el DropDownList use el valor de la propiedad Text en su lugar. Lo que significa que si este ListItem Null es seleccionado, el valor (Ninguno) intentara ser asignado al campo de datos del producto (categoryID o SupplierID, en este tutorial), lo cual genera una excepcin. Estableciendo explcitamente el Value=, un valor Null ser asignado al campo de datos del producto cuando el ListItem Null es seleccionado.

Tmese un momento para ver nuestro progreso por medio de un navegador. Cuando editamos un producto, note que los DropDownLists Categories y Suppliers tienen una opcin (Ninguno) al comienzo del DropDownList.

Figura 10. Los DropDownLists Categories y Suppliers incluyen una opcin (Ninguno) Para guardar la opcin (Ninguno) como un valor Null de base de datos, necesitamos regresar al controlador del evento UpdateCommand. Cambie las variables CategoryIDValue y SupplierIDValue a enteros nulables y asgneles un valor que no sea Nothing solamente si el SelectedValue del DropDownList no es una cadena vaca:
Dim categoryIDValue As Nullable(Of Integer) = Nothing If Not String.IsNullOrEmpty(categories.SelectedValue) Then categoryIDValue = Convert.ToInt32(categories.SelectedValue) End If Dim supplierIDValue As Nullable(Of Integer) = Nothing If Not String.IsNullOrEmpty(suppliers.SelectedValue) Then supplierIDValue = Convert.ToInt32(suppliers.SelectedValue) End If

Con este cambio, un valor de Nothing ser pasado al mtodo UpdateProduct de la BLL si el usuario selecciona la opcin (Ninguno) en cualquiera de las listas de los DropDownLists, que corresponde a un valor de base de datos Null. Resumen En este tutorial vimos como crear una interfaz de edicin en el DataList ms compleja, que incluyera tres controles web de entradas diferentes un TextBox, dos DropDownLists y un CheckBox junto con los controles de validacin. Cuando

construimos la interfaz de edicin, los pasos son los mismos independientemente del control web que se est usando: comenzamos agregando los controles web al EditItemTemplate del DataList, usamos la sintaxis de enlace de datos para asignar los valores de los campos de datos correspondientes con las propiedades del control web apropiado y en el controlador del evento UpdateCommand, accedemos mediante programacin a los controles web y a sus propiedades, pasando sus valores a la BLL. Cuando creamos una interfaz de edicin, si est compuesta de solo TextBoxes o de una coleccin de controles web diferentes, debemos estar seguros de manejar los valores Null de la base de datos. Cuando contamos con Nulls, es imperativo que no solamente mostremos correctamente un valor Null existente en la interfaz de edicin, tambin debemos ofrecer un medio para marcar un valor como Null. Para los DropDownList en el DataList, esto normalmente significa agregar un ListItem esttico cuya propiedad Value es definida explcitamente en una cadena vaca (Value=) y agregar un poco de cdigo al controlador de evento UpdateCommand para determinar si el ListItem Null fue seleccionado.

PAGINACION Y ORDENACION CON EL DATALIST Y REPEATER

41. PAGINAR UN REPORTE DE DATOS CON UN CONTROL DATALIST Y REPEATER


Aunque el DataList y el Repeater no ofrecen soporte automtico de paginacin u ordenamiento, este tutorial muestra como agregar un soporte de paginacin al DataList y Repeater que permita interfaces de paginacin y visualizacin de datos mucho ms flexible. Introduccin La paginacin y la clasificacin son dos caractersticas muy comunes cuando mostramos datos en una aplicacin en lnea. Por ejemplo, cuando buscamos libros de ASP.Net en una librera en lnea, hay cientos de libros como estos, pero el reporte muestra una lista de los resultados de la bsqueda mostrando solo diez resultados por pgina. Por otra parte los resultados pueden ser ordenados por ttulo, precio, nmero de pginas, autor y as sucesivamente. Como discutimos en el tutorial de Paginacin y Ordenamiento de Informes de datos, los controles GridView, DetailsView y FormView proporcionan un soporte predefinido de paginacin, que puede ser habilitado seleccionando una casilla de verificacin. El GridView tambin incluye soporte de ordenamiento. Desafortunadamente el DataList y el Repeater no ofrecen soporte de paginacin y ordenamiento automtico. En este tutorial examinaremos como agregar soporte de paginacin al DataList o Repeater. Crearemos manualmente la interfaz de paginacin, mostrando el nmero apropiado de registros y recordando la pgina visitada a travs de devoluciones de datos. Aunque esto toma ms tiempo y cdigo que con los controles GridView, FormView y DetailsView, el DataList y Repeater nos permiten una interfaz de paginacin y visualizacin de datos mucho ms flexible. Nota: Este tutorial se enfoca exclusivamente en la paginacin. En el siguiente tutorial enfocaremos nuestra atencin en agregar capacidades de ordenamiento. Paso 1. Agregar las pginas web de paginacin y ordenamiento Antes de iniciar este tutorial, primero nos tomaremos un momento para agregar las pginas ASP.Net necesarias para este y el siguiente tutorial. Comenzamos creando una nueva carpeta en el proyecto llamado PagingSortingDataListRepeater. Luego agregue

las siguientes cinco pginas ASP.Net a esta carpeta, configurndolas para que utilicen la pgina maestra Site.master: Default.aspx Paging.aspx Sorting.aspx SortingWithDefaultPaging.aspx SortingWithCustomPaging.aspx

Figura 1. Crear una carpeta PagingSortingDataListRepeater y agregar las paginas tutoriales ASP.Net Luego abra la pgina Default.aspx y arrastre el control de usuario

SectionLevelTutorialListing.ascx desde la carpeta UserControls hasta la superficie de diseo. Este control de usuario, el cual creamos en el tutorial Pginas Maestras y Navegacin del sitio, enumera el mapa del sitio y muestra los tutoriales de la seccin actual en una lista con vietas.

Figura 2. Agregar el control de usuario SectionLevelTutorialListing.ascx a Default.aspx Con el fin que la lista con vietas muestre los tutoriales de paginacin y ordenamiento que se van a crear, tenemos que agregarlos al mapa del sitio. Abra el archivo Web.sitemap y agregue el siguiente marcado despus del nodo de Edicin y Eliminacin de datos en el mapa del sitio:
<siteMapNode url="~/PagingSortingDataListRepeater/Default.aspx" title="Paging and Sorting with the DataList and Repeater" description="Paging and Sorting the Data in the DataList and Repeater Controls"> <siteMapNode url="~/PagingSortingDataListRepeater/Paging.aspx" title="Paging" description="Learn how to page through the data shown in the DataList and Repeater controls." /> <siteMapNode url="~/PagingSortingDataListRepeater/Sorting.aspx" title="Sorting" description="Sort the data displayed in a DataList or Repeater control." /> <siteMapNode url="~/PagingSortingDataListRepeater/SortingWithDefaultPaging.aspx" title="Sorting with Default Paging" description="Create a DataList or Repeater control that is paged using default paging and can be sorted." />

<siteMapNode url="~/PagingSortingDataListRepeater/SortingWithCustomPaging.aspx" title="Sorting with Custom Paging" description="Learn how to sort the data displayed in a DataList or Repeater control that uses custom paging." /> </siteMapNode>

Figura 3. Actualizar el mapa del sitio para incluir las nuevas pginas ASP.Net Un resumen de paginacin En tutoriales anteriores vimos como paginar los datos en los controles GridView, FormView y DetailsView. Estos tres controles ofrecen una forma sencilla de paginacin llamada paginacin por defecto que puede ser implementada seleccionando solamente la opcin Habilitar Paginacin en la etiqueta inteligente del control. Con la paginacin por defecto, cada vez que una pgina de datos es solicitada ya sea en la primera visita a la pgina o cuando el usuario navega a una pgina de datos diferente, el GridView, DetailsView y FormView manda todas las solicitudes de los datos desde el ObjectDataSource. A continuacin recorta el conjunto de registros particulares para mostrar el ndice de la pgina solicitada y mostrar el nmero de registros a mostrar por pgina. Discutimos con detalle la paginacin por defecto en el tutorial Paginacin y Ordenamiento de datos. Como la paginacin por defecto solicita todos los registros para cada pgina, esto no es prctico cuando paginamos a travs de cantidades de datos muy grandes. Por

ejemplo, imaginmonos paginando a travs de 50,000 registros con una pgina de tamao 10. Cada vez que el usuario se mueva a una nueva pgina, todos los 50.000 registros deben ser recuperados de la base de datos, aunque solamente 10 de ellos sern mostrados. La paginacin personalizada soluciona los problemas de rendimiento de la paginacin por defecto grabando solamente el subconjunto de registros preciso que se mostrara en la pagina solicitada. Cuando implementamos paginacin personalizada, debemos escribir la consulta SQL que devolver eficientemente solo el conjunto de registros adecuado. Vimos como crear una consulta utilizando la palabra clave ROW_NUMBER() de SQL Server 2005 en el tutorial Paginacin Eficiente a travs de grandes cantidades de datos. Para implementar la paginacin por defecto en los controles DataList y Repeater, podemos usar la clase PagedDataSource como un contenedor alrededor del ProductsDataTable cuyos contenidos estn siendo llamados. La clase PagedDataSource tiene una propiedad DataSource que puede ser asignada a cualquier objeto enumerable y las propiedades PageSize y CurrentPageIndex que indican cuantos registros mostrar por pagina y el ndice de la pagina actual. Una vez que se han definido estas propiedades, la PagedDataSource puede utilizarse como el origen de datos de cualquier control web de datos. La PagedDataSource cuando se enumera, solo devolvera el subconjunto de registros apropiado de su DataSource interno, basandose en las propiedades PageSize y CurrentPageIndex. La figura 4 muestra la funcionalidad de la clase PagedDataSource.

Figura 4. El PagedDataSource envuelve un objeto enumerable con una interfaz paginable El objeto PagedDataSource puede ser creado y configurado directamente desde la capa lgica de negocios y enlazado al DataList o Repeater mediante un ObjectDataSource, o puede ser creado y configurado directamente en la clase de cdigo subyacente de las paginas ASP.Net. Si se emplea el ltimo enfoque, debemos olvidarnos de usar el ObjectDataSource y en su lugar enlazar los datos paginados en el DataList o Repeater mediante programacin. El objeto PagedDataSource tambin tiene propiedades para soportar la paginacin personalizada. Sin embargo podemos pasar por alto usando un PagedDataSource para paginacin personalizada ya que tenemos mtodos BLL en la clase ProductsBLL diseados para la paginacin personalizada que devuelva los registros precisos para mostrar. En este tutorial veremos la implementacin de la paginacin por defecto en un DataList agregando un nuevo mtodo a la clase ProductsBLL que devuelva un objeto PagedDataSource configurado apropiadamente. En el siguiente tutorial veremos cmo usar la paginacin personalizada. Paso 2. Agregar un mtodo de paginacin por defecto en la capa lgica de negocios La clase ProductsBLL actualmente tiene un mtodo para devolver toda la informacin de los productos GetProducts() y uno para devolver un conjunto de productos en particular

con un ndice de inicio GetProductsPaged(startRowIndex, maximumRows). Con la paginacin por defecto, los controles GridView, DetailsView y FormView usan el mtodo GetProducts() para recuperar todos los productos, pero luego usa internamente un PagedDataSource para mostrar solamente el conjunto de registros apropiados. Para replicar esta funcionalidad con los controles DataList y Repeater, podemos crear un nuevo mtodo en la BLL que imite este comportamiento. Agregamos un mtodo a la clase ProductsBLL llamado GetProductsAsPagedDataSource que tome dos parmetros de entrada de tipo entero: pageIndex: ndice de la pagina para mostrar y pageSize: el nmero de registros a mostrar por pgina. inicia recuperando todos los registros de

GetProductsAsPagedDataSource

GetProducts(). Luego crea un objeto PagedDataSource, estableciendo sus propiedades CurrentPageSize y PageSize a los valores pasados en los parmetros pageIndex y pageSize. El mtodo concluye devolviendo este PagedDataSource configurado:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsAsPagedDataSource(ByVal pageIndex As Integer, _ ByVal pageSize As Integer) As PagedDataSource ' Get ALL of the products Dim products As Northwind.ProductsDataTable = GetProducts() ' Limit the results through a PagedDataSource Dim pagedData As New PagedDataSource() pagedData.DataSource = products.Rows pagedData.AllowPaging = True pagedData.CurrentPageIndex = pageIndex pagedData.PageSize = pageSize Return pagedData End Function

Paso 3. Mostrar la informacin de los productos en un DataList usando paginacin por defecto Con el mtodo GetProductsAsPagedDataSource agregado a la clase ProductsBLL, podemos crear un DataList o Repeater que proporcione paginacin por defecto. Comenzamos abriendo la pgina Paging.aspx en la carpeta

PagingSortingDataListRepeater y arrastramos un DataList desde la caja de herramientas hasta el diseador, estableciendo la propiedad ID del DataList en ProductsDefaultpaging. Desde la etiqueta inteligente del DataList, cree un nuevo ObjectDataSource llamado ProductsDefaultPagingDataSource y configrelo para que recupere los datos usando el mtodo GetProductsAsPagedDataSource.

Figura 5. Crear un ObjectDataSource y configurarlo para que use el mtodo GetProductsAsPagedDataSource() Establezca las listas desplegables de las pestaas INSERT, UPDATE y DELETE en (Ninguno)

Figura 6. Establezca las listas desplegables de las pestaas INSERT, UPDATE y DELETE en (Ninguno) Como el mtodo GetProductsAsPagedDataSource espera dos parmetros de entrada, el asistente nos solicitara el origen de los valores de estos parmetros. Los valores pageindex y pageSize deben ser recordados a travs de las devoluciones de datos. Pueden almacenarse en el ViewState, conservarlos en la cadena de consulta, almacenarlos en las variables de sesin o recordados con alguna otra tcnica. Para este tutorial usaremos la cadena de consulta que tiene la ventaja de permitir que una pgina de datos sea marcada. En particular utilice las cadenas de consulta de pageIndex y pageSize para los parmetros pageIndex y pageSize respectivamente (Ver Figura 7). Tmese un momento para establecer los valores predeterminados de estos parametros, ya que los valores de las cadenas de consulta no sern presentados cuando el usuario visite por primera vez la pgina. Para pageIndex establezca el valor predeterminado de 0 (lo cual mostrara la primera pgina) y para pageSize establezca 4 como valor predeterminado.

Figura 7. Usar la cadena de consulta como fuente para los parmetros pageIndex y pageSize

Despus de configurar el ObjectDataSource, Visual Studio crea automticamente un ItemTemplate para el DataList. Personalice el ItemTemplate de modo que solo se muestre el nombre del producto, categora y proveedor. Tambin establezca la propiedad RepeatColumns del DataList en 2, su Width en 100% y el Width del ItemStyle en 50%. Estas configuraciones de ancho brindan un espacio igual para las dos columnas. Despus de realizar estos cambios, el marcado del ObjectDataSource y del DataList debe ser similar al siguiente:
<asp:DataList ID="ProductsDefaultPaging" runat="server" Width="100%" DataKeyField="ProductID" DataSourceID="ProductsDefaultPagingDataSource" RepeatColumns="2" EnableViewState="False"> <ItemTemplate> <h4><asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>'></asp:Label></h4> Category: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label><br /> Supplier: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label><br /> <br /> <br /> </ItemTemplate> <ItemStyle Width="50%" /> </asp:DataList> <asp:ObjectDataSource ID="ProductsDefaultPagingDataSource" runat="server" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProductsAsPagedDataSource"> <SelectParameters> <asp:QueryStringParameter DefaultValue="0" Name="pageIndex" QueryStringField="pageIndex" Type="Int32" /> <asp:QueryStringParameter DefaultValue="4" Name="pageSize" QueryStringField="pageSize" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Nota: Como no estamos aplicando ninguna funcionalidad de actualizacin o eliminacin en este tutorial, podemos deshabilitar el ViewState del control DataList para reducir el tamao del la pagina presentada. Cuando procedamos a visitar esta pgina a travs de un navegador, no son proporcionadas las cadenas de consulta de los parmetros pageIndex y pageSize, por lo tanto se utilizaran los valores por defecto de 0 y 4. Como muestra la figura 8, esto origina un DataList que muestra los cuatro primeros productos.

Figura 8. Se muestran los cuatro primeros productos Sin una interfaz de paginacin, actualmente no hay ningn instrumento para que el usuario pueda navegar a la segunda pgina de datos. Crearemos una interfaz de paginacin en el paso 4. Sin embargo por ahora la localizacin solo puede llevarse a cabo especificando los criterios de localizacin directamente en la cadena de consulta. Por ejemplo para ver la segunda pgina cambie en la Url de la barra de direcciones del navegador de Paging.aspx a Paging.aspx?pageIndex=2 y presione ingresar. Esto hace que se muestre la segunda pgina de datos.

Figura 9. Se muestra la segunda pgina de datos Paso 4. Creacin de la interfaz de paginacin Hay una variedad diferente de interfaces de paginacin que pueden implementarse. Los controles GridView, DetailsView y FormView proporcionan cuatro interfaces diferentes para elegir entre: Next, Previous los usuarios pueden moverse una pgina a la vez, ya sea el siguiente o el anterior Next, Previous, First, Last adems de los botones Next y Previous, esta interface incluye los botones First y Last para pasar de la primera a la ltima pgina. Numeric muestra los nmeros de pgina en la interfaz de paginacin, permitiendo al usuario saltar a una pgina determinada. Numeric, First, Last, adems de los nmeros de pagina, incluye botones para pasar de la primera a la ultima pagina. Para el DataList y Repeater, somos responsables de decidir sobre una interfaz de paginacin y su implementacin. Esto implica la creacin de los controles web necesarios en la pgina y la visualizacin de la pgina solicitada cuando un botn determinado de la interfaz de paginacin sea seleccionado. Adems algunos controles de la interfaz de paginacin necesitan ser deshabilitados. Por ejemplo, cuando visualizamos la primera pgina de datos usando la interfaz Next, Previous, First y Last, los botones First y Previous deben deshabilitarse.

Para este tutorial usaremos la interfaz Next, Previous, First, Last. Agregamos cuatro controles web Button a la pgina y establecemos sus ID en FirstPage, PrevPage, NextPage y LastPage. Establezca las propiedades Text en <<First, <Prev, Next> y Last>>.
<asp:Button runat="server" ID="FirstPage" Text="<< First" /> <asp:Button runat="server" ID="PrevPage" Text="< Prev" /> <asp:Button runat="server" ID="NextPage" Text="Next >" /> <asp:Button runat="server" ID="LastPage" Text="Last >>" />

Luego, creamos un controlador del evento click para cada uno de estos botones, por lo que agregaremos el cdigo necesario para mostrar la pagina solicitada. Recordar el nmero total de registros que estn siendo paginados Independientemente de la interfaz de paginacin seleccionada, necesitamos calcular y recordar el nmero total de registros que estan siendo paginados. El nmero total de filas() determina el nmero total de pginas de datos que estn siendo paginadas, lo cual determina que controles de paginacin son agregados o deshabilitados. En la interface Next, Previous, First, Last que estamos construyendo. El nmero de pginas es usado de dos formas: Para determinar si estamos viendo la ultima pgina, en cuyo caso los botones Next y Last se deshabilitan. Si el usuario hace clic en el botn Last necesitamos detenernos en la ltima pgina, cuyo ndice es uno menos que el nmero de pginas El nmero de pginas es calculado como el nmero total de filas dividido por el tamao de la pgina. Por ejemplo si estamos paginando a travs de 79 registros con cuatro registros por fila, entonces el numero de pginas es de 20 ( el resultado de 79/4). Si est usando la interfaz de paginacin Numeric, esta informacin nos informa cuantos botones de pgina numeric mostraremos, si nuestra interfaz de paginacin incluye botones Next y Last, el numero de pginas es usado para determinar cundo deshabilitar los botones Next y Last. Si la interfaz de paginacin incluye un botn Last, es imperativo que el nmero total de registros que estn paginados sean recordados a travs de las devoluciones para que

cuando el botn Last sea seleccionado podamos determinar el ndice de la ltima pgina. Para facilitar esto, creamos la propiedad TotalRowCount en la clase de cdigo subyacente de la pgina ASP.Net, que persista su valor al view state:
Private Property TotalRowCount() As Integer Get Dim o As Object = ViewState("TotalRowCount") If (o Is Nothing) Then Return -1 Else Return Convert.ToInt32(o) End If End Get set(Value as Integer) ViewState("TotalRowCount") = value End Set End Property

Adems de TotalRowCount, tmese un minuto para crear propiedades a nivel de pgina de solo lectura para acceder fcilmente al ndice de pgina, tamao de pgina y nmero de pginas:
Private ReadOnly Property PageIndex() As Integer Get If (Not String.IsNullOrEmpty(Request.QueryString("pageIndex"))) Then Return Convert.ToInt32(Request.QueryString("pageIndex")) Else Return 0 End If End GetEnd PropertyPrivate ReadOnly Property PageSize() As Integer Get If (Not String.IsNullOrEmpty(Request.QueryString("pageSize"))) Then Return Convert.ToInt32(Request.QueryString("pageSize")) Else Return 4 End If End Get End Property Private ReadOnly Property PageCount() As Integer

Get If TotalRowCount <= 0 OrElse PageSize <= 0 Then Return 1 Else Return ((TotalRowCount + PageSize) - 1) / PageSize End If End Get End Property

Determinar el nmero total de registros que estn siendo paginados El objeto PagedDataSource regresado desde el mtodo Select() del ObjectDataSource tiene en su interior todos los registros de los productos, a pesar que solo un subconjunto de ellos ser mostrado en el DataList. La propiedad Count del PagedDataSource devuelve solamente el nmero de elementos que sern mostrados en el DataList, la propiedad DataSourceCount devuelve el nmero total de tems dentro del PagedDataSource. Por lo tanto, necesitamos asignar la propiedad TotalRowCount de la pgina ASP.Net al valor de la propiedad DataSourceCount del PagedDataSource. Para realizar esto, creamos un controlador de eventos para el evento Selected del ObjectDataSource. En el controlador del evento Selected accedemos al valor devuelto por el mtodo Select() del ObjectDataSource, en este caso el del PagedDataSource.
Protected Sub ProductsDefaultPagingDataSource_Selected(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs) _ Handles ProductsDefaultPagingDataSource.Selected ' Reference the PagedDataSource bound to the DataList Dim pagedData As PagedDataSource = CType(e.ReturnValue, PagedDataSource) ' Remember the total number of records being paged through across postbacks TotalRowCount = pagedData.DataSourceCount End Sub

Visualizacin de la pgina de datos solicitada Cuando el usuario hace clic en uno de los botones en la interfaz de paginacin, necesitamos mostrar la pgina de datos seleccionada. Como los parmetros de paginacin son especificados por cadenas de consulta, para mostrar la pgina de datos solicitada usamos Response.Redirect(url) para hacer que el navegador solicite nuevamente la pgina Paging.aspx con los parmetros de paginacin apropiados. Por

ejemplo para mostrar la segunda pgina de datos, redirigimos al usuario a Paging.aspx?pageIndex=1 . Para facilitar esto, creamos un mtodo RedirectUser (sendUserToPageIndex) que redirija al usuario a Paging.aspx?pageIndex= sendUserToPageIndex . Luego llame este mtodo desde los controladores de evento de los cuatro botones. En el controlador de evento click de FirstPage, llame a RedirectUser(0) para enviarlos a la primera pagina, en el controlador de evento clic de PrevPage, use PageIndex -1 como el page index y as sucesivamente.
Protected Sub FirstPage_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles FirstPage.Click ' Send the user to the first page RedirectUser(0)End SubProtected Sub PrevPage_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles PrevPage.Click ' Send the user to the previous page RedirectUser(PageIndex - 1) End Sub Protected Sub NextPage_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles NextPage.Click ' Send the user to the next page RedirectUser(PageIndex + 1) End Sub Protected Sub LastPage_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles LastPage.Click ' Send the user to the last page RedirectUser(PageCount - 1)End SubPrivate Sub RedirectUser(ByVal sendUserToPageIndex As Integer) ' Send the user to the requested page Response.Redirect(String.Format("Paging.aspx?pageIndex={0}&pageSize={1}", _ sendUserToPageIndex, PageSize)) End Sub

Con los controladores de eventos Click completos, los registros del DataList pueden ser paginados haciendo clic en los botones. Tmese un momento para probarlo! Deshabilitar los controles de la interfaz de paginacion

Actualmente todos los cuatro botones estn habilitados independientemente de la pgina que estemos viendo. Sin embargo deseamos deshabilitar los botones First y Previous cuando mostremos la primera pgina de datos y los botones Next y Last cuando mostremos la ltima pgina. El objeto PagedDataSource devuelto por el mtodo Select() del ObjectDataSource tiene propiedades IsFirstPage y IsLastPage que pueden examinarse y determinar si estamos visualizando la primera o ultima pgina de datos. Agregue el siguiente controlador para el evento Selected del ObjectDataSource:
' Configure the paging interface based on the data in the PagedDataSource FirstPage.Enabled = Not pagedData.IsFirstPage PrevPage.Enabled = Not pagedData.IsFirstPage NextPage.Enabled = Not pagedData.IsLastPage LastPage.Enabled = Not pagedData.IsLastPage

Con esta adicin, los botones First y Previous sern deshabilitados cuando visualicemos la primera pgina, mientras que los botones Next y Last sern deshabilitados cuando visualicemos la ltima pgina. Complete la interfaz de paginacin informando al usuario que pagina est viendo actualmente y el total de pginas que existen. Agregue un control web Label a la pgina y establezca su propiedad ID a CurrentPageNumber. Establezca su propiedad Text en el controlador del evento Selected del ObjecDataSource de tal forma que incluya la pgina que est siendo vista actualmente (PageIndex+1) y el total de nmero de pginas (PageCount)
' Display the current page being viewed... CurrentPageNumber.Text = String.Format("You are viewing page {0} of {1}...", _ PageIndex + 1, PageCount)

La figura 10 muestra Paging.aspx cuando la visitamos por primera vez. Como las cadenas de consulta estn vacas, el DataList por defecto muestra los primeros cuatro productos, los botones First y Previous estn deshabilitados. Dando clic en Next se muestran los siguientes cuatro registros (Ver Figura 11), ahora los botones First y Previous estn habilitados.

Figura 10. Se muestra la primera pgina de datos

Figura 11. Se muestra la segunda pgina de datos Nota: La interfaz de paginacin puede ser mejorada, permitiendo al usuario especificar el nmero de items para ver por pgina. Por ejemplo podramos agregar un DropDowList con las opciones 5, 10, 25, 50 y todos. Al seleccionar el tamao de la

pgina el lector.

el

usuario

ser

redirigido

Paging.aspx?pageIndex=0&pageSize=

selectedPageSize. Dejaremos la implementacin de esta mejora como un ejercicio para

Utilizacin de la paginacion personalizada El DataList pagina sus datos usando la ineficiente tcnica de paginacin por defecto. Cuando paginamos grandes cantidades de datos, es imperativo que se utilice la paginacin personalizada. Aunque los detalles de implementacin difieren ligeramente, los conceptos detrs de la implementacin de la paginacin personalizada en un DataList es igual que con la paginacin por defecto. Con la paginacin personalizada, utilice el metodo GetProductsPaged (en lugar de GetProductsAsPagedDataSource). Como discutimos en el tutorial de Paginacion Eficiente de grandes cantidades de datos, GetProductsPaged debe pasar el ndice de fila inicial y el nmero mximo de filas a devolver. Estos parmetros deben ser mantenidos por medio de una cadena de consulta al igual que los parmetros pageIndex y pageSize utilizados en la paginacin por defecto. Como no hay PagedDataSource con la paginacin personalizada, las tcnicas alternativas deben ser usadas para determinar el nmero de registros que han sido paginados y si estamos mostrando la primera o ultima pagina de datos. El mtodo TotalNumberOfProducts() en la clase ProductsBLL devuelve el nmero total de registros que estn siendo paginados. Para determinar si la primera pgina est siendo vista, examinamos el ndice de la fila de inicio si es cero entonces la primera pgina est siendo visualizada. La ultima pgina est siendo visualizada si el ndice de la fila de inicio mas el mximo de filas devuelve un nmero mayor o igual al nmero total de registros que estn siendo paginados. Exploraremos la implementacin de la paginacin personalizada con mayor detalle en el siguiente tutorial. Resumen Aunque el DataList y el Repeater no ofrecen el soporte de paginacin predeterminado encontrado en los controles GridView, DetailsView y FormView, se puede agregar con un mnimo esfuerzo. La forma ms fcil de implementar una paginacin por defecto es envolver un conjunto de datos completo dentro de un PagedDataSource y luego enlazar el PagedDataSource al DataList o Repeater. En este tutorial agregamos el mtodo

GetProductsAsPagedDataSource

la

clase

ProductsBLL

para

devolver

el

PagedDataSource. La clase ProductsBLL ya contiene los mtodos necesarios para la paginacin personalizada GetProductsPaged y TotalNumberOfProducts. Junto con la recuperacin precisa del conjunto de datos a mostrar para la paginacin personalizada o todos los registros en un PagedDataSource para paginacin por defecto, tambin necesitamos agregar manualmente la interfaz de paginacin. Para este tutorial creamos una interfaz Next, Previous, First, Last con cuatro controles web Button. Adems de un control label que muestra el numero de la pgina actual y el total de numero de pginas agregadas. En el prximo tutorial veremos cmo agregar soporte de ordenamiento al DataList y Repeater. Tambin veremos cmo crear un DataList que puede ser paginado y ordenado (con ejemplos usando paginacin por defecto y personalizada).

42. ORDENAR DATOS EN UN CONTROL DATALIST Y REPEATER


En este tutorial examinaremos la forma de incluir un soporte para el ordenamiento en el DataList y Repeater, as como la forma de construir un DataList o Repeater, cuyos datos se pueden paginar y ordenar. Introduccin En el tutorial anterior examinamos como agregar el soporte de paginacin a un control DataList. Creamos un nuevo mtodo (GetProductsAsPagedDataSource) en la clase ProductsBLL que devuelve un objeto PagedDataSource. Cuando lo enlazamos un DataList o Repeater, el DataList o Repeater solo muestra la pagina de datos solicitada. Esta tcnica es similar a la usada internamente por los controles GridView, DetailsView y FormView para proporcionar su funcionalidad de soporte de paginacin predeterminado. Adems de ofrecer soporte de paginacin, el GridView tambin incluye el soporte de ordenamiento. Ni el DataList ni el Repeater ofrecen funcionalidad de ordenamiento predeterminada, sin embargo las funciones de ordenamiento puede ser agregadas con un poco de cdigo. En este tutorial examinaremos como incluir soporte de ordenamiento en el DataList y Repeater, as como la forma de construir un DataList o Repeater cuyos datos puedan ser paginados y ordenados. Una revisin al ordenamiento Como vimos en el tutorial Paginacin y Ordenamiento de Informes de datos, el control GridView proporciona un soporte de ordenamiento. Cada campo en el GridView puede tener asociado una SortExpression, la cual indica los campos de datos con los cuales se ordenan los datos. Cuando la propiedad AllowSorting del GridView est establecida en True, cada campo del GridView tiene un valor en la propiedad SortExpression y su encabezado se presenta como un LinkButton. Cuando un usuario hace clic en el encabezado de un campo del GridView en particular, se genera una devolucin de datos y los datos son ordenados de acuerdo al SortExpression del campo seleccionado. El control GridView tiene una propiedad SortExpression, que almacena el

SortExpression del campo del GridView por el cual se ordenan los datos. Adems una propiedad SortDirection indica si los datos son ordenados de forma ascendente o

descendente (si un usuario hace clic dos veces en el encabezado de un campo del GridView en particular, el sentido de ordenamiento cambia). Cuando el GridView es enlazado a su control de origen de datos, este entrega sus propiedades SortExpression y SortDirection al control de origen de datos. El control de origen de datos recupera los datos y luego los ordena de acuerdo a las propiedades SortExpression y SortDirection suministradas. Despus de ordenar los datos, el control de origen de datos los devuelve al GridView. Para replicar esta funcionalidad con los controles DataList o Repeater, debemos: Crear una interfaz de ordenamiento Recordar el campo de datos a ordenar y si se debe ordenar de forma ascendente o descendente. Indicar al ObjectDataSource el orden de los datos para un campo de datos en particular. Despus de realizar estas tres tareas, en los pasos 3 y 4, examinaremos como incluir el soporte de paginacin y ordenamiento en el DataList o Repeater. Paso 2. Mostrar los productos en un Repeater Antes de preocuparnos en la implementacin de cualquier funcionalidad de ordenamiento, comenzaremos mostrando los productos en un control Repeater. Comenzamos abriendo la pgina Sorting.aspx de la carpeta PagingSortingDataListRepeater. Agregue un control Repeater a la pgina web, estableciendo su propiedad ID en SortableProducts. Desde la etiqueta inteligente del Repeater, cree un nuevo ObjectDataSource llamado ProductsDataSource y configrelo para que recupere sus datos desde el mtodo GetProducts() de la clase ProductsBLL. Seleccione la opcin Ninguno en las listas desplegables de las pestaas INSERT, UPDATE y DELETE.

Figura 1. Crear un ObjectDataSource y configurarlo para que use el mtodo GetProductsAsPagedDataSource()

Figura 2. Establezca las listas desplegables de las pestaas UPDATE, INSERT y DELETE en Ninguno A diferencia del DataList, Visual Studio no crea automticamente un ItemTemplate para el Repeater luego que se enlaza a su origen de datos. Por lo tanto debemos agregar este ItemTemplate de forma declarativa, ya que la etiqueta inteligente del control Repeater carece de la opcin Editar templates encontrada en los DataLists. Usaremos el

mismo ItemTemplate del tutorial anterior, el cual mostraba el nombre, proveedor y categora del producto. Despus de agregar el ItemTemplate, el marcado declarativo del Repeater y ObjectDataSource debe lucir similar al siguiente:
<asp:Repeater ID="SortableProducts" DataSourceID="ProductsDataSource" EnableViewState="False" runat="server"> <ItemTemplate> <h4><asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>'></asp:Label></h4> Category: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label><br /> Supplier: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label><br /> <br /> <br /> </ItemTemplate> </asp:Repeater><asp:ObjectDataSource ID="ProductsDataSource" runat="server" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProducts"> </asp:ObjectDataSource>

La figura 3 muestra la pgina cuando la visualizamos desde un navegador.

Figura 3. Se muestra el nombre, proveedor y categora de cada producto Paso 3. Indicar al ObjectDataSource el orden de los datos Para ordenar los datos mostrados en un Repeater, necesitamos informar al ObjectDataSource la expresin de ordenamiento con la cual los datos sern ordenados. Antes de recuperar los datos, el ObjectDataSource primero genera su evento Selecting, el cual nos proporciona una oportunidad para especificar una expresin de ordenamiento. de tipo El controlador del evento La Selecting clase pasa un objeto de tipo est ObjectDataSourceSelectingEventArgs, el cual tiene una propiedad llamada Arguments DataSourceSelectArguments. DataSourceSelectArguments diseada para pasar las solicitudes relacionados con datos desde un consumidor de datos al control de origen de datos, e incluye una propiedad SortExpression. Para pasar la informacin de ordenamiento desde la pgina ASP.Net al

ObjectDataSource, creamos un controlador de evento para el evento Selecting y usamos el siguiente cdigo:
Protected Sub ProductsDataSource_Selecting(ByVal sender As Object, _ ByVal e As ObjectDataSourceSelectingEventArgs) _ Handles ProductsDataSource.Selecting e.Arguments.SortExpression = sortExpression

End Sub

El valor sortExpression debe ser asignado al nombre del campo de datos para ordenar los datos (como ProductName). No hay ninguna propiedad relacionada con la direccin de ordenamiento, as que si desea ordenar los datos en orden descendente, agregue la cadena DESC al valor de sortExpression (como ProductName DESC) Nos adelantaremos y definiremos diferentes valores no modificables para

sortExpression y probaremos los resultados en un navegador. Como muestra la figura 4, cuando usamos ProductName DESC como sortExpression, los productos son ordenados por su nombre en sentido contrario al alfabtico.

Figura 4. Los productos son ordenados por nombre en orden contrario al alfabtico Paso 4. Creacin de la Interfaz de Ordenamiento y recordar la expresin y direccin de ordenamiento Habilitando el soporte de ordenamiento en el GridView convierte cada texto del encabezado del campo ordenable en un LinkButton que cuando es presionado, ordena los datos acordemente. Esta interfaz de ordenamiento tiene sentido para el GridView donde sus datos son cuidadosamente acomodados en columnas. Sin embargo para los controles DataList y Repeater es necesaria una interfaz de ordenamiento diferente. Una

interfaz de ordenamiento comn para una lista de datos (a diferencia de una red de datos) es una lista desplegable que proporciona los campos con los cuales los datos pueden ser ordenados. Vamos a implementar tal interfaz para este tutorial. Agregue un control web DropDownList encima del Repeater SortableProducts y establezca su propiedad ID en SortBy. Desde la ventana propiedades, haga clic en los puntos suspensivos en la propiedad tems para abrir el editor de la coleccin ListItem. Agregue los ListItems para ordenar los datos por los campos ProductName, CategoryName y SupplierName. Adems agregue un ListItem para ordenar los productos por su nombre en orden contrario al alfabeto. Las propiedades Text de los ListItem puede establecerse en cualquier valor (como Name), pero las propiedades Value deben ser definidas con el nombre del campo de datos (como ProductName). Para ordenar los resultados en orden descendente, agregue la cadena DESC al nombre del campo de datos, como ProductName DESC.

Figura 5. Agregue un ListItem para cada uno de los campos de datos ordenables Finalmente agregue un control web Button a la derecha del DropDownList. Establezca su ID a RefreshRepeater y su propiedad Text a Refresh. Despus de crear el ListItem y agregar el botn Refresh, la sintaxis declarativa del DropDownList y Buttons lucir similar al siguiente:

<asp:DropDownList ID="SortBy" runat="server"> <asp:ListItem Value="ProductName">Name</asp:ListItem> <asp:ListItem Value="ProductName DESC">Name (Reverse Order) </asp:ListItem> <asp:ListItem Value="CategoryName">Category</asp:ListItem> <asp:ListItem Value="SupplierName">Supplier</asp:ListItem> </asp:DropDownList> <asp:Button runat="server" ID="RefreshRepeater" Text="Refresh" />

Con el DropDownList de ordenamiento completo, luego necesitamos actualizar el controlador de evento Selecting del ObjectDataSource para que use la propiedad Value del ListItem SortBy en lugar de una expresin de ordenamiento codificada fuertemente.
Protected Sub ProductsDataSource_Selecting _ (ByVal sender As Object, ByVal e As ObjectDataSourceSelectingEventArgs) _ Handles ProductsDataSource.Selecting ' Have the ObjectDataSource sort the results by the ' selected sort expression e.Arguments.SortExpression = SortBy.SelectedValue End Sub

Hasta este momento cuando visitamos por primera vez la pgina los productos inicialmente son ordenados por el campo de datos ProductName, ya que es el ListItem Sort By seleccionado por defecto (ver figura 6). Seleccionando una opcin de ordenamiento diferente como Category y dando clic en refresh generamos una devolucin de datos y volvemos a ordenar los datos por el nombre de la categora, como se ve la figura 7.

Figura 6. Los productos inicialmente son ordenados por su nombre Nota: Al dar clic en el botn Refresh hacemos que los datos sean ordenados nuevamente de forma automtica porque el View State del Repeater ha sido deshabilitado, lo que origina que el Repeater sea enlazado nuevamente a su origen de datos en cada devolucin de datos. Si ha dejado el View State del Repeater habilitado, cambiar la lista desplegable de ordenamiento no tendr ningn efecto en el orden de ordenamiento. Para remediar esto, creamos un controlador de evento para el evento Click del button Refresh y enlazamos nuevamente el Repeater a su fuente de datos (llamando al mtodo DataBind() del Repeater).

Figura 7. Ahora los productos son ordenados por Categora Recordar la expresin y direccin de ordenamiento Cuando creamos un DataList o Repeater ordenable en una pgina donde pueden ocurrir devoluciones de datos no relacionadas con el ordenamiento, es necesario que la expresin y direccin de ordenamiento sea recordado a travs de las devoluciones de datos. Por ejemplo, imagine que en este tutorial actualizamos el Repeater para que incluya un botn Delete con cada producto. Cuando el usuario hace clic en el botn Delete, ejecutamos algn cdigo para borrar el producto seleccionado y luego volvemos a enlazar los datos al Repeater. Si los detalles de ordenamiento no persisten a travs de las devoluciones de datos, los datos mostrados en la pantalla volvern a su orden original. Para este tutorial, el DropDownList guarda implcitamente por nosotros la expresin de ordenamiento y la direccin en su ViewState. Si se utiliza una interfaz de ordenamiento diferente, digamos que los LinkButtons proporcionen varias opciones de ordenamiento, necesitamos tener cuidado para recordar el orden de ordenamiento a travs de las devoluciones de datos. Esto podra realizarse ordenando los parmetros de ordenamiento en el ViewState de la pgina, para incluir el parmetro de ordenamiento en la cadena de consulta o por medio de otra tcnica de persistencia del estado.

En ejemplos futuros de este tutorial exploraremos como persistir los detalles de ordenamiento en el ViewState de la pgina. Paso 5. Agregar un soporte de ordenamiento al DataList para que use paginacin predeterminada En el tutorial anterior examinamos como implementar la paginacin predeterminada con un DataList. Ampliaremos el ejemplo anterior para incluir la capacidad para ordenar los datos paginados. y Comenzamos Paging.aspx abriendo en las la pginas carpeta SortingWithDefaultPaging.aspx

PagingSortingDataListRepeater. Desde la pgina Paging.aspx, haga clic en el botn Fuente para ver el marcado declarativo de la pgina. Copie el texto seleccionado (Ver Figura 8) y pguelo en el marcado declarativo de SortingWithDefaultPaging.aspx entre las etiquetas <asp:Content>.

Figura 8. Duplique el marcado declarativo en las etiquetas <asp:Content> desde Paging.aspx a SortingWithDefaultPaging.aspx Despus de copiar el marcado declarativo, copie los mtodos y propiedades en la clase de cdigo subyacente de la pgina Paging.aspx a la clase de cdigo subyacente de SortingWithDefaultPaging.aspx. Luego tmese un momento para ver la pgina

SortingWithdefaultPaging.aspx en un navegador. Esto exhibira la misma funcionalidad y apariencia de Paging.aspx. Mejorar el ProductsBLL para incluir un mtodo de ordenamiento y paginacin predeterminado En el tutorial anterior creamos un mtodo GetProductsAsPagedDataSource (pageIndex, pageSize) en la clase ProductsBLL que devuelve un objeto PagedDataSource. Este objeto PagedDataSource fue poblado con todos los productos (a travs del mtodo GetProducts() de la BLL) pero cuando se enlaza al DataList solo se muestran aquellos registros que corresponden a los parmetros de entrada pageIndex y pageSize especificados. Anteriormente es este tutorial agregamos soporte de ordenamiento para especificar la expresin de ordenamiento en el controlador de eventos Selecting del ObjectDataSource. Esto funciona bien cuando el ObjectDataSource devuelve un objeto que puede ser ordenado, como el ProductsDataTable devuelto por el mtodo GetProducts(). Sin embargo el objeto PagedDataSource devuelto por el mtodo GetProductsAsPagedDataSource no soporta el ordenamiento de su origen de datos interno. En su lugar, necesitamos ordenar los resultados devueltos desde el mtodo GetProducts() antes de ponerlo en el PagedDataSource. Para realizar esto, creamos un nuevo mtodo en la clase ProductsBLL, Para

GetProductsSortedAsPagedDataSource(sortExpression, propiedad Sort de su DataTableView por defecto.


<System.ComponentModel.DataObjectMethodAttribute _

pageIndex,

pageSize).

ordenar el ProductsDataTable devuelto por el mtodo GetProducts(), especificamos la

(System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsSortedAsPagedDataSource _ (sortExpression As String, pageIndex As Integer, pageSize As Integer) _ As PagedDataSource ' Get ALL of the products Dim products As Northwind.ProductsDataTable = GetProducts() 'Sort the products products.DefaultView.Sort = sortExpression ' Limit the results through a PagedDataSource Dim pagedData As New PagedDataSource()

pagedData.DataSource = products.DefaultView pagedData.AllowPaging = True pagedData.CurrentPageIndex = pageIndex pagedData.PageSize = pageSize Return pagedData End Function

El mtodo GetProductsSortedAsPagedDataSource difiere ligeramente del mtodo GetProductsAsPagedDataSource creado en el tutorial anterior. En particular, GetProductsSortedAsPagedDataSource acepta un parmetro de entrada sortExpression adicional y asigna este valor a la propiedad Sort del DefaultView del ProductDataTable. Despus de unas pocas lneas de cdigo, el DataSource del objeto PagedDataSource es asignado al DefaultView del ProductDataTable. Llamar al mtodo GetProductsSortedAsPagedDataSource y especifique el valor para el parmetro de entrada SortExpression Con el mtodo GetProductsSortedAsPagedDataSource completo, el siguiente paso es proporcionar el valor para este parmetro. El ObjectDataSource en SortingWithDefaultPaging.aspx est configurado actualmente para llamar al mtodo GetProductsAsPagedDataSource y pasar los dos parmetros de entrada a travs de sus dos QueryStringParameters, que son especificadas en la coleccin SelectParameters. Estas dos QueryStringParameters indican que la fuente para los parmetros pageIndex y pageSize del mtodo GetProductsAsPagedDataSource viene de los campos de las cadenas de consulta de pageIndex y pageSize. Actualice la propiedad SelectMethod del ObjectDataSource para que invoque el nuevo mtodo GetProductsSortedAsPagedDataSource. Luego agregue un nuevo QueryStringParameter para que el parmetro de entrada sortExpression sea accesible desde el campo de la cadena de consulta sortExpression. Establezca el DefaultValue del QueryStringParameter a ProductName. Despus de estos cambios, el marcado declarativo del ObjectDataSource debe lucir como:
<asp:ObjectDataSource ID="ProductsDefaultPagingDataSource" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProductsSortedAsPagedDataSource"

OnSelected="ProductsDefaultPagingDataSource_Selected" runat="server"> <SelectParameters> <asp:QueryStringParameter DefaultValue="ProductName" Name="sortExpression" QueryStringField="sortExpression" Type="String" /> <asp:QueryStringParameter DefaultValue="0" Name="pageIndex" QueryStringField="pageIndex" Type="Int32" /> <asp:QueryStringParameter DefaultValue="4" Name="pageSize" QueryStringField="pageSize" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Hasta el momento, la pgina SortingWithDefaultPaging.aspx ordenara sus resultados alfabticamente por el nombre de producto (Ver Figura 9). Esto se debe a que por defecto, el valor de ProductName es pasado como parmetro sortExpression del mtodo GetProductsSortedAsPagedDataSource.

Figura 9. Los resultados son ordenados por ProductName por defecto Si agregamos manualmente un campo de cadena de consulta sortExpression como SortingWithDefaultPaging.aspx?sortExpression=CategoryName ordenados por la sortExpression especificada. Sin los resultados este sern embargo parmetro

sortExpression no est incluido en la cadena de consulta cuando nos movemos a una

pgina de datos diferente. De hecho al hacer clic en los botones de pgina Next o Last regresamos a Paging.aspx. Adems no hay interfaz de ordenamiento. La nica forma que un usuario pueda cambiar el orden de una pgina paginada es manipulando la cadena de consulta directamente. Crear la interfaz de ordenamiento Primero necesitamos actualizar el mtodo RedirectUser para enviar al usuario a SortingWithDefaultPaging.aspx (en lugar de Paging.aspx) e incluir el valor sortExpression en la cadena de consulta. Tambin debemos agregar una propiedad de solo lectura a nivel de pgina llamada SortExpression. Esta propiedad es similar a las propiedades PageIndex y PageSize creadas en el tutorial anterior para que devuelva el valor del campo de cadena de consulta sortExpression si existe o en caso contrario el valor por defecto (ProductName). Actualmente el mtodo RedirectUser acepta solo un parmetro de entrada, el ndice de la pgina para mostrar. Sin embargo hay ocasiones en que deseamos redirigir al usuario a una pgina de datos en particular usando una expresin de ordenamiento en lugar de la que es especificada en la cadena de consulta. En un momento crearemos la interfaz de ordenamiento para esta pgina, la cual incluir una serie de controles web Button para ordenar los datos por una columna especificada. Cuando uno de estos botones es presionado, deseamos redirigir al usuario pasando el valor de la expresin de ordenamiento apropiada. Para proporcionar esta funcionalidad creamos dos versiones del mtodo RedirectUser. El primero debe aceptar solo el ndice de la pgina a mostrar, mientras que el segundo acepta el ndice de pgina y la expresin de ordenamiento.
Private ReadOnly Property SortExpression() As String Get If Not String.IsNullOrEmpty(Request.QueryString("sortExpression")) Then Return Request.QueryString("sortExpression") Else Return "ProductName" End If End GetEnd PropertyPrivate Sub RedirectUser(ByVal sendUserToPageIndex As Integer) ' Use the SortExpression property to get the sort expression ' from the querystring RedirectUser(sendUserToPageIndex, SortExpression)

End Sub Private Sub RedirectUser(ByVal sendUserToPageIndex As Integer, ByVal sendUserSortingBy As String) ' Send the user to the requested page with the ' requested sort expression Response.Redirect(String.Format("SortingWithDefaultPaging.aspx?" & _ "pageIndex={0}&pageSize={1}&sortExpression={2}", _ sendUserToPageIndex, PageSize, sendUserSortingBy)) End Sub

En el primer ejemplo de este tutorial, creamos una interfaz de ordenamiento usando un DropDownList. Para este ejemplo usaremos tres controles web Button ubicado encima del DataList, uno para ordenar por ProductName, otro para ordenar por CategoryName y otro para ordenar por SupplierName. Agregue los tres controles web Button, definiendo sus propiedades ID y Text apropiadamente:
<p> <asp:Button runat="server" id="SortByProductName" Text="Sort by Product Name" /> <asp:Button runat="server" id="SortByCategoryName" Text="Sort by Category" /> <asp:Button runat="server" id="SortBySupplierName" Text="Sort by Supplier" /> </p>

Luego cree un controlador de evento Click para cada uno. Estos controladores de evento deben llamar al mtodo RedirectUser devolviendo al usuario a la primera pgina y usando la expresin de ordenamiento adecuada.
Protected Sub SortByProductName_Click(sender As Object, e As EventArgs) _ Handles SortByProductName.Click 'Sort by ProductName RedirectUser(0, "ProductName") End Sub Protected Sub SortByCategoryName_Click(sender As Object, e As EventArgs) _ Handles SortByCategoryName.Click 'Sort by CategoryName RedirectUser(0, "CategoryName") End Sub

Protected Sub SortBySupplierName_Click(sender As Object, e As EventArgs) _ Handles SortBySupplierName.Click 'Sort by SupplierName RedirectUser(0, "SupplierName") End Sub

Cuando

visitamos

la

pgina

por

primera

vez,

los

datos

estn

ordenados

alfabticamente por el nombre del product (Refirase de nuevo a la figura 9). Haga clic en el botn Next para avanzar a la segunda pgina de datos y luego presione el botn Sort By Category. Esto nos devuelve a la primera pgina de datos, ordenada por el nombre de la categora (Ver Figura 10). Del mismo modo, presionando el botn Sort By Supplier ordenamos los datos por proveedor comenzando desde la primera pgina de datos. La opcin de ordenamiento es recordada a travs de cmo los datos son paginados. La figura 11 muestra la pgina despus de ordenarla por categora y luego avanzando a la decimo tercera pgina de datos.

Figura 10. Los productos son ordenados por Categora

Figura 11. La expresin de ordenamiento es recordada a travs de la paginacin de datos Paso 6. Paginacin personalizada a travs de los registros en un Repeater El ejemplo del DataList mostrado en el paso 5, este pagina sus datos usando una tcnica de paginacin ineficiente. Cuando se paginan grandes cantidades de datos, es imperativo usar paginacin personalizada. Regresando a los tutoriales Paginacin Eficiente de grandes cantidades de datos y Ordenamiento personalizado de datos paginados, examinamos las diferencias entre la paginacin por defecto y la personalizada y creamos mtodos en la BLL para utilizar paginacin personalizada y ordenamiento personalizado de datos paginados. En particular, en estos dos tutoriales anteriores, agregamos los siguientes tres mtodos a la clase ProductsBLL: GetProductsPaged(startRowIndex, maximumRows) devuelve un conjunto de registros en particular comenzando por startRowIndex y no excediendo maximumRows. GetProductsPagedAndSorted(sortExpression, entrada sortExpression especificado. TotalNumberOfproducts() proporciona el nmero total de registros en la tabla Products de la base de datos. startRowIndex, maximumRows) devuelve un conjunto de registros en particular ordenados por el parmetro de

Estos mtodos pueden ser usados para paginar y ordenar datos usando un control Repeater o DataList de forma eficiente. Para ilustrar esto, comenzamos creando un control Repeater con un soporte de paginacin personalizado, luego agregaremos las capacidades de ordenamiento. Abra la pgina SortingWithCustomPaging.aspx de la carpeta

PagingSortingDataListRepeater y agregue un Repeater a la pgina, estableciendo su propiedad ID en Products. Desde la etiqueta inteligente del Repeater, cree un nuevo ObjectDataSource llamado ProductsDataSource. Configrelo para que seleccione sus datos desde el mtodo GetProductsPaged de la clase ProductsBLL.

Figura 12. Configure el ObjectDataSource para que use el mtodo GetProductsPaged de la clase ProductsBLL Establezca las listas desplegables de las pestaas UPDATE, INSERT y DELETE en Ninguno y luego haga clic al botn Next, El asistente de configuracin de origen de datos nos solicita las fuentes para los parmetros de entrada startRowIndex y maximumRows del mtodo GetProductsPaged. En la actualidad estos parmetros de entrada son ignorados. En su lugar los valores startRowIndex y maximumRows sern pasados a travs de la propiedad Arguments en el controlador de eventos Selecting del ObjectDataSource, igual como especificamos la sortExpression en el primer ejemplo de este tutorial. Por lo tanto dejaremos las listas desplegables de origen del parmetro en el asistente definidas como Ninguno.

Figura 13. Dejamos las fuentes de los parmetros establecidas en Ninguno Nota: No establezca la propiedad EnablePaging del ObjectDataSource a True. Esto har que el ObjectDataSource incluya automticamente sus propios parmetros startRowIndex y maximumRows a la lista de parmetros del SelectMethod existente. La propiedad EnablePaging es til cuando enlazamos los datos paginados personalizados a los controles GridView, DetailsView o FormView porque estos controles esperan cierto comportamiento desde el ObjectDataSource que solamente es vlido cuando la propiedad EnablePaging es True. Ya que tenemos que agregar manualmente el soporte de paginacin para el DataList y Repeater, dejaremos esta propiedad establecida en false (por defecto), ya que agregaremos la funcionalidad necesaria directamente en nuestra pgina ASP.Net. Finalmente defina el ItemTemplate del Repeater para que se muestren el nombre del producto, categora y proveedor. Despus de estos cambios, la sintaxis declarativa del Repeater y ObjectDataSource debe lucir similar al siguiente:
<asp:Repeater ID="Products" runat="server" DataSourceID="ProductsDataSource" EnableViewState="False"> <ItemTemplate> <h4><asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>'></asp:Label></h4>

Category: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label><br /> Supplier: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label><br /> <br /> <br /> </ItemTemplate> </asp:Repeater> <asp:ObjectDataSource ID="ProductsDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged" TypeName="ProductsBLL"> <SelectParameters> <asp:Parameter Name="startRowIndex" Type="Int32" /> <asp:Parameter Name="maximumRows" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Tmese un momento para visitar la pgina a travs de un navegador y note que ningn registro es devuelto. Esto es porque aun no hemos especificado los valores de los parmetros startRowIndex y maximumRows, por lo tanto los valores de 0 se pasan para ambos parmetros. Para especificar estos valores, creamos un controlador de evento para el evento Selecting del ObjectDataSource y establecemos los valores de estos parmetros mediante programacin a los valores no modificables de 0 y 5 respectivamente:
Protected Sub ProductsDataSource_Selecting(sender As Object, _ e As ObjectDataSourceSelectingEventArgs) _ Handles ProductsDataSource.Selecting e.InputParameters("startRowIndex") = 0 e.InputParameters("maximumRows") = 5 End Sub

Con este cambio, cuando visualizamos la pgina por medio de un navegador, mostramos los primeros cinco productos.

Figura 14. Se muestran los cinco primeros registros Nota: Los productos mostrados en la Figura 14 pasan a ser ordenados por nombre del producto porque el procedimiento almacenado GetProductsPaged que realiza la consulta de la paginacin personalizada ordena los resultados por ProductName. Con el fin de permitir al usuario pasar a travs de las pginas necesitamos mantener el ndice de inicio de filas y el mximo de filas y recordar estos valores a travs de las devoluciones de datos. En el ejemplo de paginacin predeterminada usamos campos de cadena de consulta para persistir estos valores, para este demo, persistiremos esta informacin en el ViewState de la pagina. Cree las siguientes dos propiedades:
Private Property StartRowIndex() As Integer Get Dim o As Object = ViewState("StartRowIndex") If o Is Nothing Then Return 0 Else Return CType(o, Integer)

End If End Get Set(ByVal value As Integer) ViewState("StartRowIndex") = value End Set End Property Private Property MaximumRows() As Integer Get Dim o As Object = ViewState("MaximumRows") If o Is Nothing Then Return 5 Else Return CType(o, Integer) End If End Get Set(ByVal value As Integer) ViewState("MaximumRows") = value End Set End Property

Luego, actualice el cdigo en el controlador de evento Selecting para que use las propiedades StarRowIndex y MaximumRows en lugar de los valores no modificables de 0 y 5.
e.InputParameters("startRowIndex") = 0 e.InputParameters("maximumRows") = 5

Hasta el momento nuestra pgina todava muestra solo los cinco primeros registros. Sin embargo, con estas propiedades en su lugar, estamos listos para crear nuestra interfaz de paginacin. Agregar la interfaz de paginacin Usaremos la misma interfaz de paginacin First, Previous, Next, Last usada en el ejemplo de paginacin por defecto, incluyendo un control web Label que muestra que pagina de datos esa siendo visualizada y cuantas pginas en total existen. Agregue los cuatro controles web Button y el Label debajo del Repeater.

<p> <asp:Button runat="server" ID="FirstPage" Text="<< First" /> <asp:Button runat="server" ID="PrevPage" Text="< Prev" /> <asp:Button runat="server" ID="NextPage" Text="Next >" /> <asp:Button runat="server" ID="LastPage" Text="Last >>" /> </p> <p> <asp:Label runat="server" ID="CurrentPageNumber"></asp:Label> </p>

Luego cree los controladores de evento Click para los cuatro Buttons. Cuando uno de estos botones es presionado, necesitamos actualizar el StartRowIndex y enlazar nuevamente los datos al Repeater. El cdigo para los botones First, Previous y Next es bastante sencillo, pero para el botn Last como determinamos el ndice de inicio de fila para la ltima pgina de datos? Para calcular este ndice debemos ser capaces de determinar si los botones Next y Last deben ser habilitados y necesitamos conocer cuntos registros en total estn siendo paginados. Podemos determinar esto llamando al mtodo TotalNumberOfProducts() de la clase ProductsBLL. Crearemos una propiedad de solo lectura a nivel de pgina llamada TotalRowCount que devuelva los resultados del mtodo TotalNumberOfProducts():
Private ReadOnly Property TotalRowCount() As Integer Get 'Return the value from the TotalNumberOfProducts() method Dim productsAPI As New ProductsBLL() Return productsAPI.TotalNumberOfProducts() End Get End Property

Con esta propiedad podemos determinar el ndice de inicio de la ltima pgina. Especficamente es el resultado entero del TotalRowCount menos 1 divido entre MaximumRows, multiplicado por MaximumRows. Ahora podemos escribir el controlador de eventos Click para los cuatro botones de la interfaz de paginacin:
Protected Sub FirstPage_Click(sender As Object, e As EventArgs) _ Handles FirstPage.Click 'Return to StartRowIndex of 0 and rebind data StartRowIndex = 0

Products.DataBind() End Sub Protected Sub PrevPage_Click(sender As Object, e As EventArgs) _ Handles PrevPage.Click 'Subtract MaximumRows from StartRowIndex and rebind data StartRowIndex -= MaximumRows Products.DataBind()End SubProtected Sub NextPage_Click(sender As Object, e As EventArgs) _ Handles NextPage.Click 'Add MaximumRows to StartRowIndex and rebind data StartRowIndex += MaximumRows Products.DataBind()End SubProtected Sub LastPage_Click(sender As Object, e As EventArgs) _ Handles LastPage.Click 'Set StartRowIndex = to last page's starting row index and rebind data StartRowIndex = ((TotalRowCount - 1) \ MaximumRows) * MaximumRows Products.DataBind() End Sub

Finalmente, necesitamos deshabilitar los botones First y Previous en la interfaz de paginacin cuando visualizamos la primera pgina de datos y los botones Next y Last cuando visualizamos la ltima pgina. Para realizar esto, agregamos el siguiente cdigo al controlador de evento Selecting del ObjectDataSource:
' Disable the paging interface buttons, if needed FirstPage.Enabled = StartRowIndex <> 0 PrevPage.Enabled = StartRowIndex <> 0 Dim LastPageStartRowIndex As Integer = _ ((TotalRowCount - 1) \ MaximumRows) * MaximumRows NextPage.Enabled = (StartRowIndex < LastPageStartRowIndex) LastPage.Enabled = (StartRowIndex < LastPageStartRowIndex)

Despus de agregar los controladores de evento Click y el cdigo para habilitar o deshabilitar los elementos de la interfaz de paginacin basados en el ndice de inicio de la fila actual, probaremos la pgina en un navegador. Como muestra la figura 15, cuando visitamos por primera vez la pgina, los botones First y Previous estn desactivados. Haciendo clic en Next mostramos la segunda pgina de datos, mientras que dando clic en Last mostramos la pgina final (Ver figuras 16 y 17). Cuando visualizamos la ltima pgina de datos, los botones Next y Last son deshabilitados.

Figura 15. Los botones Previous y Last son deshabilitados cuando visualizamos la primera pgina de productos

Figura 16. Se muestra la segunda pgina de productos

Figura 17. Al hacer clic se muestra la ltima pgina de datos

Paso 7. Incluir Soporte de Ordenamiento con el Repeater de paginado personalizado Ahora que la paginacin personalizada ha sido implementada, estamos listos para incluir el soporte de ordenamiento. El mtodo GetProductsPagedAndSorted de la clase ProductsBLL tiene los mismos parmetros de entrada startRowIndex y maximumRows que GetProductsPaged, pero permite un parmetro de entrada sortExpression adicional. Para usar el mtodo GetProductsPagedAndSorted desde SortingWithCustomPaging.aspx, necesitamos realizar los siguientes pasos: 1. Cambie la propiedad SelectMethod del ObjectDataSource de GetProductsPaged a GetProductsPagedAndSorted. 2. Agregue un objeto Parameter sortExpression a la coleccin SelectParameters del ObjectDataSource. 3. Cree una propiedad privada SortExpression a nivel de pgina que persista su valor a travs de las devoluciones de datos por medio del ViewState de la pgina. 4. Actualice el controlador de evento Selecting del ObjectDataSource para asignar al Parameter sortExpression del ObjectDataSource el valor de la propiedad SortExpression a nivel de pagina. 5. Crear la interfaz de ordenamiento. Comencemos por actualizar la propiedad SelectMethod del ObjectDataSource y agregar un Parameter sortExpression. Asegrese que la propiedad Type del Parameter sortExpression este establecida en String. Despus de completar estas dos primeras tareas, el marcado declarativo del ObjectDataSource lucir como el siguiente:
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProductsPagedAndSorted" OnSelecting="ProductsDataSource_Selecting"> <SelectParameters> <asp:Parameter Name="sortExpression" Type="String" /> <asp:Parameter Name="startRowIndex" Type="Int32" /> <asp:Parameter Name="maximumRows" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Luego necesitamos agregar una propiedad SortExpression a nivel de pgina cuyo valor es serializado al View State. Si no se establece un valor para la expresin de ordenamiento, se usa por defecto ProductName:
Private Property SortExpression() As String Get Dim o As Object = ViewState("SortExpression") If o Is Nothing Then Return "ProductName" Else Return o.ToString() End If End Get Set(ByVal value As String) ViewState("SortExpression") = value End Set End Property

Antes que el ObjectDataSource invoque el mtodo GetProductsPagedAndSorted necesitamos establecer el Parameter sortExpression al valor de la propiedad SortExpression. En el controlador de eventos Selecting, agregue la siguiente lnea de cdigo:
e.InputParameters("sortExpression") = SortExpression

Todo lo que resta es implementar la interfaz de ordenamiento. Como hicimos en el ejemplo anterior, tenemos que implementar la interfaz de ordenamiento usando tres controles web Button que permitan al usuario ordenar los resultados por nombre del producto, categora o proveedor.
<asp:Button runat="server" id="SortByProductName" Text="Sort by Product Name" /> <asp:Button runat="server" id="SortByCategoryName" Text="Sort by Category" /> <asp:Button runat="server" id="SortBySupplierName" Text="Sort by Supplier" />

Cree un controlador para el evento Click de estos tres controles Button. En el controlador de evento, restablezca la propiedad StartRowIndex a 0, establezca la propiedad SortExpression al valor apropiado y enlace los datos al Repeater.
Protected Sub SortByProductName_Click(sender As Object, e As EventArgs) _ Handles SortByProductName.Click StartRowIndex = 0 SortExpression = "ProductName" Products.DataBind()End Sub Protected Sub SortByCategoryName_Click(sender As Object, e As EventArgs) _ Handles SortByCategoryName.Click StartRowIndex = 0 SortExpression = "CategoryName" Products.DataBind()End Sub Protected Sub SortBySupplierName_Click(sender As Object, e As EventArgs) _ Handles SortBySupplierName.Click StartRowIndex = 0 SortExpression = "CompanyName" Products.DataBind() End Sub

Esto es todo, Aunque hubo una serie de pasos para conseguir la paginacin personalizada y el ordenamiento implementado, los pasos fueron muy similares a los necesarios para la paginacin predeterminada. La figura 18 muestra los productos cuando visualizamos la ltima pgina de datos ordenados por categora. Nota: En los ejemplos anteriores cuando ordenamos los proveedores, el SupplierName fue usado como expresin de ordenamiento. Sin embargo para la implementacin de la paginacin personalizada, necesitamos usar CompanyName. Esto es debido a que el procedimiento almacenado GetProductsPagedAndSorted responsable de la implementacin de la paginacin personalizada pasa la expresin de ordenamiento dentro de la palabra clave ROW_NUMBER(). La palabra clave ROW_NUMBER() requiere el nombre de la columna actual en lugar de un alias. Por lo tanto debemos usar para la expresin de ordenamiento, CompanyName ( el nombre de la columna en la tabla Suppliers) en lugar del alias usado en la consulta SELECT (SupplierName).

Figura 18. Se muestra la ultima pagina de datos ordenada por Categora Resumen Ni el DataList ni el Repeater ofrecen soporte de ordenamiento predeterminado, pero con un poco de cdigo y una interfaz de ordenamiento personalizada, se puede agregar esta funcionalidad. Cuando implementamos ordenamiento, pero no paginacin, la expresin de orden puede ser especificada a travs del objeto DataSourceSelectArguments pasado dentro del mtodo Select del ObjectDataSource. Esta propiedad SortExpression del objeto DataSourceSelectArguments puede ser asignada en el controlador de evento Selecting del ObjectDataSource. Para agregar funcionalidades de ordenamiento al DataList o Repeater que ya proporciona soporte de paginacin, el enfoque ms fcil es personalizar la capa de lgica de negocios para que incluya un mtodo que acepte una expresin de ordenamiento. Esta informacin luego puede ser pasada a travs de un parmetro en el SelectParameters del ObjectDataSource. Con este tutorial completamos nuestro examen sobre la paginacin y ordenamiento con los controles DataList y Repeater. En nuestro siguiente y final tutorial examinamos como agregar controles web Button a las plantillas del DataList o Repeater con el fin de

proporcionar alguna funcionalidad personalizada iniciada por el usuario sobre un tem bsico.

PERSONALIZAR ACCIONES DE BOTON CON EL DATALIST Y REPEATER

43. PERSONALIZAR ACCIONES DE BOTON CON EL DATALIST Y REPEATER


En este tutorial construiremos una interfaz que use un Repeater para mostrar las categoras en el sistema, con cada categora proporcionamos un botn para mostrar los productos asociados usando un control BulletedList. Introduccin A travs de los ltimos 17 tutoriales del Repeater y DataList, creamos ejemplos de solo lectura y ejemplos de edicin y eliminacin. Para facilitar las capacidades de edicin y eliminacin dentro del DataList, agregamos botones al ItemTemplate del DataList que al ser presionados originaban una devolucin de datos y activaba un evento DataList correspondiente a la propiedad CommandName de los botones. Por ejemplo cuando agregamos un botn al ItemTemplate con el valor de la propiedad CommandName Edit hace que el EditCommand del DataList genere una devolucin de datos, uno con el CommandName Delete activa el DeleteCommand. Ademas de los botones Edit y Delete, los controles DataList y Repeater tambin pueden incluir Buttons, LinkButtons o ImageButtons que cuando son presionados realicen alguna lgica personalizada en el lado del servidor. En este tutorial construiremos una interfaz que utilice un Repeater para mostrar las categoras en el sistema. Para cada categora, el Repeater incluir un botn para mostrar los productos asociados a la categora usando un control BulletedList (Ver Figura 11).

Figura 1. Presionando el enlace Show Products mostramos los productos de la categora en un BulletedList Paso 1. Agregar las pginas web para el tutorial de personalizacin de botones Antes de ver como agregar un botn personalizado, primero nos tomaremos un momento para crear pginas ASP.Net en nuestro proyecto de sitio web que necesitaremos para este tutorial. Comenzamos agregando una nueva carpeta llamada CustomButtonsDataListRepeater. Luego agregue las siguientes dos pginas ASP.Net a la carpeta, asegurndose de asociar cada pgina con la pgina maestra Site.master: Default.aspx CustomButtons.aspx

Figura 2. Agregar las paginas ASP.Net para los tutoriales relacionados con la personalizacin de botones Como en las otras carpetas, Default.aspx en la carpeta CustomButtonsDataListRepeater muestra los tutoriales en esta seccin. Recuerde que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Agregue este control de usuario a Default.aspx arrastrndolo desde el explorador de soluciones a la vista diseo de la pgina.

Figura 3. Agregar el control de usuario SectionLevelTutorialListing.ascx a Default.apx Por ltimo agregue las pginas como entradas en el archivo Web.sitemap.

Especficamente agregue el siguiente marcado despus del <siteMapNode> Paginacin y Ordenamiento con el DataList y Repeater
<siteMapNode url="~/CustomButtonsDataListRepeater/Default.aspx" title="Adding Custom Buttons to the DataList and Repeater" description="Samples of DataList and Repeater Reports that Include Buttons for Performing Server-Side Actions"> <siteMapNode url="~/CustomButtonsDataListRepeater/CustomButtons.aspx" title="Using Custom Buttons in the DataList and Repeater's Templates" description="Examines how to add custom Buttons, LinkButtons, or ImageButtons within templates." /> </siteMapNode>

Despus de actualizar el Web.sitemap tmese un momento para ver los tutoriales del sitio web en un navegador. El men de la izquierda ahora incluye tems para el tutorial de personalizacin de botones.

Figura 4. El Mapa del Sitio ahora incluye la entrada para el Tutorial de personalizacin de botones

Paso 2. Agregar la lista de Categoras Para este tutorial necesitamos crear un Repeater que muestre todas las categoras junto con un LinkButton Show Products que al ser presionado, muestre los productos asociados con la categora en una lista de vietas. Primero crearemos un Repeater que muestre las categoras en el sistema. Comience abriendo la pgina CustomButtons.aspx en la carpeta CustomButtonsDataListRepeater. Arrastre un Repeater desde la caja de herramientas hasta el diseador y establezca su propiedad ID en Categories. Luego cree un nuevo control de origen de datos desde la etiqueta inteligente del Repeater. Especficamente creamos un nuevo control ObjectDataSource llamado CategoriesDataSource que seleccione sus datos desde el mtodo GetCategories() de la clase CategoriesBLL.

Figura 5. Configure el ObjectDataSource para que use el mtodo GetCategories() de la clase CategoriesBLL A diferencia del control DataList, para el cual Visual Studio crea un ItemTemplate por defecto basado en su origen de datos, las plantillas del Repeater deben definirse manualmente. Por lo tanto las plantillas del Repeater deben ser creadas y editadas mediante declaracin (ya que no hay una opcin Editar Templates en la etiqueta inteligente del Repeater). Haciendo clic en la pestaa Fuente en la esquina inferior izquierda y agregando un ItemTemplate que muestre los nombres de las categoras en un elemento <h3> y su descripcin en una etiqueta prrafo, incluimos tambin un SeparatorTemplate que muestre una lnea horizontal (<hr/>) entre cada categora. Adems agreguemos un

LinkButton con su propiedad Text definida como ShowProducts. Despus de completar estos pasos, el marcado declarativo de su pgina lucir similar al siguiente:
<asp:Repeater ID="Categories" DataSourceID="CategoriesDataSource" runat="server"> <ItemTemplate> <h3><%# Eval("CategoryName") %></h3> <p> <%# Eval("Description") %> [<asp:LinkButton runat="server" ID="ShowProducts"> Show Products</asp:LinkButton>] </p> </ItemTemplate> <SeparatorTemplate> <hr /> </SeparatorTemplate> </asp:Repeater> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource>

La figura 6 muestra la pgina cuando la visualizamos por medio de un navegador. Se muestra cada uno de los nombres de las categoras y su descripcin. El botn ShowProducts, cuando es presionado origina una devolucin de datos, pero aun no realiza ninguna accin.

Figura 6. Se muestra el nombre de cada categora y su descripcin, junto con un LinkButton ShowProducts Paso 3. Ejecutar lgica del lado del servidor cuando el LinkButton ShowProducts es presionado Cada vez que un Button, LinkButton o ImageButton dentro de una plantilla en el DataList o Repeater es presionado, se genera una devolucin de datos y se activa el evento ItemCommand del DataList o Repeater. Adicional al evento ItemCommand, el control DataList pueden generar otros eventos ms especficos, si la propiedad CommandName del Button est establecida en una de las cadenas reservadas (Delete, Edit, Cancel, Update o Select), pero el evento ItemCommand siempre se genera. Cuando un botn es presionado dentro de un DataList o Repeater, a menudo necesitamos pasar junto con el botn que fue presionado (en el caso que existan muchos botones en el control, como los botones Edit y Delete) alguna informacin adicional (como el valor de la llave primaria del tem cuyo botn fue presionado). El Button, LinkButton e ImageButton proporciona dos propiedades cuyos valores son pasados en el controlador de evento ItemCommand: CommandName una cadena que generalmente es usada para identificar cada botn en la plantilla.

CommandArgument comnmente usada para almacena el valor de algn campo de datos, como el valor de la llave primaria.

Para este ejemplo, establezca la propiedad CommandName del LinkButton a ShowProducts y enlace el valor de la llave primaria del registro actual CategoryID a la propiedad CommandArgument usando la sintaxis de enlace de datos CommandArgument='<%# Eval("CategoryID") %>. Despus de especificar estas dos propiedades, la sintaxis declarativa del LinkButton lucir similar a la siguiente:
<asp:LinkButton runat="server" CommandName="ShowProducts" CommandArgument='<%# Eval("CategoryID") %>' ID="ShowProducts"> Show Products</asp:LinkButton>

Cuando el botn es presionado, se genera una devolucin de datos y se activa el evento ItemCommand del DataList o Repeater. El controlador del evento pasa los valores CommandName y CommandArgument del botn. Cree un controlador para el evento ItemCommand del Repeater y note el segundo parmetro pasado en el controlador de evento (llamado e). Este segundo parmetro es de tipo RepeaterCommandEventArgs y tiene las siguientes cuatro propiedades: CommandArgument el valor de la propiedad CommandArgument del botn presionado CommandName el valor de la propiedad CommandName del botn CommandSource una referencia al control button que fue presionado tem una referencia al RepeaterItem que contiene el botn que fue presionado; cada registro enlazado al Repeater es presentado en el RepeaterItem Como el CategoryID de la categora seleccionada se pasa a travs de la propiedad CommandArgument, podemos obtener el conjunto de productos asociados con la categora seleccionada en el controlador de evento ItemCommand. Luego estos productos pueden ser enlazados a un control BulletedList en el ItemTemplate. Luego todo lo que resta es agregar el BulletedList, referenciarlo en el controlador de evento ItemCommand y enlazarlos al conjunto de productos para la categora seleccionada, todo esto lo haremos en el paso 4.

Nota:

El

controlador

de

evento

ItemCommand

pasa

un

objeto

de

tipo

DataListCommandEventArgs, que ofrece las mismas cuatro propiedades que la clase RepeaterCommandEventArgs. Paso 4. Mostrar los productos de la categora seleccionada en una lista con vietas Los productos de la categora seleccionada pueden ser mostrados en el ItemTemplate del Repeater usando cualquier nmero de controles. Podemos agregar otro Repeater anidado, un DataList, un DropDownList, un GridView y as sucesivamente. Como deseamos mostrar los productos en una lista con vietas, usaremos un control BulletedList. Regresando al marcado declarativo de la pgina CustomButtons.aspx, agregamos un control BulletedList al ItemTemplate luego del LinkButton ShowProducts. Establezca la propiedad ID del BulletedList en ProductsIn Category. El BulletedList muestra el valor de los campos de datos por medio de la propiedad DataTextField, como este control tendr la informacin enlazada a l, establezca la propiedad DataTextField en ProductName.
<asp:BulletedList ID="ProductsInCategory" DataTextField="ProductName" runat="server"></asp:BulletedList>

En el controlador de evento ItemCommand, haga referencia a este control usando e.Item.FindControl(ProductsInCategory) y enlcelo al conjunto de productos asociados con la categora seleccionada.
protected void Categories_ItemCommand(object source, RepeaterCommandEventArgs e) { if (e.CommandName == "ShowProducts") { // Determine the CategoryID int categoryID = Convert.ToInt32(e.CommandArgument); // Get the associated products from the ProudctsBLL and bind // them to the BulletedList BulletedList products = (BulletedList)e.Item.FindControl("ProductsInCategory"); ProductsBLL productsAPI = new ProductsBLL(); products.DataSource = productsAPI.GetProductsByCategoryID(categoryID); products.DataBind()); } }

Antes de realizar cualquier accin en el controlador d evento ItemCommand, es prudente primero verificar el valor del CommandName entrante. Como el controlador de evento ItemCommand se dispara cuando cualquier botn es presionado, si hay muchos botones en la plantilla use el valor CommandName para decidir qu accin tomar. Verificar aqu el CommandName es discutible, ya que solo tenemos un botn, pero es un buen hbito para formularios. Luego el CategoryID de la categora seleccionada es recuperado desde la propiedad CommandArgument. El control BulletedList en la plantilla luego es referenciado y enlazado a los resultados del mtodo GetProductsBycategoryID(categoryID) de la clase ProductsBLL. En los tutoriales anteriores que usaron botones con el DataList, como en Resumen de Edicin, Insercin y Eliminacin de Datos en el DataList, determinamos el valor de la llave primaria de un determinado tem por medio de la coleccin DataKeys. Aunque este enfoque trabaja bien con el DataList, el Repeater no tiene una propiedad DataKeys. En su lugar podemos usar un enfoque alternativo para proporcionar el valor de la llave primaria a travs de la propiedad CommandArgument del botn o asignando el valor de la llave primaria a un control web Label oculto dentro de la plantilla y leer este valor de nuevo en el controlador de evento ItemCommand usando e.Item.FinControl(LabelID). Despus de completar el controlador de evento ItemCommand, tmese un momento para probar esta pgina en un navegador. Como muestra la figura 7, dando clic en el enlace ShowProducts se origina una devolucin de datos y muestra los productos para la categora seleccionada en un BulletedList. Por otra parte, note que la informacin de este producto permanece, incluso si el enlace ShowProducts de otra categora es presionado. Nota: Si deseas modificar el comportamiento de este informe, como que solo se muestre un solo producto de la categora a la vez, sencillamente establezca la propiedad EnableViewState del control BulletedList en False.

Figura 7. Una lista de vietas es usada para mostrar los productos de la categora seleccionada Resumen Los controles DataList y Repeater pueden incluir cualquier nmero de Buttons, LinkButtons o ImageButtons dentro de sus plantillas. Como los botones al ser presionados, generan una devolucin de datos y activan el evento ItemCommand. Para asociar una accin personalizada de lado del servidor cuando un botn presionado, creamos un controlador de evento para el evento ItemCommand. En este controlador de evento primero verificamos el valor CommandName entrante para determinar que botn fue presionado. La informacin adicional puede ser suministrada a travs de la propiedad CommandArgument de los botones.

TRABAJAR CON ARCHIVOS BINARIOS

51. CARGA DE ARCHIVOS


Aprenderemos como permitir que los usuarios carguen archivos binarios (como documentos Word o PDF) a su sitio web, que podrn ser almacenados en cualquier sistema de archivos del servidor o en la base de datos. Introduccin Todos los tutoriales que hemos examinado hasta el momento han trabajado exclusivamente con datos de texto. Sin embargo muchas aplicaciones tienen modelos de datos que capturan texto y datos binarios. Un sitio web de citas en lnea podra permitir a los usuarios aadir una imagen asociada con su perfil. Un sitio web de reclutamiento podra permitir a los usuarios subir su curriculum vitae como un documento Microsoft Word o PDF. Trabajar con datos binarios agrega un nuevo conjunto de desafos. Nosotros debemos decidir cmo se almacenan los datos binarios en la aplicacin. La interfaz usada para agregar nuevos registros, debe ser actualizada para permitir que el usuario cargue un archivo desde su computadora, adems deben tomarse medidas adicionales para mostrarlos o proporcionar un medio para descargar datos binarios asociados con un registro. En este y en los prximos tres tutoriales exploraremos como vencer estos desafos. Al finalizar estos tutoriales habremos construido una aplicacin completamente funcional que asocie una figura y un catalogo PDF con cada categora. En este tutorial en particular veremos diferentes tcnicas para el almacenamiento de datos binarios y exploraremos la forma de permitir a los usuarios cargar un archivo desde su ordenador y guardarlo en un sistema de archivos del servidor. Nota: Los datos binarios que son parte de un modelo de datos de aplicacin algunas veces son conocidos como un BLOB, que es un acrnimo de Objeto Binario Grande. En estos tutoriales hemos optado por utilizar la terminologa de datos binarios, aunque el trmino es sinnimo de BLOB. Paso 1. Creacin de pginas web que trabajen con datos binarios Antes de comenzar a explorar los desafos asociados con la adicin del soporte para datos binarios, primero nos tomaremos un momento para crear las pginas Asp.Net que necesitaremos para este y los siguientes tres tutoriales, en nuestro proyecto de sitio web. Comencemos agregando una nueva carpeta llamada BinaryData. Luego

agregue las siguientes pginas Asp.Net a esa carpeta, asegurndose de asociar cada pgina con la pgina maestra Site.master: Default.aspx FileUpload.aspx DisplayOrDownloadData.aspx UploadInDetailsView.aspx UpdatingAndDeleting.aspx

Al igual que en las otras carpetas, la pgina Default.aspx de la carpeta BinaryData mostrar una lista de los tutoriales de la seccin. Recordemos que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Por lo tanto, agregamos este control de usuario a la pgina Default.aspx arrastrndolo desde el explorador de soluciones hasta la vista de diseo de la pgina.

Figura 1. Agregue las pginas Asp.Net relacionadas con los tutoriales de datos binarios

Figura 2. Agregue el control de usuario SectionLevelTutorialListing.ascx a Default.aspx Por ltimo agregue estas pginas como entradas del archivo Web.Sitemap. En concreto agregue el siguiente marcado despus del <siteMapNode> Mejoramiento del GridView:
<siteMapNode title="Paging and Sorting" url="/PagingAndSorting/Default.aspx" description="Samples of Reports that Provide Paging and Sorting Capabilities"> <siteMapNode url="/PagingAndSorting/SimplePagingSorting.aspx" title="Simple Paging & Sorting Examples" description="Examines how to add simple paging and sorting support." /> <siteMapNode url="/PagingAndSorting/EfficientPaging.aspx" title="Efficiently Paging Through Large Result Sets" description="Learn how to efficiently page through large result sets." /> <siteMapNode url="/PagingAndSorting/SortParameter.aspx" title="Sorting Data at the BLL or DAL" description="Illustrates how to perform sorting logic in the Business Logic Layer or Data Access Layer." /> <siteMapNode url="/PagingAndSorting/CustomSortingUI.aspx" title="Customizing the Sorting User Interface" description="Learn how to customize and improve the sorting user interface"/> </siteMapNode>

Despus de actualizar el Web.SiteMap, tmese un momento para ver el sitio web de tutoriales por medio de un navegador. El men de la izquierda ahora incluye elementos para los tutoriales del trabajo con los de datos binarios.

Figura 3. El mapa del sitio ahora incluye entradas para los tutoriales de trabajo con los datos binarios Paso 2. Decidir donde almacenar los datos binarios Los datos binarios que estn asociados con el modelo de datos de la aplicacin pueden ser almacenados en uno de dos lugares: en el sistema de archivos del servidor web con una referencia al archivo almacenado en la base de datos o directamente dentro de la misma base de datos (Ver Figura 4). Cada enfoque tiene sus propios conjuntos de pros y contras y meritos que discutiremos ms detalladamente.

Figura 4. Los datos binarios se pueden almacenar en un sistema de archivos o directamente en la base de datos Imaginemos que deseamos ampliar la base de datos Northwind para asociar una imagen con cada producto. Una opcin es almacenar estos archivos de imgenes en un sistema de archivos del servidor web y registrar su ruta en la tabla Products. Con este enfoque agregamos una columna ImagePath a la tabla Products de tipo varchar (200). Cuando un usuario cargue una imagen para Chai, la imagen podr ser almacenada en el sistema de archivos del servidor web en ~/Images/Tea.jpg, donde ~ representa la ruta fsica de la aplicacin. Esto es, si el sitio web est almacenado en la ruta fsica C:\WebSites\Northwind\, ~/Images/Tea.jpg sera equivalente a decir C:\WebSites\Northwind\Images\Tea.jpg. Despus de cargar el archivo de la imagen, actualizamos el registro Chai en la tabla Products, para que su columna ImagePath referencie la ruta de la nueva imagen. Podramos usar ~/Images/Tea.jpg o solamente Tea.jpg si decide que todas las imgenes deben ser colocadas en la carpeta Images de la aplicacin. Las principales ventajas de almacenar datos binarios en un sistema de archivos son:

Fcil Implementacin: como veremos prontamente, almacenar y recuperar datos


binarios almacenados directamente dentro de la base de datos involucra un poco ms de cdigo que cuando trabajamos con los datos por medio de un sistema de archivos. Adems con el fin que el usuario visualice o descargue datos binarios, se

debe presentar una Url para estos datos. Si los datos residen en el sistema de archivos del servidor web, la Url es ms sencilla. Sin embargo, si los datos estn almacenados en la base de datos, necesitamos crear una pgina web que recupere y devuelva los datos desde la base de datos.

Amplio acceso a los datos binarios: los datos binarios podran necesitar ser
accesibles por otros servicios o aplicaciones, algunos de los cuales no podran acceder a los datos a travs de una base de datos. Por ejemplo las imgenes asociadas a cada producto, podran necesitar ser accesibles por los usuarios a travs de FTP en cuyo caso es necesario almacenar los datos binarios en el sistema de archivos.

Rendimiento: Si los datos binarios se almacenan en el sistema de archivos, la


demanda y congestin de la red entre el servidor de base de datos y el servidor web ser menor que si los datos binarios se almacenan directamente en la base de datos.

El principal inconveniente de almacenar datos binarios en el sistema de archivos, es que separa los datos de la base de datos. Si se elimina un registro de la tabla Products, el archivo asociado a l en el sistema de archivos del servidor web, no se eliminar automticamente. Tendremos que escribir cdigo adicional para eliminar el archivo o el sistema de archivos se convertir en un sistema lleno de archivos no utilizables, hurfanos. Adems cuando realicemos copias de seguridad de la base de datos, debemos asegurarnos de realizar copias de seguridad de los datos binarios asociados en el sistema de archivos. Mover la base de datos a otro sitio o servidor, tambin plantea desafos similares. Por otra parte, los datos binarios pueden almacenarse directamente en una base de datos de Microsoft SQL Server 2005 mediante la creacin de una columna de tipo varbinary. Al igual que con los otros tipos de datos de longitud variable, podemos especificar la longitud mxima de los datos binarios que pueden ser almacenados en esta columna. Por ejemplo podemos reservar un mximo de 5,000 bytes usando varbinary(5000); varbinary(mx) permite el tamao de almacenamiento mximo, alrededor de 2Gb. La principal ventaja de almacenar datos binarios directamente en la base de datos, es el estrecho vnculo entre los datos binarios y el registro de la base de datos. Esto simplifica enormemente las tareas de administracin de la base de datos, como copias

de seguridad o el movimiento de la base de datos a un sitio o servidor diferente. Adems, al eliminar un registro se eliminan automticamente los datos binarios correspondientes, Hay tambin beneficios ms sutiles de almacenar los datos binarios en la base de datos. Vea Almacenamiento de archivos binarios directamente en la base de datos con Asp.Net 2.0 para profundizar en la discusin. Nota: En Microsoft SQL Server 2000 y en versiones anteriores, el tipo de datos varbinary tiene un lmite mximo de 8,000 bytes. Para almacenar datos binarios de hasta 2gb era necesario utilizar en su lugar el tipo de datos Image. Sin embargo, con la incorporacin de MAX en SQL Server 2005 el tipo de datos Image ha quedado obsoleto. Este aun se permite por compatibilidad con versiones anteriores, aunque Microsoft ha anunciado que el tipo de datos Image se remover en una versin futura de SQL Server. Si est trabajando con un modelo de datos anterior, puede observar el tipo de datos Image. La tabla Categories de la base de datos Northwind tiene una columna Picture que puede ser usada para almacenar los datos binarios de un archivo de imagen para la categora. Como la base de datos Northwind tiene sus races en Microsoft Access y versiones anteriores de SQL Server, esta columna es de tipo Image. Para este y los prximos tres tutoriales, utilizaremos ambos mtodos. La tabla Categories ya tiene una columna Picture para almacenar el contenido binario de un archivo de imagen para cada categora. Nosotros aadiremos una columna adicional, BrochurePath, para almacenar una ruta a un PDF en el sistema de archivos del servidor web que puede ser utilizada para proporcionar una calidad de impresin y una visin brillante de la categora. Paso 3. Agregar una columna BrochurePath a la tabla Categories Actualmente la tabla Categories solo tiene cuatro columnas: CategoryID, CategoryName, Description y Picture. Adems de estos campos, tenemos que aadir uno nuevo, que apunte a la imagen de la categora (si existe). Para agregar, esta columna vaya al explorador de servidores y profundice en tablas, luego haga clic derecho en la tabla Categories y elija Abrir definicin de tabla (Ver Figura 5). Si no ve el explorador de servidores, para que aparezca, seleccione la opcin Explorador de servidores del men Ver o pulse Ctrl+Alt+S.

Agregue una nueva columna varchar(200) a la tabla Categories que se denomine BrochurePath y que permita Nulls y luego haga clic en Guardar (o pulse Ctrl+S).

Figura 5. Agregar una columna BrochurePath a la tabla Categories Paso 4. Actualizacin de la arquitectura para usar las columnas Picture y BrochurePath El CategoriesDataTable en la capa de acceso a datos (DAL) actualmente cuenta con cuatro Datacolumns definidas: CategoryID, CategoryName, Description y NumberOfProducts. Cuando diseamos originalmente este DataTable en el tutorial Creacin de una capa de acceso a datos, el CategoriesDataTable solo tena las tres primeras columnas, la columna NumberOfProducts se agrego en el tutorial Maestro/Detalle utilizando una lista con vietas de los registros maestros con un DataList de detalles. Como se discuti en el tutorial de Creacin de una capa de acceso a datos, los DataTables en el DataSet tipiado conforman los objetos de negocio. Los TableAdapters son los encargados de la comunicacin con la base de datos y de poblar los objetos de negocio con los resultados de las consultas. El CategoriesDataTable es poblado por el CategoriesTableAdapter que tiene tres mtodos de recuperacin de datos:

GetCategories() ejecuta la consulta principal del TableAdapter y devuelve los


campos CategoryID, CategoryName y Description de todos los registros en la tabla

Categories. La consulta principal es la que es usada para generar automticamente los mtodos Insert y Update.

GetCategoryByCategoryID(categoryID) GetCategoriesAndNumberOfProducts()

devuelve devuelve

los los

campos campos

CategoryID, CategoryID,

CategoryName y Description de la categora cuyo CategoryID es igual a categoryID. CategoryName y Description de todos los registros en la tabla Categories. Tambin utiliza una subconsulta para devolver el nmero de productos asociados con cada categora. Tenga en cuenta que ninguna de estas consultas devuelve las columnas BrochurePath o Picture de la Tabla Categories ni hace que el CategoriesDataTable proporcione DataColumns para estos campos. Con el fin de trabajar con las propiedades BrochurePath y Picture, primero necesitamos agregarlos al CategoriesDataTable y luego actualizar la clase CategoriesTableAdapter para devolver estas columnas. Agregar los DataColumns Picture y BrochurePath Comenzamos agregando estas dos columnas al CategoriesDataTable. Haga clic derecho en el encabezado del CategoriesDataTable, seleccione Agregar del men contextual y luego seleccione la opcin columna. Esto creara un nuevo DataColumn en el DataTable llamado Column1. Nombre esta columna Picture. Desde la ventana Propiedades, establezca la propiedad DataType del DataColumn a System.Byte[] (Esta no es una opcin en la lista desplegable, es necesario ingresarla).

Figura 6. Crear un DataColumn llamado Picture cuyo DataType es System.Byte[] Agregue otro DataColumn al DataTable, nombrndolo BrochurePath y use el valor DataType por defecto (System.String). Devolver los valores de Picture y BrochurePath desde el TableAdapter Con estos dos DataColumns agregados al CategoriesDataTable, estamos listos para actualizar el CategoriesTableAdapter. Podemos tener los valores de ambos columnas devueltos en la consulta principal del TableAdapter, pero esto significara recuperar los datos binarios cada vez que el mtodo GetCategories() sea invocado. En su lugar, actualizaremos la consulta principal del TableAdapter para que devuelva la ruta (BrochurePath) y crearemos un mtodo adicional que devuelva datos, para que devuelva la columna Picture de una categora en particular. Para actualizar la consulta principal del TableAdapter, hacemos clic derecho en el encabezado del CategoriesTableAdapter y seleccionamos la opcin configurar desde el men contextual. Esto abrir el asistente de configuracin del TableAdapter, el cual hemos visto en un gran nmero de tutoriales pasados. Actualice la consulta para que aparezca el BrochurePath y haga clic en finalizar.

Figura 7. Actualiza la lista de columnas en la sentencia SELECT para que devuelva el BrochurePath Cuando usamos sentencias SQL ad-hoc para el TableAdapter, al actualizar la lista de columnas en la consulta principal se actualiza la lista de columnas de todos los

mtodos de consulta SELECT en el TableAdapter. Lo que significa que el mtodo GetCategoryByCategoryID(categoryID) ha sido actualizado para recuperar la columna BrochurePath, lo cual era nuestra intencin. Sin embargo esto tambin actualiza la lista de columnas del mtodo GetCategoriesAndNumberOfProducts() removiendo la subconsulta que devuelve el numero de productos de cada categora. Por lo tanto necesitamos actualizar la sentencia SELECT de esta consulta. Haga clic derecho en el mtodo GetCategoriesAndNumberOfProducts(), escoja configurar y regrese la sentencia SELECT de nuevo a su valor original.
SELECT CategoryID, CategoryName, Description, (SELECT COUNT(*) FROM Products p WHERE p.CategoryID = c.CategoryID) as NumberOfProducts FROM Categories c

Luego agregamos un nuevo mtodo al TableAdapter que devuelva el valor de la columna Picture de una categora en particular. Haga clic derecho sobre el encabezado del CategoriesTableAdapter y seleccione la opcin agregar consulta para iniciar el asistente de configuracin de consultas del TableAdapter. El primer paso de este asistente es preguntarnos si deseamos que la consulta de datos use una sentencia SQL ad-hoc, un nuevo procedimiento almacenado o usar uno existente. Seleccione usar sentencias SQL y haga clic en siguiente. Como devolveremos una fila, seleccione la opcin SELECT que devuelva filas en el segundo paso.

Figura 8. Seleccione la opcin Usar Sentencias SQL

Figura 9. Como la consulta devuelve un registro desde la tabla Categories, seleccione la opcin SELECT que devuelva filas En el tercer paso ingrese la siguiente consulta SQL y de clic en siguiente:
SELECT CategoryID, CategoryName, Description, BrochurePath, Picture FROM Categories WHERE CategoryID = @CategoryID

El

ltimo

paso

es

seleccionar

el

nombre y

para

el

nuevo

mtodo.

Utilice

FillCategoryWithBinaryDataByCategoryID

GetCategoryWithBinaryDataByCategoryID

para los enfoques Llenar un DataTable y Devolver un DataTable, respectivamente. Haga clic en Finalizar para completar el asistente.

Figura 10. Seleccione los nombres para los mtodos del TableAdapter Nota: Despus de completar el asistente de configuracin de consultas del TableAdapter podramos ver un cuadro de dialogo informndonos que el nuevo command text devuelve datos con un esquema diferente al esquema de la consulta principal. En concreto el asistente nota que la consulta principal GetCategories() del TableAdapter devuelve un esquema diferente al que recin creamos. Pero esto es lo que deseamos as que podemos descartar el mensaje. Tambin tenga en mente que si est usando sentencias SQL ad-hoc y usa el asistente para cambiar la consulta principal del TableAdapter como en el punto anterior, esto modificara la lista de columnas de la sentencia SELECT del mtodo GetCategoryWithBinaryDataByCategoryID para incluir estas columnas a la columna principal (esto es, removeremos la columna Picture de la consulta). Tendremos que actualizar manualmente la lista de columnas para devolver la columna Picture, de forma similar a lo que hicimos con el mtodo GetCategoriesAndNumberOfProducts() en el paso anterior Despus de agregar los dos DataColumns al CategoriesDataTable y el mtodo GetCategoriesWithBinaryDataByCategoryID al CategoriesTableAdapter, estas clases en el diseador del DataSet tipiado lucirn como la captura de pantalla en la figura 11.

Figura 11. El diseador del DataSet incluye las nuevas columnas y el mtodo Actualizando la capa lgica de negocios (BLL) Con la DAL actualizada, todo lo que resta es ampliar la capa lgica de negocios (BLL) para incluir un mtodo para el nuevo mtodo del CategoriesTableAdapter. Agregue el siguiente mtodo a la clase CategoriesBLL:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetCategoryWithBinaryDataByCategoryID(categoryID As Integer) _ As Northwind.CategoriesDataTable Return Adapter.GetCategoryWithBinaryDataByCategoryID(categoryID) End Function

Paso 5. Cargar un archivo desde el cliente al servidor web Cuando recogemos datos binarios, a menudo esta informacin es suministrada por un usuario final. Para capturar esta informacin, el usuario debe ser capaz de cargar un archivo desde su ordenador al servidor web. Luego los datos cargados deben ser integrados con el modelo de datos, lo que significa guardar el archivo en el sistema de archivos del servidor web y aadir la ruta del archivo en la base de datos, o escribir el contenido binario directamente en la base de datos. En este paso observaremos como permitir al usuario cargar archivos desde su ordenador al servidor. En el siguiente tutorial centraremos nuestra atencin en la integracin de los archivos cargados con el modelo de datos. El nuevo control web FileUpload de Asp.Net 2.0 proporciona un mecanismo para que los usuarios enven un archivo desde su ordenador al servidor web. El control FileUpload se muestra como un elemento <input> cuyo atributo type se establece en archivo, para que los navegadores lo muestren como un cuadro de texto con un botn Examinar. Al hacer clic en el botn examinar se abrir un cuadro de dialogo desde donde el usuario puede seleccionar un archivo. Cuando el formulario es enviado de

regreso, los contenidos del archivo seleccionado son enviados junto con la devolucin de datos. En el lado del servidor la informacin acerca del archivo cargado es accesible por medio de las propiedades del control FileUpload. Para demostrar la carga de archivos, abra la pgina FileUpload.aspx en la carpeta BinaryData, arrastre un control FileUpload desde la barra de herramientas hasta el diseador y establezca la propiedad ID del control en UploadTest. Luego agregue un control web Button estableciendo las propiedades ID y Text del control en UploadButton y UploadSelectedFile, respectivamente. Finalmente, coloquemos un control web Label debajo del Botn, limpiando su propiedad Text y establezca su propiedad ID a UploadDetails.

Figura 12. Agregue un control FileUpload a la pgina ASP.Net La Figura 13 muestra esta pgina cuando la visualizamos a travs de un navegador. Note que dando clic en el botn del navegador aparecer un cuadro de dialogo de seleccin de archivos, permitiendo al usuario seleccionar un archivo desde su computador. Una vez que el archivo ha sido seleccionado, dando clic en el botn Cargar archivo seleccionado originamos una devolucin de datos que enva el contenido binario del archivo seleccionado al servidor web.

Figura 13. El usuario puede seleccionar un archivo para cargarlo desde su computador al servidor En la devolucin de datos, el archivo seleccionado puede ser guardado en el sistema de archivos o sus datos binarios pueden ser almacenados directamente por medio de un Stream. Para este ejemplo, crearemos una carpeta ~/Brochures y guardaremos all los archivos cargados. Comenzaremos agregando al sitio la carpeta Brochures como una subcarpeta del directorio raz. Luego creamos un controlador de eventos para el evento clic del control UploadButton y agregamos el siguiente cdigo:
Protected Sub UploadButton_Click(sender As Object, e As EventArgs) _ Handles UploadButton.Click If UploadTest.HasFile = False Then ' No file uploaded! UploadDetails.Text = "Please first select a file to upload..." Else ' Display the uploaded file's details UploadDetails.Text = String.Format( _ "Uploaded file: {0}<br />" & _ "File size (in bytes): {1:N0}<br />" & _ "Content-type: {2}", _ UploadTest.FileName, _ UploadTest.FileBytes.Length, _ UploadTest.PostedFile.ContentType) ' Save the file Dim filePath As String = _ Server.MapPath("~/Brochures/" & UploadTest.FileName) UploadTest.SaveAs(filePath) End If End Sub

El control FileUpload proporciona una variedad de propiedades para trabajar con los datos cargados. Por ejemplo la propiedad HasFile indica si el usuario cargo un archivo,

mientras que la propiedad FileBytes proporciona acceso a los datos binarios cargados como un conjunto de bytes. El controlador del evento Clic comienza asegurndose que un archivo ha sido cargado. Si un archivo ha sido cargado, el Label muestra el nombre del archivo cargado, su tamao en bytes y su tipo de contenido. Nota: Para asegurarse que los usuarios cargaron un archivo podemos verificar la propiedad HasFile y mostrar una advertencia si es False o podra usar en su lugar el control RequiredFieldValidator. El SaveAs(filePath) del FileUpload guarda el archivo cargado en la ruta de archivo especificada. La ruta del archivo en debe lugar ser de una una ruta ruta fsica virtual (C:\Websites\Brochures\SomeFile.pdf)

(/Brochures/SomeFile.pdf). El mtodo Server.MapPath(virtPath) toma una ruta virtual y devuelve su correspondiente ruta fsica. Aqu la ruta virtual es ~/Brochures/fileName, donde fileName es el nombre del archivo cargado. Vea usar Server.MapPath para mayor informacin sobre las rutas fsicas y virtuales y el uso de Server.MapPath. Despus de completar el controlador del evento Click, tmese un momento para probar la pgina en un navegador. Haga clic en el botn Buscar y seleccione un archivo de su disco duro, luego haga clic en el botn cargar archivo seleccionado. La devolucin de datos enviar los contenidos del archivo seleccionado al servidor web, el cual mostrar luego la informacin relacionada con el archivo antes de guardarlo en la carpeta ~/Brochures. Despus de cargar el archivo, regresamos a Visual Studio y hacemos clic en el botn actualizar del explorador de soluciones. Usted deber ver el archivo recin cargado en la carpeta ~/Brochures!

Figura 14. El archivo EvolutionValley.jpg ha sido cargado al servidor web

Figura 15. EvolutionValley.jpg ha sido guardado en la carpeta ~/Brochures Sutilezas con los archivos cargados guardados al sistema de archivos Hay muchas sutilezas que deben ser direccionadas cuando guardamos archivos cargados al sistema de archivos del servidor web. Primero, hay un riesgo de seguridad. Para guardar un archivo en el sistema de archivos, el contexto de seguridad bajo el cual la pgina ASP.Net se est ejecutando debe tener permisos de escritura. El servidor de desarrollo web ASP.Net arranca bajo el contexto de su cuenta de usuario actual. Si usted est usando Microsoft Internet Information Services (IIS) como servidor web, el contexto de seguridad depende de la versin de IIS y su configuracin. Otro desafo de guardar archivos en el sistema de archivos gira en torno al nombre de los archivos. Actualmente, nuestra pgina guarda todos los archivos cargados en el directorio ~/Brochures usando el mismo nombre de archivo que en el computador del cliente. Si el usuario A carga un Brochure con el nombre Brochure.pdf, el archivo ser guardado como ~/Brochure/Brochure.pdf. Pero que sucede si luego el usuario B carga un folleto de archivo diferente con el mismo nombre (Brochure.pdf)? Con el cdigo que tenemos ahora, el archivo del usuario A se sobrescribir con el cargado por el usuario B. Hay un nmero de tcnicas para resolver los conflictos de los nombres de los archivos. Una opcin es la de prohibir cargar un archivo si ya existe uno con el mismo nombre.

Con este enfoque cuando un usuario B intenta cargar un archivo llamado Brochure.pdf, el sistema no guarda el archivo y en su lugar mostrar un mensaje al usuario B para cambiar el nombre del archivo e intentarlo de nuevo. Otro enfoque es guardar el archivo usando un nombre nico de archivo, el cual puede ser un globally unique identifier (GUID) o el valor correspondiente de la columna de llave primaria del registro de la base de datos correspondiente (asumiendo que la carga est asociada con una fila en particular en el modelo de datos). En el prximo tutorial exploraremos estas opciones en ms detalle. Desafos Involucrados con el manejo de datos binarios muy grandes Estos tutoriales asumen que los datos binarios capturados son modestos en tamao. Trabajar con archivos de datos binarios demasiados grandes que tienen demasiados megabytes introduce nuevos desafos que estn fuera del alcance de estos tutoriales. Por ejemplo, por defecto ASP.Net rechaza cargas de ms de 4MB, aunque esto puede ser configurado por medio del elemento <httpRuntime> en Web.config. IIS tambin impone sus propias limitaciones de tamao del archivo cargado. Adems el tiempo tomado para cargar archivos grandes podra exceder los 110 segundos por defecto que ASP.Net esperar por una solicitud. Tambin hay problemas de memoria y rendimiento que surgen cuando trabajamos con archivos grandes. El control FileUpload es poco prctico para la carga de archivos muy grandes. Cuando los contenidos de los archivos estn siendo posteados al servidor, el usuario final debe esperar pacientemente sin ninguna confirmacin de que su carga esta en progreso. Esto no es mucho problema cuando tratamos con archivos pequeos que pueden ser cargados en pocos segundos, pero puede ser un problema cuando tratamos con archivos muy grandes que pueden tomar varios minutos para ser cargados. Hay una variedad de controles de carga de archivos de terceras partes que son mejores para el manejo de grandes cargas y muchos de estos vendedores proporcionan indicadores de progreso y manejadores de carga ActiveX que presentan una experiencia al usuario mucho ms amigable. Si su aplicacin necesita manejar grandes archivos, necesitara investigar

cuidadosamente los desafos y encontrar soluciones apropiadas para sus necesidades particulares. Resumen

Construir una aplicacin que necesite capturar datos binarios introduce un gran nmero de desafos. En este tutorial exploramos las primeras dos: decidir donde almacenar los datos binarios y permitir al usuario cargar el contenido a travs de una pgina web. En los prximos tres tutoriales exploraremos como asociar los datos cargados con un registro en la base de datos as como tambin mostrar los datos binarios junto a sus campos de datos de texto.

52. MOSTRAR DATOS BINARIOS EN LOS CONTROLES DE DATOS WEB


En este tutorial veremos las opciones para presentar datos binarios en una pgina web, incluyendo la presentacin de un archivo de imagen y el suministro de un enlace de descarga para un archivo PDF. Introduccin En el tutorial anterior exploramos las dos tcnicas para asociar datos binarios con el modelo de datos subyacente de la aplicacin y usamos el control FileUpload para cargar archivos desde un navegador al sistema de archivos del servidor web. Aun no hemos visto como asociar los datos binarios cargados con el modelo de datos. Esto es despus que un archivo ha sido cargado y guardado en el sistema de archivos, una ruta al archivo debe ser almacenada en el registro apropiado de la base de datos. Si los datos estn siendo almacenados directamente en la base de datos entonces los datos binarios cargados no necesitan guardarse en el sistema de archivos, pero deben ser ingresados en la base de datos. Antes de observar como asociar los datos con el modelo de datos, primero observaremos como proporcionar los datos binarios al usuario final. Presentar datos de texto es bastante sencillo, pero como deben ser presentados los datos binarios? Esto por supuesto depende del tipo de de datos binarios. Para imgenes, podramos querer mostrar las imgenes, para documentos PDFs, Microsoft Word, archivos ZIP y otros tipos de datos binarios, proporcionar un enlace de descarga es probablemente lo ms apropiado. En este tutorial observaremos como presentar los datos binarios junto a sus datos de texto asociados usando controles de datos web como el GridView y DetailsView. En el siguiente tutorial pondremos nuestra atencin en asociar un archivo cargado con la base de datos. Paso 1. Proporcionar los valores de BrochurePath La columna Pictures de la tabla Categories ya contiene datos binarios para las imgenes de varias de las categoras. Especficamente, la columna Picture de cada registro almacena los contenidos binarios granulados, de baja calidad y en un mapa de imagen de 16 bits. Cada imagen de categora es de 172 pixeles de ancho por 120 pixeles de alto y consume aproximadamente 11KB. Adems el contenido binario en la columna

Picture incluye un encabezado OLE de 78 bytes. Este encabezado de informacin es presentado porque la base de datos Northwind tiene sus races en Microsoft Access. En Acces los datos binarios son almacenados usando el tipo de datos de objetos OLE, el cual se incrusta en este encabezado. Por ahora veremos cmo eliminar los encabezados de estas imgenes de baja calidad con el fin de mostrar la imagen. En un futuro tutorial construiremos una interfaz para actualizar la columna Picture de la categora y remplazar estos mapas de bits de las imgenes que usen encabezados OLE con imgenes JPG equivalentes sin los encabezados OLE innecesarios. En el tutorial anterior vimos como usar el control FileUpload. Por lo tanto, podemos continuar y agregar archivos de catalogo al sistema de archivos del servidor web. Sin embargo, haciendo esto no actualizamos la columna BrochurePath en la tabla Categories. En el siguiente tutorial veremos cmo realizar esto, pero por ahora es necesario que proporcionemos manualmente los valores a esta columna. En la descarga de este tutorial encontraremos siete archivos PDF en la carpeta ~/Brochures para cada una de las categoras a excepcin de Seafood. Hemos omitido a propsito la adicin de un brochure a Seafood para mostrar cmo manejar escenarios donde no todos los registros tienen datos binarios asociados. Para actualizar la tabla Categories con estos valores, hacemos clic derecho sobre el nodo Categories del explorador de servidores y seleccionamos mostrar tabla de datos. Luego ingrese la ruta virtual del archivo catalogo para cada categora que tenga un catalogo, como muestra la figura 1. Como no hay un catalogo para la categora Seafood, dejaremos el valor de su columna BrochurePath como NULL.

Figura 1. Ingrese manualmente los valores para la columna BrochurePath de la tabla Categories Paso 2. Proporcionar un enlace de descarga para los Brochures en un GridView

Con los valores de BrochurePath proporcionados para la tabla Categories, estamos listos para crear un GridView que muestre cada categora junto con un enlace para descargar el folleto de la categora. En el paso 4 ampliaremos este GridView para mostrar tambin la imagen de la categora. Comenzamos arrastrando un GridView desde la caja de herramientas hasta el diseador de la pgina DisplayOrDownloadData.aspx en la carpeta BinaryData. Establezca el ID del GridView en Categories y a travs de la etiqueta inteligente del GridView, seleccione enlazar este a un nuevo origen de datos. Especficamente enlace este a un ObjectDataSource llamado CategoriesDataSource que devuelva los datos usando el mtodo GetCategories() del objeto CategoriesBLL.

Figura 2. Crear un nuevo ObjectDataSource llamado CategoriesDataSource

Figura 3. Configure el ObjectDataSource para usar la clase CategoriesBLL

Figura 4. Recupere la lista de categoras usando el mtodo GetCategories() Despus de completar el asistente de configuracin del origen de datos, Visual Studio agregar automticamente un BoundField al GridView Categories para las DataColumns CategoryID, CategoryName, Description, NumberOfProducts y BrochurePath. Sigamos adelante y eliminemos el BoundField NumberOfProducts ya que la consulta del mtodo GetCategories() no recupera esta informacin. Tambin removamos el BoundField CategoryID y cambie las propiedades HeaderText de los BoundFields CategoryName y BrochurePath a Category y Brochure respectivamente. Despus de realizar estos

cambios, el marcado declarativo de su ObjectDataSource y el GridView lucirn como sigue:


<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False" DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" EnableViewState="False"> <Columns> <asp:BoundField DataField="CategoryName" HeaderText="Category" SortExpression="CategoryName" /> <asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" /> <asp:BoundField DataField="BrochurePath" HeaderText="Brochure" SortExpression="BrochurePath" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource>

Vea esta pgina a travs de un navegador (ver figura 5). Cada una de las 8 categoras es mostrada. Las siete categoras con valores de BrochurePath tienen el valor de BrochurePath mostrados en el BoundField respectivo. Comida de mar, que es la categora que tiene un valor NULL en su BrochurePath, muestra una celda vaca.

Figura 5. Se muestra el valor del Name, Description y BrochurePath de cada categora En lugar de mostrar el texto de la columna BrochurePath, deseamos crear un enlace al Brochure. Para realizar esto, remueva el BoundField BrochurePath y remplcelo con un

HiperLinkField. Establezca la propiedad HeaderText del nuevo HiperLinkField a Brochure, su propiedad Text a View Brochure y su propiedad DataNavigateUrlFields a BrochurePath.

Figura 6. Agregue un HiperLinkField por BrochurePath Esto agregar una columna de enlaces al GridView, como muestra la figura 7. Haciendo clic en el enlace ViewBrochure mostraremos el PDF directamente en el navegador o solicitaremos al usuario que descargue el archivo, dependiendo de si el lector de PDF est instalado y de las configuraciones del navegador.

Figura 7. Un folleto de la categora puede ser visto haciendo clic en el enlace Ver Folleto

Figura 8. Se muestra el enlace PDF de la categora Esconder el texto Ver Archivo para las categoras sin archivo Como se muestra en la Figura 7, el HiperLink BrochurePath muestra el valor de su propiedad Text (View Brochure) para todos los registros, independientemente de si hay o no un valor non-Null para BrochurePath. Por supuesto si BrochurePath es NULL, entonces el enlace es mostrado solamente como un texto, como es el caso de la categora Seafood (regresar a la Figura 7). En lugar de mostrar el texto Ver Archivo, podra ser bueno hacer que aquellas categoras sin un valor BrochurePath muestren algn texto alternativo como No Brochure Available. Con el fin de proporcionar este comportamiento, necesitamos usar un TemplateField cuyo contenido es generado por medio de una llamada al mtodo de la pgina que emite la salida apropiada segn el valor de BrochurePath. Primeramente exploramos esta tcnica de formato en el tutorial anterior Uso de TemplateFields en el GridView.

Convierta el HiperLinkField en un TemplateField seleccionando el HiperLinkField BrochurePath y presionando el enlace convertir este campo en un TemplateField en el cuadro de dialogo Editar Columnas.

Figura 9. Convertir el HiperLinkField en un TemplateField Esto creara un TemplateField con un ItemTemplate que contiene un control HiperLink cuya propiedad NavigateUrl esta enlazada al valor de BrochurePath. Remplace este marcado con una llamada al mtodo GenerateBrochureLink, que pasa el valor de BrochurePath:
<asp:TemplateField HeaderText="Brochure"> <ItemTemplate> <%# GenerateBrochureLink(Eval("BrochurePath")) %> </ItemTemplate> </asp:TemplateField>

Luego cree un mtodo Protected en la clase de cdigo subyacente de la pgina ASP.Net llamado GenerateBrochureLink que devuelva una cadena y acepte un Object como parmetro de entrada.
Protected Function GenerateBrochureLink(BrochurePath As Object) As String If Convert.IsDBNull(BrochurePath) Then Return "No Brochure Available" Else

Return String.Format("<a href="{0}">View Brochure</a>", _ ResolveUrl(BrochurePath.ToString())) End If End Function

Este mtodo determina si el valor Object pasado es un valor Null de base de datos, si es as devuelve un mensaje indicando que la categora carece de un folleto. De lo contrario, si hay un valor BrochurePath este es mostrado en un HiperLink. Note que si el valor BrochurePath es presentado, este es pasado al mtodo ResolveUrl(url). Este mtodo resuelve la Url pasada, remplazando el carcter ~ con la ruta virtual apropiada. Por ejemplo, si la aplicacin tiene sus races en /Tutorial55, ResolverUrl(~/Brochures/Meats.pdf) devolver /Tutorial55/Brochures/Meat.pdf. La figura 10 muestra la pgina despus de que se han aplicado estos cambios. Note que el campo BrochurePath de la categora Seafood ahora muestra el texto No Brochure Available.

Figura 10. El texto No Brochure Available es mostrado en aquellas categoras sin folleto Paso 3. Agregar una pgina web que muestre la imagen de una categora Cuando un usuario visita una pgina ASP.Net, recibe el HTML de la pgina ASP.Net. El HTML recibido es solamente texto y no contiene ningn dato binario. Cualquier dato binario adicional, como imgenes, archivos de sonido, aplicaciones de Macromedia Flash, videos de Windows Media Player y otros, existen como recursos separados en el servidor web. El HTML contiene referencias a estos archivos, pero no incluye los contenidos actuales de los archivos.

Por ejemplo en HTML el elemento <img> es usado para referenciar una imagen, con el atributo src apuntando al archivo imagen, as:
<img src="MyPicture.jpg" ... />

Cuando un navegador recibe este HTML, hace otra solicitud al servidor web para recuperar los contenidos binarios del archivo imagen, que luego se muestra en el navegador. El mismo concepto se aplica a cualquier dato binario. En el paso 2, el folleto no fue enviado al navegador como parte del marcado HTML de la pgina. En lugar de esto, el HTML presentado proporciono HiperLinks que cuando son presionados, originan que el navegador solicite el documento PDF directamente. Para mostrar o permitir a los usuarios descargar datos binarios que residen dentro de la base de datos, necesitamos crear una pgina web separada que devuelva los datos. Para nuestra aplicacin, solamente hay un campo de datos binarios almacenado directamente en la base de datos, la imagen de la categora. Por lo tanto, necesitamos una pgina que cuando sea llamada devuelva la imagen de datos de una categora en particular. Agregue una nueva pgina ASP.Net a la carpeta BinaryData llamada

DisplayCategoryPicture.aspx. Cuando hagamos esto, dejemos la casilla de verificacin de la seleccin de pgina maestra sin marcar. Esta pgina espera un valor CategoryID en la cadena de consulta y devuelve el dato binario de la columna Picture de la categora. Como la pgina devuelve datos binarios y nada ms, no necesitamos ningn marcado en la seccin HTML. Por lo tanto presione la pestaa Fuente en la parte inferior izquierda de la pgina y remueva todo el marcado de la pgina excepto la directiva <%@Page%>. El marcado declarativo que DisplayCategoryPicture.aspx lucir consiste de una sola lnea:
<%@ Page Language="VB" AutoEventWireup="true" CodeFile="DisplayCategoryPicture.aspx.vb" Inherits="BinaryData_DisplayCategoryPicture" %>

Si ve el atributo MasterPageFile en la directiva <%@Page %>, remuvalo. En la clase de cdigo subyacente, agregue el siguiente cdigo al controlador de evento Page_Load:

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load Dim categoryID As Integer = _ Convert.ToInt32(Request.QueryString("CategoryID")) ' Get information about the specified category Dim categoryAPI As New CategoriesBLL() Dim categories As Northwind.CategoriesDataTable = _ categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID) Dim category As Northwind.CategoriesRow = categories(0) ' Output HTTP headers providing information about the binary data Response.ContentType = "image/bmp" ' Output the binary data ' But first we need to strip out the OLE header Const OleHeaderLength As Integer = 78 Dim strippedImageLength As Integer = _ category.Picture.Length OleHeaderLength Dim strippedImageData(strippedImageLength) As Byte Array.Copy(category.Picture, OleHeaderLength, _ strippedImageData, 0, strippedImageLength) Response.BinaryWrite(strippedImageData) End Sub

Este cdigo inicia leyendo el valor CategoryID de la cadena de consulta en la variable llamada categoryID. Luego el dato de imagen es recuperado llamando al mtodo GetCategoryWithBinaryDataByCategoryID(categoryID) de la clase CategoriesLL. Este dato es recuperado para el cliente usando el mtodo Response.BinaryWrite(data), pero antes de que sea llamado, el encabezado OLE del valor de la columna Picture debe ser removido. Esto es realizado creando un array Byte llamado strippedImageData que almacenara precisamente 78 caracteres menos que lo que est en la columna Picture. El mtodo Array.Copy es usado para copiar los datos de Category.Picture comenzando en la posicin 78 en adelante de strippedImageData. La propiedad Response.ContentType especifica el tipo MIME del contenido que est siendo recuperado as el navegador conoce como presentarlo. Como la columna Picture de la tabla Categories es un mapa de bits de imagen, el tipo MIME del mapa de bits usado aqu es (image/bmp). Si omite el tipo MIME, la mayora de navegadores aun mostraran la imagen correctamente porque ellos pueden inferir el tipo basado en el contenido de los datos binarios del archivo Image. Sin embargo, es prudente incluir el

tipo MIME cuando sea posible. Vea asignacin de nmeros en internet autorizados para los sitios web para una lista completa de los tipos media MIME. Con esta pgina creada, una imagen de una categora en particular puede ser visualizada visitando DisplayCategoryPicture.aspx?CategoryID=categoryID. La figura 11 muestra la imagen de la categora Beverages, que puede ser vista desde DisplayCategoryPicture.aspx?CategoryID=1.

Figura 11. Se muestra la imagen de la categora Beverages Si cuando visitamos DisplayCategoryPicture.aspx?CategoryID=categoryID, se genera una excepcin que diga Unable to cast of type System.DBNull to type System.Byte[], hay dos cosas que pueden estar causando esto. Primero, la columna Picture de la tabla Categories no permite valores Null. Sin embargo, la pgina DisplayCategoryPicture.aspx asume que no hay presente un valor non-NULL. La propiedad Picture de la CategoriesDataTable no puede ser accesible directamente si tiene un valor NULL.. Si desea permitir valores Null para la columna Picture, debemos incluir la siguiente condicin:
If category.IsPictureNull() Then ' Display some "No Image Available" picture Response.Redirect("~/Images/NoPictureAvailable.gif") Else ' Send back the binary contents of the Picture column ' ... Set ContentType property and write out ... ' ... data via Response.BinaryWrite ... End If

El

cdigo

anterior

asume

que

hay

algn

archivo

de

imagen

llamado

No

PictureAvailable.gif en la carpeta Images que desea ser mostrado para aquellas categoras sin una imagen. Esta excepcin tambin puede ser causada si la sentencia SELECT del mtodo GetCategoryWithBinaryDataByCategoryID del CategoriesTableAdapter revierte la lista de columnas de la consulta principal, lo que puede pasar si est usando sentencias SQL ad-hoc y ha vuelto a ejecutar la consulta principal del TableAdapter. Asegrese de verificar que la sentencia SELECT del mtodo GetCategoryWithBinaryDataByCategoryID todava incluye la columna Picture. Nota: Cada vez que DisplayCategoryPicture.aspx es visitada, la base de datos es accedida y los datos de la imagen de la categora especificada son recuperados. Si la imagen de la categora no ha cambiado desde que el usuario la visito por ltima vez, aunque este es un esfuerzo intil. Afortunadamente, HTTP permite condicionales GET. Con un condicional GET, el cliente hace que la solicitud HTTP sea enviada junto con el encabezado HTTP If-Modified-Since que proporciona la fecha y hora en la que el cliente recupero este recurso desde el servidor web. Si el contenido no ha tenido cambios desde la fecha especificada, el servidor web podra responder con un Not Modified status code (304) y olvidar enviar el contenido del recurso solicitado. En concreto, esta tcnica alivia al servidor web de tener que enviar el contenido de nuevo de un recurso si no ha sido modificado desde la ltima vez que el cliente accedi a l. Sin embargo para implementar este comportamiento, se requiere que agregue una columna PictureLastModified a la tabla Categories que capture cuando la columna Picture fue actualizada por ltima vez as como el cdigo para verificar el encabezado If-Modified-Since. Para mayor informacin sobre el encabezado If-Modified-Since y el flujo de trabajo del condicional GET, vea Condicionales HTTP GET para RSS Hackers y una mirada ms profunda al desarrollo de las solicitudes HTTP en una pgina ASP.Net. Paso 4. Mostrar la imagen de las categoras en un GridView Ahora que tenemos una pgina web que muestra la imagen de una categora en particular, podemos mostrarla usando el control web Image o un elemento HTML <img> apuntando a DisplayCategoryPicture.aspx?CategoryID=categoryID. Las Imgenes cuya URL es determinada por los datos de la base de datos que pueden ser mostrados en el GridView o DetailsView usando el ImageField. El ImageField contiene

las propiedades DataImageUrlField y DataUrlFormatString que trabajan igual que las propiedades DataNavigateUrlField y DataNavigateUrlFormatString del HiperLinkField. Ampliaremos el GridView Categories en DisplayOrDownloadData.aspx para agregar un ImageField que muestre la figura de cada categora. Sencillamente agrega el ImageField y establezca sus propiedades DataImageUrlField y DataImageUrlFormatString en CategoryID y DisplayCategoryPicture.aspx?CategoryID={0}, respectivamente. Esto crear una columna GridView que presente un elemento <img> cuyo atributo src referencie a DisplayCategoryPicture.aspx?CategoryID={0}, donde {0} es remplazado con el valor CategoryID de cada fila del GridView.

Figura 12. Agrega un ImageField al GridView Despus de agregar el ImageField, la sintaxis declarativa del GridView lucir como la siguiente:
<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False" DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" EnableViewState="False"> <Columns> <asp:BoundField DataField="CategoryName" HeaderText="Category" SortExpression="CategoryName" />

<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" /> <asp:TemplateField HeaderText="Brochure"> <ItemTemplate> <%# GenerateBrochureLink(Eval("BrochurePath")) %> </ItemTemplate> </asp:TemplateField> <asp:ImageField DataImageUrlField="CategoryID" DataImageUrlFormatString="DisplayCategoryPicture.aspx?CategoryID={0}"> </asp:ImageField> </Columns> </asp:GridView>

Tmese un momento para ver esta pgina a travs de un navegador. Note que ahora cada registro incluye una imagen para la categora.

Figura 13. La imagen de la categora es mostrada para cada fila Resumen En este tutorial examinamos como presentar datos binarios. Como se presentan los datos depende del tipo de datos. Para los archivos folleto PDF, ofrecemos al usuario un enlace Ver Folleto que cuando es presionado lleva al usuario directamente al archivo PDF. Para la imagen de la categora, primero creamos una pgina que recupere y

devuelva los datos binarios de la base de datos y luego usamos esa pgina para mostrar la imagen de cada categora en un GridView. Tenga en cuenta que hemos usado como mostrar datos binarios, por lo que estamos listos para examinar como insertar, actualizar y eliminar los datos binarios en la base de datos. En el prximo tutorial veremos cmo asociar un archivo cargado con su correspondiente registro en la base de datos. En el siguiente tutorial veremos cmo actualizar los datos binarios existentes as como la forma de eliminar los datos binarios cuando su registro asociado ha sido eliminado.

53. INCLUIR LA OPCIN DE CARGA DE ARCHIVO CUANDO AGREGAMOS UN NUEVO REGISTRO


Este tutorial se muestra cmo crear una interfaz web que permita al usuario ingresar datos y cargar archivos binarios. Para mostrar las opciones que son validas para almacenar datos binarios, guardaremos un archivo en la base de datos y otro ser almacenado en el sistema de archivos. Introduccin En los dos tutoriales anteriores exploramos tcnicas para almacenar datos binarios que estn asociados con el modelo de datos de la aplicacin; vimos como usar el control FileUpload para enviar archivos desde el cliente al servidor web, y vimos como presentar estos datos binarios en un control de datos web. Aun no hemos hablado de cmo asociar los datos cargados con el modelo de datos. En este tutorial crearemos una pgina web para agregar una nueva categora. Adems de los TextBoxes para el nombre de la categora y descripcin, esta pgina necesita incluir dos controles FileUpload, uno para la imagen de la categora y otro para el folleto. La imagen cargada ser almacenada directamente en el nuevo registro en la columna Picture, mientras que el folleto ser guardado en la carpeta ~/Brochures con la ruta al archivo guardado en la columna BrochurePath del nuevo registro. Antes de crear esta nueva pgina web, necesitamos actualizar la arquitectura. La consulta principal del CategoriesTableAdapter no recupera la columna Picture. Por lo tanto el mtodo Insert generado automticamente solo tiene entradas para los campos CategoryName, Description y BrochurePath. Por lo tanto necesitamos crear un mtodo adicional en el TableAdapter que nos solicite todos los cuatro campos de Categories. La clase Categories en la capa lgica de negocios tambin necesita ser actualizada. Paso 1. Agregar un mtodo InsertingWithPicture al CategoriesTableAdapter Cuando creamos el CategoriesTableAdapter en el tutorial anterior Creacin de una capa de acceso a datos, lo configuramos para que generara automticamente las sentencias INSERT, UPDATE y DELETE basado en la consulta principal. Adems indicamos al TableAdapter para que empleara el enfoque directo DB, que crea los mtodos Insert, Update y Delete. Estos mtodos ejecutan las sentencias INSERT, UPDATE y DELETE generadas automticamente y en consecuencia aceptan parmetros de entrada basados

en las columnas devueltas por la consulta principal. En el tutorial Carga de Archivos ampliamos la consulta principal del CategoriesTableAdapter para que usara la columna BrochurePath. Como la consulta principal del CategoriesTableAdapter no referencia la columna Picture, no podemos agregar un nuevo registro o actualizar un registro existente con un valor para la columna Picture. Con el fin de capturar esta informacin, podemos agregar un nuevo mtodo en el TableAdapter que sea usado para especificar la insercin de un registro con datos binarios o podemos personalizar la sentencia INSERT generada automticamente. El problema con la personalizacin de la sentencia INSERT generada automticamente es que tenemos el riesgo que nuestras personalizaciones sean sobrescritas por el asistente. Por ejemplo, imagine que personalizamos la sentencia INSERT para incluir el uso de la columna Picture. Esto actualizara el mtodo Insert del TableAdapter e incluir un parmetro de entrada adicional para los datos binarios de la imagen de la categora. Luego podemos crear un mtodo en la capa lgica de negocios que use este mtodo DAL e invoque este mtodo BLL a travs de la capa de presentacin y todo trabajara maravillosamente. Esto ser as hasta la prxima vez que configuremos el TableAdapter por medio del asistente de configuracin del TableAdapter. Tan pronto sea completado el asistente, nuestras personalizaciones a la sentencia INSERT se sobrescriben, el mtodo Insert regresara a su antigua forma y nuestro cdigo podra no volver a compilar. Nota: Esta molestia no se genera cuando usamos procedimientos almacenados en lugar de sentencias SQL ad-hoc. En un futuro tutorial exploraremos el uso de procedimientos almacenados en lugar de sentencias SQL ad-hoc en la capa de acceso a datos. Para evitar este potencial dolor de cabeza, en lugar de personalizar las sentencias SQL generadas automticamente, crearemos un nuevo mtodo para el TableAdapter. Este mtodo llamado InsertWithPicture, aceptara valores para las columnas CategoryName, Description, BrochurePath y Picture y ejecuta una sentencia INSERT que almacena todos estos nuevos valores en un nuevo registro. Abra el DataSet tipiado y desde el diseador haga clic derecho sobre el encabezado del CategoriesTableAdapter y seleccione Agregar Consulta en el men contextual. Esto ejecutara el asistente de configuracin de consultas del TableAdapter, que comienza preguntndonos como la consulta del TableAdapter acceder a la base de datos.

Seleccione usar sentencias SQL y presione Siguiente. El siguiente paso nos solicita el tipo de consulta que generamos. Como estamos creando una consulta para agregar un nuevo registro a la tabla Categories, seleccionamos INSERT y presionamos Siguiente.

Figura 1. Seleccione la opcin INSERT Ahora necesitamos especificar la sentencia SQL INSERT. El asistente automticamente nos sugiere una sentencia INSERT que corresponde a la consulta principal del TableAdapter. En este caso, es una sentencia INSERT que ingresa los valores CategoryName, Description y BrochurePath correspondientes. Actualice la sentencia para que la columna Picture sea incluida junto con un parmetro @Picture, as:
INSERT INTO [Categories] ([CategoryName], [Description], [BrochurePath], [Picture]) VALUES (@CategoryName, @Description, @BrochurePath, @Picture)

La pantalla final del asistente nos pregunta el nombre del nuevo mtodo del TableAdapter. Ingrese InsertWithPicture y presione Finalizar.

Figura 2. Nombre el nuevo mtodo del TableAdapter InsertWithPicture Paso 2. Actualizar la capa lgica de negocios Como la capa de presentacin solamente interacta con la capa lgica de negocios en lugar de ir pasando directamente a la capa de acceso a datos, necesitamos crear un mtodo BLL que invoque el mtodo DAL que acabamos de crear (InsertWithPicture). Para este tutorial, creamos un mtodo en la clase CategoriesBLL llamado InsertWithPicture que acepte como entradas, tres Strings y un array Byte. Los parmetros de entrada String son para el nombre de la categora, descripcin y ruta del archivo del folleto, mientras que el array Byte es para el contenido binario de la imagen de la categora. Como muestra el siguiente cdigo, este mtodo BLL invoca el correspondiente mtodo DAL:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Insert, False)> _ Public Sub InsertWithPicture(categoryName As String, description As String, _ brochurePath As String, picture() As Byte) Adapter.InsertWithPicture(categoryName, description, brochurePath, picture) End Sub

Nota: Asegrese que ha guardado el DataSet tipiado antes de agregar el mtodo InsertWithPicture a la BLL. Como el cdigo de la clase CategoriesTableAdapter es generado automticamente basado en el DataSet tipiado, si no guarda primero los cambios en el DataSet tipiado, la propiedad Adapter no reconocer al mtodo InsertWithPicture.

Paso 3. Mostrar las categoras existentes y sus datos Binarios En este tutorial crearemos una pgina que permita que un usuario final agregue una nueva categora al sistema, proporcionado una imagen y un folleto para la nueva categora. En el tutorial anterior usamos un GridView con un TemplateField y un ImageField para mostrar el nombre de cada categora, descripcin, imagen y un enlace para descargar su folleto. Replicaremos esa funcionalidad en este tutorial, creando una pgina que muestre todas las categoras existentes y que permita que nuevas categoras sean creadas. Comenzamos abriendo la pgina DisplayOrDownload.aspx de la carpeta BinaryData. Vaya a la vista fuente y copie la sintaxis declarativa del GridView y ObjectDataSource, pegndola dentro del elemento <asp:Content> en UploadInDetailsView.aspx. Adems no olvide copiar el mtodo GenerateBrochureLink desde la clase de cdigo subyacente de DisplayOrDownload.aspx a UploadInDetailsView.aspx.

Figura 3. Copie y pegue la sintaxis declarativa desde DisplayOrDownload.aspx a UploadInDetailsView.aspx Despus de copiar la sintaxis declarativa y el mtodo GenerateBrochureLink en la pgina UploadInDetailsView.aspx, veamos la pgina desde un navegador asegurndonos que todo fue copiado correctamente. Deberamos ver un GridView que muestre las ocho categoras que incluyen un enlace para descargar el folleto as como la imagen de la categora.

Figura 4. Deberamos ver cada categora junto con sus datos binarios Paso 4. Configurar el CategoriesDataSource para soportar la Insercin El ObjectDataSource CategoriesSource usado por el GridView Categories actualmente no proporciona la habilidad de insertar datos. Con el fin de soportar la insercin por medio de su control de origen de datos, necesitamos asignar su mtodo Insert a un mtodo en su objeto subyacente CategoriesBLL. En concreto, deseamos asignarlo al mtodo de CategoriesBLL que agregamos en el paso 2, InsertWithPicture. Comenzamos presionando el enlace de configuracin de origen de datos desde la etiqueta inteligente del ObjectDataSource. La primera pantalla nos muestra el objeto con el que est configurado para trabajar el origen de datos, CategoriesBLL. Deje esta definicin como esta y presione siguiente para avanzar a la pantalla de definicin de mtodos de datos. Muvase a la pestaa INSERT y seleccione el mtodo InsertWithPicture de la lista desplegable. Presione finalizar para completar el asistente.

Figura 5. Configure el ObjectDataSource para que use el mtodo InsertWithPicture Nota: Una vez completado el asistente, Visual Studio podra preguntarnos si deseamos actualizar los campos y llaves, lo cual volver a generar los campos de los controles de datos web. Seleccione NO porque al seleccionar YES sobrescribiremos cualquier personalizacin de los campos que hayamos hecho. Despus de completar el asistente, ObjectDataSource ahora incluye un valor para su propiedad InsertMethod as como InsertParameters para las cuatro columnas de la categora, as que el marcado declarativo ser como el siguiente:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"> <InsertParameters> <asp:Parameter Name="categoryName" Type="String" /> <asp:Parameter Name="description" Type="String" /> <asp:Parameter Name="brochurePath" Type="String" /> <asp:Parameter Name="picture" Type="Object" /> </InsertParameters> </asp:ObjectDataSource>

Paso 5. Crear la interfaz de insercin Como cubrimos primero en el tutorial Resumen de Insercin, Actualizacin y Eliminacin de dato, el control DetailsView proporciona una interfaz de insercin predefinida que puede ser utilizada cuando trabajamos con un control de origen de

datos que soporte insercin. Agregaremos un control DetailsView a esta pgina encima del GridView que se presentara permanentemente en su interfaz de insercin, permitindole al usuario agregar rpidamente una nueva categora. Una vez agregamos una nueva categora en el DetailsView, el GridView de debajo de este se actualizara automticamente y mostrara la nueva categora. Comenzamos arrastrando un DetailsView desde la caja de herramientas hasta el diseador encima del GridView, estableciendo su propiedad ID en NewCategory y limpiando el valor de sus propiedades Height y Width. Desde la etiqueta inteligente del DetailsView, enlace este al CategoriesDataSource existente y luego verifique la casilla de verificacin Habilitar Insercin.

Figura 6. Enlazar el DetailsView al CategoriesDataSource y habilitar la insercin Para presentar de forma permanente el DetailsView en su interfaz de insercin, establezca su propiedad DefaultMode en Insert. Note que el DetailsView tiene cinco BoundFields CategoryID, CategoryName,

Description, NumberOfProducts y BrochurePath aunque el BoundField CategoryID no es presentado en la interfaz de insercin porque su propiedad InsertVisible est definida en False. Estos BoundFields existen porque son las columnas devueltas por el mtodo GetCategories(), que es el que el ObjectDataSource invoca para recuperar sus datos. Sin embargo para la insercin, no deseamos permitir que el usuario especifique un valor para NumberOfProducts. Adems necesitamos permitir que carguen una imagen a la nueva categora as como cargar un PDF para el brochure.

Remueva el BoundField NumberOfProducts del DetailsView y luego actualice las propiedades HeaderText de los BoundFields CategoryName y BrochurePath a Category y Brochure respectivamente. Luego convierta el BoundField BrochurePath en un TemplateField y agregue un nuevo TemplateField para la imagen, dando a este nuevo TemplateField un valor HeaderText de Picture. Mueva el TemplateField Picture para que este en medio del TemplateField BrochurePath y el CommandField.

Figura 7. Enlace el DetailsView al CategoriesDataSource y Habilite la insercin Si convirti el BoundField BrochurePath en un TemplateField por medio del cuadro de dialogo Editar Campos, el TemplateField incluye un ItemTemplate, EditItemTemplate y un InsertItemTemplate. Sin embargo solo necesitamos el InsertItemTemplate, as que sintase libre de remover los otros dos templates. Hasta este momento la sintaxis declarativa del nuestro DetailsView lucir similar al siguiente:
<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False" DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" DefaultMode="Insert"> <Fields> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" InsertVisible="False" ReadOnly="True" SortExpression="CategoryID" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" SortExpression="CategoryName" />

<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" /> <asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath"> <InsertItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("BrochurePath") %>'></asp:TextBox> </InsertItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Picture"></asp:TemplateField> <asp:CommandField ShowInsertButton="True" /> </Fields> </asp:DetailsView>

Agregar controles FileUpload para los campos Brochure y Picture Actualmente el InsertItemTemplate del TemplateField BrochurePath contiene un TextBox, mientras que el TemplateField Picture no contiene ningn templates. Necesitamos actualizar estos dos InsertItemTemplates de los TemplateFields para que usen controles FileUpload. Desde la etiqueta inteligente del DetailsView, seleccione la opcin Editar templates y luego seleccione el InsertItemTemplate del TemplateField BrochurePath desde la lista desplegable. Remueva el TextBox y luego arrastre un control FileUpload desde la caja de herramientas hasta el template. Establezca el ID del control FileUpload en BrochureUpload. De forma similar, agregue un control FileUpload al InsertItemTemplate del TemplateField Picture. Establezca el ID de este control FileUpload en PictureUpload.

Figura 8. Agregue un control FileUpload al InsertItemTemplate Despus de hacer estas adiciones, el marcado declarativo de los dos TemplateFields lucir as:
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath"> <InsertItemTemplate> <asp:FileUpload ID="BrochureUpload" runat="server" /> </InsertItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Picture"> <InsertItemTemplate> <asp:FileUpload ID="PictureUpload" runat="server" /> </InsertItemTemplate> </asp:TemplateField>

Cuando un usuario agrega una nueva categora, deseamos asegurarnos que el folleto y la imagen son del tipo de archivo correcto. Para el folleto, el usuario debe suministrar un PDF. Para la imagen necesitamos que el usuario cargue un archivo de imagen, pero deseamos permitir cualquier archivo de imagen o solamente archivos de imagen de un tipo particular, como GIFs o JPGs? Con el fin de permitir diferentes tipos de archivos necesitamos ampliar el esquema Categories para incluir una columna que capture el tipo de archivo para que este tipo pueda ser enviado al cliente por medio de Response.ContentType en DisplayCategoryPicture.aspx. Como no tenemos una columna as, es prudente restringir a los usuarios para que solo proporcionen un tipo de archivo de imagen especfico. Las imgenes existentes en la tabla Categories son mapas de bits, pero JPGs es un formato de archivo ms apropiado para imgenes servidas en la web. Si un usuario carga un tipo de archivo incorrecto, necesitamos cancelar la insercin y mostrar un mensaje indicando el problema. Agregue un control web Label debajo del DetailsView. Establezca su propiedad ID en UploadWarning, limpie su propiedad Text, establezca la propiedad CssClass en Warning y las propiedad Visible y EnableViewState en False. La clase CSS Warning est definida en Styles.css y presenta un texto grande, rojo, italizado y en negrita.

Nota: Idealmente, los BoundFields CategoryName y Description sern convertidos en TemplatesFields y se personalizaron sus interfaces de insercin. Por ejemplo la interfaz de insercin de Description, sera ms adecuada en un TextBox multilnea y como la columna CategoryName no acepta valores nulos, deber agregarse un RequiredFieldValidator que asegure que el usuario proporciono un valor para el nombre de la nueva categora. Estos pasos se dejan como ejercicio al lector. Refirase de nuevo al tutorial Personalizacin de la interfaz de modificacin de datos para un vistazo ms profundo del mejoramiento de las interfaces de modificacin de datos. Paso 6. Guardar el folleto cargado en el sistema de archivos del servidor web Cuando el usuario ingresa los valores para una nueva categora y presiona el botn Insert, ocurre una devolucin de datos y se desarrolla el flujo de trabajo de insercin. Primero se genera el evento ItemInserting del DetailsView. Luego se invoca el mtodo Insert() del ObjectDataSource, que resulta en un nuevo registro que est siendo agregado a la tabla Categories(). Despus de esto se genera el evento ItemInserted del DetailsView. Antes que sea invocado el mtodo Insert() del ObjectDataSource, primero debemos asegurarnos que el tipo apropiado de archivos fue cargado por el usuario y luego guardar el folleto PDF en el sistema de archivos del servidor web. Cree un controlador de evento para el evento ItemInserting del DetailsView y agregue el siguiente cdigo:
' Reference the FileUpload controls Dim BrochureUpload As FileUpload = _ CType(NewCategory.FindControl("BrochureUpload"), FileUpload) If BrochureUpload.HasFile Then ' Make sure that a PDF has been uploaded If String.Compare(System.IO.Path.GetExtension _ (BrochureUpload.FileName), ".pdf", True) <> 0 Then UploadWarning.Text = _ "Only PDF documents may be used for a category's brochure." UploadWarning.Visible = True e.Cancel = True Exit Sub End If End If

El controlador de evento inicia referenciando el control FileUpload BrochureUpload en las plantillas del DetailsView. Luego si el folleto ha sido cargado, se examina la extensin del archivo cargado. Si la extensin no es .PDF, entonces se muestra una advertencia, la insercin es cancelada y finaliza la ejecucin de los otros controladores de eventos. Nota: Confiar en la extensin del archivo cargado no es una tcnica confiable que asegure que el archivo cargado es un documento .PDF. El usuario puede tener un documento PDF vlido con la extensin .Brochure o podra no tener un documento vlido y darle una extensin .pdf. El contenido del archivo binario necesita ser examinado mediante programacin con el fin de verificar el tipo de archivo. Sin embargo los enfoques exhaustivos a menudo son excesivos, ya que la verificacin de la extensin es suficiente para la mayora de escenarios. Como discutimos en el tutorial Carga de Archivos, debemos tener cuidado cuando guardemos archivos en un sistema de archivos, ya que la carga del usuario podra sobrescribir a otro. Para este tutorial intentaremos usar el mismo nombre de un archivo cargado. Sin embargo si ya existe un archivo en el directorio ~/Brochures con el mismo nombre, agregaremos un nmero al final hasta que sea encontrado u nombre nico. Por ejemplo si el usuario carga un folleto llamado Meats.pdf pero ya hay un archivo llamado Meats.pdf en la carpeta ~/Brochures. Cambiaremos el nombre del archivo guardado a Meats-1.pdf. Si este ya existe, trataremos con Meats-2.pdf y as sucesivamente, hasta que sea encontrado un nombre nico para el archivo. El siguiente cdigo usa el mtodo File.Exists(path) para determinar si un archivo ya existe con el nombre de archivo especificado. Si es as continua probando nuevos nombres de archivo para el folleto hasta que no haya ningn conflicto.
Const BrochureDirectory As String = "~/Brochures/" Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName Dim fileNameWithoutExtension As String = _ System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName) Dim iteration As Integer = 1 While System.IO.File.Exists(Server.MapPath(brochurePath)) brochurePath = String.Concat(BrochureDirectory, _ fileNameWithoutExtension, "-", iteration, ".pdf") iteration += 1

End While

Una vez que se ha encontrado un nombre de archivo valido, el archivo necesita ser guardado en el sistema de archivos y el valor del InsertParameter del ObjectDataSource BrochurePath necesita ser actualizado para que este nombre de archivo sea escrito en la base de datos. Como vimos anteriormente en el tutorial Carga de Archivos, el archivo puede ser guardado usando el mtodo SavesAs(path) del control FileUpload. Para actualizar el parmetro BrochurePath del ObjectDataSource, usamos la coleccin e.Values.
' Save the file to disk and set the value of the brochurePath parameter BrochureUpload.SaveAs(Server.MapPath(brochurePath)) e.Values("brochurePath") = brochurePath

Paso 7. Guardar la imagen cargada en la base de datos. Para guardar la imagen cargada en el nuevo registro Categories, necesitamos asignar el contenido binario cargado al parmetro picture del ObjectDataSource en el evento ItemInserting del DetailsView. Sin embargo, antes de hacer esta asignacin primero necesitamos asegurarnos que la imagen cargada es un JPG y no algn otro tipo de imagen. Igual que en el paso 6, usaremos la extensin de la imagen cargada para examinar su tipo. Aunque la table Categories permite valores Null para la columna Picture, todas las categoras actualmente tienen una imagen. Forzaremos al usuario para que proporcione una imagen cuando agregue una nueva categora por medio de esta pgina. El siguiente cdigo hace una verificacin para asegurarse que la imagen ha sido cargada y que tiene una extensin apropiada.
' Reference the FileUpload controls Dim PictureUpload As FileUpload = _ CType(NewCategory.FindControl("PictureUpload"), FileUpload) If PictureUpload.HasFile Then ' Make sure that a JPG has been uploaded If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _ ".jpg", True) <> 0 AndAlso _ String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _ ".jpeg", True) <> 0 Then UploadWarning.Text = _

"Only JPG documents may be used for a category's picture." UploadWarning.Visible = True e.Cancel = True Exit Sub End If Else ' No picture uploaded! UploadWarning.Text = _ "You must provide a picture for the new category." UploadWarning.Visible = True e.Cancel = True Exit Sub End If

Este cdigo debe ser colocado despus del cdigo del paso 6, ya que si hay un problema con la imagen cargada, el controlador de evento terminara despus que el archivo folleto es guardado en el sistema de archivos. Asumiendo que el archivo apropiado ha sido cargado, asignamos el contenido binario al valor del parmetro Picture con la siguiente lnea de cdigo:
' Set the value of the picture parameter e.Values("picture") = PictureUpload.FileBytes

El controlador de evento ItemInserting completo Para finalizar, aqu est el controlador de evento ItemInserting completo:
Protected Sub NewCategory_ItemInserting _ (sender As Object, e As DetailsViewInsertEventArgs) _ Handles NewCategory.ItemInserting ' Reference the FileUpload controls Dim PictureUpload As FileUpload = _ CType(NewCategory.FindControl("PictureUpload"), FileUpload) If PictureUpload.HasFile Then ' Make sure that a JPG has been uploaded If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _ ".jpg", True) <> 0 AndAlso _ String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _ ".jpeg", True) <> 0 Then UploadWarning.Text = _

"Only JPG documents may be used for a category's picture." UploadWarning.Visible = True e.Cancel = True Exit Sub End If Else ' No picture uploaded! UploadWarning.Text = _ "You must provide a picture for the new category." UploadWarning.Visible = True e.Cancel = True Exit Sub End If ' Set the value of the picture parameter e.Values("picture") = PictureUpload.FileBytes ' Reference the FileUpload controls Dim BrochureUpload As FileUpload = _ CType(NewCategory.FindControl("BrochureUpload"), FileUpload) If BrochureUpload.HasFile Then ' Make sure that a PDF has been uploaded If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _ ".pdf", True) <> 0 Then UploadWarning.Text = _ "Only PDF documents may be used for a category's brochure." UploadWarning.Visible = True e.Cancel = True Exit Sub End If Const BrochureDirectory As String = "~/Brochures/" Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName Dim fileNameWithoutExtension As String = _ System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName) Dim iteration As Integer = 1 While System.IO.File.Exists(Server.MapPath(brochurePath)) brochurePath = String.Concat(BrochureDirectory, _ fileNameWithoutExtension, "-", iteration, ".pdf") iteration += 1 End While ' Save the file to disk and set the value of the brochurePath parameter BrochureUpload.SaveAs(Server.MapPath(brochurePath))

e.Values("brochurePath") = brochurePath End If End Sub

Paso 8. Arreglando la pgina DisplayCategoryPicture.aspx Tmese un momento para probar la interfaz de insercin y el controlador del evento ItemInserting que fue creado en los pasos anteriores. Visite la pgina UploadInDetailsView.aspx por medio del navegador y trate de agregar una categora, pero omita la imagen, o especifique una imagen que no sea JPG o un folleto que no sea PDF. En cualquiera de estos casos, se mostrar un mensaje de error y el flujo de insercin ser cancelado.

Figura 9. Se muestra un mensaje de advertencia si un archivo de tipo no valido es cargado Una vez que hemos verificado que la pgina requiere una imagen y que no aceptara archivos diferentes a PDF o JPG, agregamos una nueva categora con una imagen JPG valida, dejando vacio el campo Brochure. Despus de presionar el botn Insert, la pgina devolver datos y se agregara un nuevo registro a la tabla Categories con el contenido binario de la imagen guardado directamente en la base de datos. El GridView es actualizado y muestra una fila para la categora recin agregada, pero como muestra la figura 10, la imagen de la nueva categora no es presentada correctamente.

Figura 10. La imagen de la nueva categora no es presentada correctamente. La razn por la cual la nueva imagen no es presentada correctamente es porque la pgina DisplayCategoryPicture.aspx que devuelve una imagen de la categora especificada est configurada para procesar un mapa de bits que tiene un encabezado OLE. Este encabezado es removido de los contenidos binarios de la columna Picture antes que sean enviados al cliente. Pero el archivo JPG que acabamos de cargar para la nueva categora no tienen este encabezado OLE, por lo tanto bytes validos y necesarios estn siendo removidos del dato binario de la imagen. Como ahora hay mapas de bit con encabezados OLE y JPGs en la tabla Categories, necesitamos actualizar DisplayCategoryPicture.aspx para hacer que el encabezado OLE en las ocho categoras originales sea ignorado y omitamos dicha omisin para los nuevos registros de categora. En nuestro prximo tutorial examinaremos como actualizar una imagen de un registro existente y actualizaremos todas las imgenes de las antiguas categoras para remplazarlas con JPGs. Por ahora, usaremos el siguiente cdigo en DisplayCategoryPicture.aspx para omitir los encabezados OLE solamente en las ocho categoras originales:
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load Dim categoryID As Integer = Convert.ToInt32(Request.QueryString("CategoryID")) ' Get information about the specified category Dim categoryAPI As New CategoriesBLL() Dim categories As Northwind.CategoriesDataTable = _ categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID) Dim category As Northwind.CategoriesRow = categories(0) If categoryID <= 8 Then ' Output HTTP headers providing information about the binary data Response.ContentType = "image/bmp" ' Output the binary data ' But first we need to strip out the OLE header

Const OleHeaderLength As Integer = 78 Dim strippedImageLength As Integer = _ category.Picture.Length OleHeaderLength Dim strippedImageData(strippedImageLength) As Byte Array.Copy(category.Picture, OleHeaderLength, _ strippedImageData, 0, strippedImageLength) Response.BinaryWrite(strippedImageData) Else ' For new categories, images are JPGs... ' Output HTTP headers providing information about the binary data Response.ContentType = "image/jpeg" ' Output the binary data Response.BinaryWrite(category.Picture) End If End Sub

Con este cambio, ahora la imagen JPG e presentada correctamente en el GridView

Figura 11. Las imgenes JPG para las nuevas categoras son presenatdas correctamente Paso 9. Eliminacin del folleto en vista de una excepcin Uno de los desafos de almacenar datos binarios en el sistema de archivos del servidor web es que introduce una desconexin entre el modelo de datos y sus datos binarios. Por lo tanto, si un registro es eliminado, los datos binarios correspondientes en el sistema de archivos deben ser removidos. Esto puede entrar en juego con la insercin. Considere el siguiente escenario: un usuario agrega una nueva categora, especificando una imagen y folleto valido. Una vez que presionamos el botn Insert, ocurre una devolucin de datos y se genera el evento ItemInserting del DetailsView, guardando el folleto en el sistema de archivos del servidor web. Luego es invocado el mtodo Insert()

del ObjectDataSource, el cual llama al mtodo InsertWithPicture de la clase CategoriesBLL, que llama al mtodo InsertWithPicture del CategoriesTableAdapter. Ahora que sucede si la base de datos esta fuera de lnea, o si hay un error en la sentencia SQL INSERT? Claramente fallara la insercin, as que ninguna fila de nueva categora ser agregada a la base de datos. Pero si ya tenemos el archivo folleto cargado en el sistema de archivos del servidor web! Este archivo necesita ser eliminado en vista de una excepcin durante el flujo de trabajo de la insercin. Como discutimos previamente en el tutorial Manejo de Excepciones a nivel de DAL y BLL, cuando una excepcin es generada en las profundidades de la arquitectura, esta sobrepasa varias capas. En la capa de presentacin podemos determinar si ocurri una excepcin en el evento ItemInserted del DetailsView. Este controlador de evento tambin puede proporcionar los valores de los InsertParameters del ObjectDataSource. Por lo tanto podemos crear un controlador de evento para el evento ItemInserted que verifique si hubo una excepcin y de ser as elimine el archivo especificado por el parmetro brochurePath del ObjectDataSource:
Protected Sub NewCategory_ItemInserted _ (sender As Object, e As DetailsViewInsertedEventArgs) _ Handles NewCategory.ItemInserted If e.Exception IsNot Nothing Then ' Need to delete brochure file, if it exists If e.Values("brochurePath") IsNot Nothing Then System.IO.File.Delete(Server.MapPath _ (e.Values("brochurePath").ToString())) End If End If End Sub

Resumen Hay una serie de pasos que deben ser realizados con el fin de proporcionar una interfaz basada en la web para agregar registros que incluyen datos binarios. Si el dato binario es almacenado directamente en la base de datos, necesitaremos actualizar la arquitectura, agregando mtodos especficos que manejan el caso donde datos binarios estn siendo insertados. Una vez que la arquitectura ha sido actualizada, el siguiente paso es crear la interfaz de insercin, que puede ser realizada usando un

DetailsView que ha sido personalizado para incluir un control FileUpload para cada campo de datos binarios. Luego los datos cargados sern guardados en el sistema de archivos del servidor web o asignados a un parmetro de origen de datos en el controlador de evento ItemInserting del DetailsView. Guardar datos binarios en el sistema de archivos requiere ms planeacin que guardar datos directamente en la base de datos. Un nombre de esquema debe ser escogido con el fin de evitar que un usuario sobrescriba otro. Adems pasos adicionales deben ser tomados para eliminar el archivo cargado si la insercin en la base de datos falla. Ahora tenemos la habilidad de agregar nuevas categoras al sistema con un folleto o imagen, pero aun no hemos visto como actualizar un dato binario existente de una categora o como remover correctamente los datos binarios para una categora eliminada. Exploraremos estos dos tpicos en el siguiente tutorial.

54. ACTUALIZACIN Y ELIMINACIN DE DATOS BINARIOS EXISTENTES


En los tutoriales anteriores vimos como el control GridView hace que sea sencillo editar y eliminar datos de texto. En este tutorial veremos como el control GridView hace que sea posible editar y eliminar datos binarios, si los datos binarios son guardados en la base de datos o almacenados en el sistema de archivos Introduccin A travs de los ltimos tres tutoriales agregamos un poco de funcionalidad para trabajar con datos binarios. Comenzamos agregando una columna BrochurePath a la tabla Categories y actualizamos la arquitectura acordemente. Tambin agregamos mtodos a la capa de acceso a datos y a la capa lgica de negocios para trabajar con la columna Picture existente en la tabla Categories, que almacena el contenido binario de un archivo de imagen. Hemos construido pginas web para presentar los datos binarios en un GridView con un enlace de descarga para el folleto y con la imagen de la categora mostrada en un elemento <img> adems hemos agregado un DetailsView para permitir a los usuarios agregar una nueva categora y cargar su folleto y datos de imagen. Todo lo que resta por implementar es la habilidad para editar y eliminar las categoras existentes, esto lo realizaremos en este tutorial usando las capacidades de edicin e insercin predefinidas del GridView. Cuando editamos una categora, el usuario debe ser capaz de cargar de manera opcional una nueva imagen o hacer que la categora contine usando la existente. Para el folleto, podemos escoger usar el folleto existente, cargar un nuevo folleto o indicar que la categora ya tiene un folleto asociado con l. Paso 1. Actualizar la capa de acceso a datos La DAL tiene mtodos Insert, Update y Delete generados automticamente, pero estos mtodos fueron generados basados en la consulta principal del CategoriesTableAdapter, que no incluye a la columna Picture. Por lo tanto los mtodos Insert y Update no incluyen parmetros para especificar los datos binarios de la imagen de la categora. Al igual que como hicimos en el tutorial anterior, necesitamos crear un nuevo mtodo en el TableAdapter para actualizar la tabla Categories cuando especificamos datos binarios.

Abra el DataSet tipiado y desde el diseador, haga clic derecho sobre el encabezado del CategoriesTableAdapter y seleccione Agregar consulta en el men contextual, esto abrir el asistente de configuracin de consulta del TableAdapter. Este asistente inicia preguntndonos como la consulta del TableAdapter acceder a la base de datos. Seleccione Usar sentencias SQL y presione siguiente. El siguiente paso nos solicita el tipo de consulta que ser generado. Como estamos creando una consulta que agrega un nuevo registro a la tabla Categories, seleccione UPDATE y presione siguiente.

Figura 1. Seleccione la opcin UPDATE Ahora necesitamos especificar la sentencia SQL UPDATE. El asistente sugiere automticamente una sentencia UPDATE que corresponde a la consulta principal del TableAdapter (una que actualiza los valores de CategoryName, Description y BrochurePath). Cambie la sentencia para que la columna Picture sea incluida junto con el parmetro @Picture, as:
UPDATE [Categories] SET [CategoryName] = @CategoryName, [Description] = @Description, [BrochurePath] = @BrochurePath , [Picture] = @Picture WHERE (([CategoryID] = @Original_CategoryID))

La pantalla final del asistente nos pregunta el nombre del Nuevo mtodo del TableAdapter. Ingrese UpdateWithPicture y presione Finalizar.

Figura 2. Nombre el nuevo mtodo del TableAdapter UpdateWithPicture Paso 2. Agregar los mtodos a la capa lgica de negocios Adems de actualizar la DAL, necesitamos actualizar la BLL para que incluya mtodos para actualizar y eliminar una categora. Estos mtodos sern invocados desde la capa de presentacin. Para la eliminacin de una categora, podemos usar el mtodo Delete generado automticamente del CategoriesTableAdapter. Agregue el siguiente mtodo a la clase CategoriesBLL:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteCategory(ByVal categoryID As Integer) As Boolean Dim rowsAffected As Integer = Adapter.Delete(categoryID) ' Return true if precisely one row was deleted, otherwise false Return rowsAffected = 1 End Function

Para este tutorial crearemos dos mtodos para actualizar una categora, uno que espere el dato binario de la imagen e invoque al mtodo UpdateWithPicture que recin creamos en el CategoriesTableAdapter y otro que solamente espere los valores CategoryName, Description y BrochurePath y use la sentencia Update generada automticamente por la clase CategoriesTableAdapter. La razn de usar dos mtodos,

es que en algunas circunstancias, un usuario podra actualizar la imagen de la categora junto con sus otros campos, en cuyo caso el usuario carga una nueva imagen. Los datos binarios de la imagen cargada pueden ser usados luego en la sentencia UPDATE. En otros casos, el usuario podra estar interesado solamente en actualizar, digamos el nombre y la descripcin. Pero si la sentencia UPDATE espera los datos binarios para la columna Picture, entonces necesitaramos proporcionar esta informacin. Por lo tanto deseamos dos mtodos UPDATE. La capa lgica de negocios determinara cual usar basado en si los datos de imagen fueron proporcionados cuando actualizamos la categora. Para facilitar esto, agregamos dos mtodos a la clase CategoriesBLL, ambos llamados UpdateCategory. El primero deber aceptar tres Strings, un array Byte y un Integer como sus parmetros de entrada; el segundo solo tres Strings y un Integer. Los parmetros de entrada String son para el nombre de la categora, descripcin y ruta fsica del folleto, el array Byte es para los contenidos binarios de la imagen de la categora y el Integer identifica el CategoryID del registro a actualizar. Note que la primera recarga invoca a la segunda si el array Byte pasado es Nothing:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, False)> _ Public Function UpdateCategory(categoryName As String, description As String, _ brochurePath As String, picture() As Byte, categoryID As Integer) As Boolean ' If no picture is specified, use other overload If picture Is Nothing Then Return UpdateCategory(categoryName, description, brochurePath, categoryID) End If ' Update picture, as well Dim rowsAffected As Integer = Adapter.UpdateWithPicture _ (categoryName, description, brochurePath, picture, categoryID) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateCategory(categoryName As String, description As String, _ brochurePath As String, categoryID As Integer) As Boolean Dim rowsAffected As Integer = Adapter.Update _ (categoryName, description, brochurePath, categoryID)

' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function

Paso 3. Copiar la insercin y ver funcionalidad En el tutorial anterior creamos una pgina llamada UploadInDetailsView.aspx que muestra todas las categoras en un GridView y proporciona un DetailsView para agregar nuevas categoras al sistema. En este tutorial mejoraremos el GridView para que incluya el soporte de edicin y eliminacin. En lugar de continuar trabajando desde UploadInDetailsView, vamos a colocar los cambios del tutorial en la pgina UpdatingAndDeleteing.aspx en la misma carpeta ~/BinaryData. Copie y pegue el marcado declarativo y el cdigo desde UploadInDetailsView.aspx a UpdatingAndDeleting.aspx Comience abriendo la pgina UploadInDetailsView.aspx. Copie toda la sintaxis declarativa dentro del elemento <asp:Content> como se muestra en la figura 3. Luego abra UpdatingAndDeleting.aspx y pegue este marcado dentro de su elemento <asp:Content>. De forma similar copie el cdigo desde la clase de cdigo subyacente de la pgina UploadInDetailsView.aspx a UpdatingAndDeleting.aspx.

Figura 3. Copiar el marcado declarativo desde UploadInDetailsView.aspx Despus de copiar el marcado declarativo y el cdigo, visitamos

UpdatingAndDeleting.aspx. Deber ver la misma salida y tener la misma experiencia de usuario que con la pgina UploadInDetailsView.aspx del tutorial anterior.

Paso 4. Agregar soporte de eliminacin al ObjectDataSource y GridView Como discutimos en el tutorial anterior Resumen de la insercin, actualizacin y eliminacin de datos, el GridView proporciona capacidades de eliminacin predefinidas y estas capacidades pueden ser habilitadas marcando una casilla de verificacin si el origen de datos subyacente del GridView soporta la eliminacin. Actualmente el ObjectDataSource enlazado al GridView (CategoriesDataSource) no soporta la eliminacin. Para remediar esto, presione la opcin Configurar origen de datos desde la etiqueta inteligente del ObjectDataSource para iniciar el asistente. La primera pantalla muestra que el ObjectDataSource est configurado para trabajar con la clase CategoriesBLL. Presione siguiente. Actualmente solo estn especificadas las propiedades InsertMethod y SelectMethod del ObjectDataSource. Sin embargo el asistente poblara automticamente las listas desplegables de las pestaas UPDATE y DELETE con los mtodos UpdateCategory y DeleteCategory respectivamente. Esto es debido a que en la clase CategoriesBLL marcamos estos mtodos usando el DataObjectMethodAttribute como los mtodos predefinidos para actualizar y eliminar. Por ahora establezca la lista desplegable de la pestaa UPDATE en (Ninguno), pero deje la lista desplegable de la pestaa DELETE establecida en DeleteCategory. Volveremos a este asistente en el paso 6 para agregar soporte de actualizacin.

Figura 4. Configure el ObjectDataSource para que utilice el mtodo DeleteCategory Nota: una vez completado el asistente, Visual Studio podra preguntarnos si deseamos actualizar los campos y las llaves, lo cual volver a generar los controles web de los

campos de datos. Seleccione, NO porque seleccionando YES se sobrescribe cualquier personalizacin de campos que hayamos hecho. Ahora el ObjectDataSource incluye un valor para su propiedad DeleteMethod as como un DeleteParameter. Recuerde que cuando usamos el asistente para especificar los mtodos, Visual Studio establece la propiedad OldValuesParameterFormatString en original_{0}, lo que causa problemas con la invocacin de los mtodos de actualizacin y eliminacin. Por lo tanto limpie esta propiedad o defnala con su valor por defecto {0}. Si necesita actualizar su memoria sobre esta propiedad del ObjectDataSource, vea el tutorial Resumen de Insercin, Actualizacin y Eliminacin de datos. Despus de completar el asistente y arreglar el OldValuesParameterFormatString, el marcado declarativo del ObjectDataSource lucir similar al siguiente:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL" InsertMethod="InsertWithPicture" DeleteMethod="DeleteCategory"> <InsertParameters> <asp:Parameter Name="categoryName" Type="String" /> <asp:Parameter Name="description" Type="String" /> <asp:Parameter Name="brochurePath" Type="String" /> <asp:Parameter Name="picture" Type="Object" /> </InsertParameters> <DeleteParameters> <asp:Parameter Name="categoryID" Type="Int32" /> </DeleteParameters> </asp:ObjectDataSource>

Despus de configurar el ObjectDataSource, agregamos las capacidades de eliminacin al GridView seleccionando la casilla de verificacin Habilitar Eliminacin desde la etiqueta inteligente del GridView. Esto agregara un CommandField al GridView cuya propiedad ShowDeleteButton este definida en True.

Figura 5. Habilitar el soporte de eliminacin en el GridView Tome un momento para probar la funcionalidad de eliminacin. Existe una llave fornea entre el CategoryID de la tabla Products y el CategoryID de la tabla Categories, as que si intentamos eliminar cualquiera de las primeras ocho categoras, generaremos una excepcin de violacin de restriccin de llave fornea. Para probar esta funcionalidad, agregamos una nueva categora, proporcionando el folleto y la imagen. Mi categora test mostrada en la figura 6, incluye un archivo de folleto de prueba llamado test.pdf y una imagen de prueba. La figura 7 muestra el GridView despus que la categora test ha sido agregada.

Figura 6. Agregar una categora Test con un folleto e imagen

Figura 7. Despus de insertar la categora test, es mostrada en el GridView En Visual Studio, actualice el explorador de soluciones. Ahora deber ver un nuevo archivo en la carpeta ~/Brochures, Test.pdf (Ver Figura 8). Luego presione el enlace Delete en la fila de la categora Test, originando que la pgina devuelva los datos y llame al mtodo DeleteCategory de la clase CategoriesBLL. Esto invocara al mtodo Delete de la DAL, haciendo que la sentencia DELETE apropiada sea enviada a la base de datos. Luego los datos son enlazados nuevamente al GridView y el marcado es enviado de regreso al cliente ya sin la categora test presente. Aunque el flujo de trabajo de eliminacin removi la categora test de la tabla Categories, no removi su archivo de folleto del sistema de archivos del servidor web. Actualice el explorador de soluciones y vera que Test.pdf todava est en la carpeta ~/brochures.

Figura 8. El archivo Test.pdf no ha sido eliminado del sistema de archivos del servidor web Paso 5. Remover el archivo folleto de la categora eliminada Uno de los desafos de almacenar datos binarios externos a la base de datos es que se deben tomar medidas extras para limpiar estos archivos cuando el registro asociado ha sido eliminado de la base de datos. El GridView y el ObjectDataSource proporcionan eventos que se generan antes y despus que el comando delete ha sido realizado. Actualmente necesitamos crear controladores de evento para los eventos de las acciones previas y posteriores. Antes que el registro Categories sea eliminado necesitamos determinar la ruta de su archivo PDF, pero no deseamos eliminar el PDF antes que la categora es eliminada ya que en el caso que ocurra alguna excepcin, la categora no sea eliminada El evento RowDeleting del GridView se genera despus que el comando delete del ObjectDataSource ha sido invocado, mientras que el evento RowDeleted se genera despus. Cree controladores de eventos para estos dos eventos usando el siguiente cdigo:
' A page variable to "remember" the deleted category's BrochurePath value Private deletedCategorysPdfPath As String = Nothing

Protected Sub Categories_RowDeleting(sender As Object, e As GridViewDeleteEventArgs) _ Handles Categories.RowDeleting ' Determine the PDF path for the category being deleted... Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID")) Dim categoryAPI As New CategoriesBLL() Dim categoriesData As Northwind.CategoriesDataTable = _ categoryAPI.GetCategoryByCategoryID(categoryID) Dim category As Northwind.CategoriesRow = categoriesData(0) If category.IsBrochurePathNull() Then deletedCategorysPdfPath = Nothing Else deletedCategorysPdfPath = category.BrochurePath End If End Sub Protected Sub Categories_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _ Handles Categories.RowDeleted ' Delete the brochure file if there were no problems deleting the record If e.Exception Is Nothing Then DeleteRememberedBrochurePath() End If End Sub

En el controlador de evento RowDeleting, el CategoryID de la fila que est siendo eliminada es grabado desde la coleccin DataKeys del GridView, al cual podemos acceder en este controlador de evento por medio de la coleccin e.Keys. Luego el GetCategoryByCategoryID(categoryID) de la clase CategoriesBLL es invocado para recuperar la informacin acerca del registro que est siendo eliminado. Si el objeto CategoriesDataRow recuperado tiene un valor BrochurePath no nulo entonces este es almacenado en la variable de pgina deletedCategorysPdfPath para que el archivo pueda ser eliminado en el controlador de evento RowDeleted. Nota: En lugar de recuperar los detalles BrochurePath del registro Categories que est siendo eliminado en el controlador de evento RowDeleting, de forma alterna podramos agregar el BrochurePath a la propiedad DataKeysNames del GridView y acceder al valor del registro usando la coleccin e.Keys. Haciendo esto incrementamos ligeramente el tamao del ViewState del GridView, pero reducimos una cantidad de cdigo necesario y nos ahorramos un viaje a la base de datos.

Despus que el comando delete del ObjectDataSource subyacente ha sido invocado, se genera el controlador del evento RowDeleted del GridView. Si no hay excepciones en la eliminacin de datos y hay un valor para deletedCategorysPdfPath, entonces el PDF es eliminado del sistema de archivos. Note que este cdigo extra no es necesario para limpiar los datos binarios de la imagen asociada con su categora. Esto es porque los datos de imagen estn almacenados directamente en la base de datos, eliminando la fila Categories tambin eliminamos los datos de la imagen de la categora. Despus de agregar los dos controladores de eventos, ejecutamos esta prueba de nuevo. Cuando eliminamos la categora, su PDF asociado tambin es eliminado. Actualizar los datos binarios asociados a un registro existente proporciona algunos desafos interesantes. El resto de este tutorial profundiza en la adicin de las capacidades de edicin del folleto y la imagen. El paso 6 explora las tcnicas para actualizar la informacin del folleto, mientras que el paso 7 ve como actualizar la imagen. Paso 6. Actualizar un folleto de categora Como discutimos en el tutorial de Resumen sobre Insercin, Actualizacin y Eliminacin de datos, el GridView ofrece un soporte de edicin a nivel de fila predefinido que puede ser implementado marcando una casilla de verificacin si su origen de datos est configurado apropiadamente. Actualmente el ObjectDataSource no est configurado para incluir soporte de actualizacin, as que se lo agregaremos. Presione el enlace Configurar origen de datos desde el asistente del ObjectDataSource y proceda al segundo paso. Debido al DataObjectMethodAttribute usado en CategoriesBLL, la lista desplegable de UPDATE se poblara automticamente con la recarga UploadCategory que acepta cuatro parmetros de entrada (todas las columnas menos Picture). Cambiemos esto para que acepte la recarga con cinco parmetros de entrada.

Figura 9. Configurar el ObjectDataSource para que use el mtodo UpdateCategory que incluye un parmetro para Picture El ObjectDataSource ahora incluir un valor para su propiedad UpdateMethod as como los correspondientes UpdateParameters. Como notamos en el paso 4, Visual Studio establece la propiedad OldValuesParameterFormatString en original_{0} cuando usamos el asistente de configuracin de origen de datos. Esto causara problemas con la invocacin de los mtodos de actualizacin y eliminacin. Por lo tanto limpie esta propiedad o establzcala a su valor por defecto de {0}. Despus de completar el asistente y arreglar el OldValuesParameterFormatString, el marcado declarativo de su ObjectDataSource lucir como el siguiente:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL" InsertMethod="InsertWithPicture" DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory"> <InsertParameters> <asp:Parameter Name="categoryName" Type="String" /> <asp:Parameter Name="description" Type="String" /> <asp:Parameter Name="brochurePath" Type="String" /> <asp:Parameter Name="picture" Type="Object" /> </InsertParameters> <DeleteParameters> <asp:Parameter Name="categoryID" Type="Int32" /> </DeleteParameters>

<UpdateParameters> <asp:Parameter Name="categoryName" Type="String" /> <asp:Parameter Name="description" Type="String" /> <asp:Parameter Name="brochurePath" Type="String" /> <asp:Parameter Name="picture" Type="Object" /> <asp:Parameter Name="categoryID" Type="Int32" /> </UpdateParameters> </asp:ObjectDataSource>

Para habilitar las funciones de edicin predefinidas del GridView, seleccione la opcin Habilitar Edicin en la etiqueta inteligente del GridView. Esto establecer la propiedad ShowEditButton del CommandField en True, lo que originara la adicin de un botn Edit (y botones Update y Cancel para la fila que est siendo editada)

Figura 10. Configurar el soporte de edicin del GridView Visite la pgina por medio de un navegador y presione uno de los botones Edit de las filas. Los BoundFields CategoryName y Description son presentados como TextBoxes. El TemplateField BrochurePath carece de un EditItemTemplate, as que continuara mostrando su ItemTemplate, un enlace al folleto. El ImageField Picture es presentado como un TextBox cuya propiedad Text esta asignada al valor del DataImageUrlField del ImageField, en este caso CategoryID.

Figura 11. El GridView carece de una interfaz de edicin para BrochurePath Personalizar la interfaz de edicin del BrochurePath Necesitamos crear una interfaz de edicin para el TemplateField BrochurePath, que permita al usuario: Dejar el folleto de la categora como esta Actualizar el folleto de la categora actualizando un nuevo folleto o Removiendo el folleto de la categora (en el caso que la categora no tenga un folleto asociado) Tambin necesitamos actualizar la interfaz de edicin del ImageField Picture, pero haremos esto en el paso 7. Desde la etiqueta inteligente del GridView, presione el enlace Editar Templates y seleccione en la lista desplegable el EditItemTemplate del TemplateField del BrochurePath. Agregue un control web RadioButtonList, estableciendo su propiedad ID en BrochureOptions y su propiedad AutoPostBack en False. En la ventana Propiedades, presione los puntos suspensivos de la propiedad Items, lo que abrir el editor de coleccin ListItem. Agregue las siguientes tres opciones con los valores 1, 2 y 3, respectivamente: Usar el folleto actual Remover el folleto actual Actualizar un nuevo folleto

Establezca la propiedad Selected del primer ListItem en True.

Figura 12. Agregar tres ListItems al RadioButtonList Debajo del RadioButtonList, agregue un control FileUpload llamado BrochureUpload. Establezca su propiedad Visible en False.

Figura 13. Agregar un control RadioButtonList y FileUpload al EditItemTemplate Este RadioButtonList proporciona tres opciones al usuario. La idea es que el control FileUpload se mostrara solamente si la ltima opcin Actualizar un nuevo folleto ha sido seleccionada. Para realizar esto creamos un controlador de evento para el evento SelectedIndexChanged del RadioButtonList y agregamos el siguiente cdigo:

Protected Sub BrochureOptions_SelectedIndexChanged _ (sender As Object, e As EventArgs) ' Get a reference to the RadioButtonList and its Parent Dim BrochureOptions As RadioButtonList = _ CType(sender, RadioButtonList) Dim parent As Control = BrochureOptions.Parent ' Now use FindControl("controlID") to get a reference of the ' FileUpload control Dim BrochureUpload As FileUpload = _ CType(parent.FindControl("BrochureUpload"), FileUpload) ' Only show BrochureUpload if SelectedValue = "3" BrochureUpload.Visible = (BrochureOptions.SelectedValue = "3") End Sub

Como los controles RadioButtonList y FileUpload estn dentro de una plantilla, tenemos que escribir un poco de cdigo para acceder mediante programacin a estos controles. El controlador de evento SelectedIndexChanged pasa una referencia del RadioButtonList en el parmetro de entrada sender. Para conseguir el control FileUpload, necesitamos conseguir el control padre del RadioButtonList y usar el mtodo FindControl(controlID). Una vez que se han referenciados ambos controles, la propiedad Visible del FileUpload se establece en True solamente si el SelectedValue del RadioButtonList es igual a 3, que es el valor del ListItem Actualizar un nuevo folleto. Con este cdigo en su lugar, tmese un momento para probar la interfaz de edicin. Presione el botn Edit de una fila. Inicialmente la opcin Usar el folleto actual debe estar seleccionada. Cambiando el ndice seleccionado originamos una devolucin de datos. Si la tercera opcin es seleccionada, se muestra el control FileUpload o de lo contrario permanece oculto. La figura 14 muestra la interfaz de edicin cuando el primer botn Edit es presionado, la figura 15 muestra la interfaz luego que la opcin actualizar un nuevo folleto es seleccionada.

Figura 14. Inicialmente la opcin Usar el folleto actual esta seleccionada

Figura 15. Escoger la opcin Actualiza un nuevo folleto muestra el control FileUpload Guardar el archivo folleto y actualizar la columna BrochurePath Cuando es presionado el botn Update del GridView, se genera el evento RowUpdating. El comando Update del ObjectDataSource es invocado y luego se genera el evento RowUpdated del GridView. Al igual que en el flujo de trabajo de eliminacin, necesitamos crear controladores de eventos para ambos eventos. En el controlador de evento RowUpdating, necesitamos determinar que accin tomar basados en el SelectedValue del RadioButtonList BrochureOptions: Si el SelectedValue es 1, deseamos continuar usando la misma definicin de BrochurePath. Por lo tanto necesitamos establecer el parmetro brochurePath del ObjectDataSource en el valor existente del BrochurePath del registro que est siendo actualizado. El parmetro brochurePath del ObjectDataSource puede ser definido usando e.NewValues[brochurePath]=value. Si el SelectedValue es 2, deseamos establecer el BrochurePath del registro en Null. Para realizar esto establecemos el parmetro brochurePath del ObjectDataSource en Nothing, que genera un valor nulo de base de datos que est siendo usado en la sentencia UPDATE. Si existe un archivo de folleto que est siendo eliminado, necesitamos eliminar el archivo existente. Sin embargo, solamente deseamos hacer esto si la actualizacin se completa sin generar ninguna excepcin. Si el SelectedValue es 3, entonces deseamos asegurarnos que el usuario ha cargado un archivo PDF y luego guardarlo en el sistema de archivos y actualizar el valor de la columna BrochurePath. Por otra parte, si existe un archivo de folleto que est siendo remplazado, necesitamos eliminar el archivo anterior. Sin embargo solo deseamos hacer esto si la actualizacin se completa sin generar ninguna excepcin. Estos pasos necesarios para realizar cuando el SelectedValue del RadioButtonList es 3, son virtualmente idnticos a los usados en el controlador de evento ItemInserting del DetailsView. Este controlador de evento es ejecutado cuando un nuevo registro de categora es agregado desde el control DetailsView en el tutorial anterior. Por lo tanto

nos corresponde agrupar esta funcionalidad en mtodos separados. Especficamente movemos la funcionalidad en comn dentro de dos mtodos: ProcessBrochureUpload(FileUpload, out bool) acepta como entrada una instancia del control FileUpload y un valor de salida Booleano que especifica si la opcin de edicin o eliminacin ser procesada o si ser cancelada debido a algn error de validacin. Este mtodo devuelve la ruta del archivo guardado o null si ningn archivo fue guardado. DeleteRememberBrochurePath elimina el archivo especificado por la ruta en la variable de pagina deletedCategoryPdfPath si deletedCategoryPdfPath es null El cdigo de estos dos controladores es el siguiente. Note la similitud entre ProcessBrochureUpload y el controlador de evento ItemInserting del DetailsView del tutorial anterior. En este tutorial hemos actualizado los controladores de evento del DetailsView para que use estos nuevos mtodos. Descargue el cdigo asociado con este tutorial para ver las modificaciones a los controladores de eventos del DetailsView.
Private Function ProcessBrochureUpload _ (BrochureUpload As FileUpload, CancelOperation As Boolean) As String CancelOperation = False ' by default, do not cancel operation If BrochureUpload.HasFile Then ' Make sure that a PDF has been uploaded If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _ ".pdf", True) <> 0 Then UploadWarning.Text = _ "Only PDF documents may be used for a category's brochure." UploadWarning.Visible = True CancelOperation = True Return Nothing End If Const BrochureDirectory As String = "~/Brochures/" Dim brochurePath As String = BrochureDirectory + BrochureUpload.FileName Dim fileNameWithoutExtension As String = _ System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName) Dim iteration As Integer = 1 While System.IO.File.Exists(Server.MapPath(brochurePath)) brochurePath = String.Concat(BrochureDirectory, _

fileNameWithoutExtension, "-", iteration, ".pdf") iteration += 1 End While ' Save the file to disk and set the value of the brochurePath parameter BrochureUpload.SaveAs(Server.MapPath(brochurePath)) Return brochurePath Else ' No file uploaded Return Nothing End If End Function Private Sub DeleteRememberedBrochurePath() ' Is there a file to delete? If deletedCategorysPdfPath IsNot Nothing Then System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath)) End If End Sub

Los controladores de eventos RowUpdating y RowUpdated usan los mtodos ProcessBrochureUpload y DeleteRememberBrochurePath, como muestra el siguiente cdigo:
Protected Sub Categories_RowUpdating _ (sender As Object, e As GridViewUpdateEventArgs) _ Handles Categories.RowUpdating ' Reference the RadioButtonList Dim BrochureOptions As RadioButtonList = _ CType(Categories.Rows(e.RowIndex).FindControl("BrochureOptions"), _ RadioButtonList) ' Get BrochurePath information about the record being updated Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID")) Dim categoryAPI As New CategoriesBLL() Dim categoriesData As Northwind.CategoriesDataTable = _ categoryAPI.GetCategoryByCategoryID(categoryID) Dim category As Northwind.CategoriesRow = categoriesData(0) If BrochureOptions.SelectedValue = "1" Then ' Use current value for BrochurePath If category.IsBrochurePathNull() Then e.NewValues("brochurePath") = Nothing

Else e.NewValues("brochurePath") = category.BrochurePath End If ElseIf BrochureOptions.SelectedValue = "2" Then ' Remove the current brochure (set it to NULL in the database) e.NewValues("brochurePath") = Nothing ElseIf BrochureOptions.SelectedValue = "3" Then ' Reference the BrochurePath FileUpload control Dim BrochureUpload As FileUpload = _ CType(categories.Rows(e.RowIndex).FindControl("BrochureUpload"), _ FileUpload) ' Process the BrochureUpload Dim cancelOperation As Boolean = False e.NewValues("brochurePath") = _ ProcessBrochureUpload(BrochureUpload, cancelOperation) e.Cancel = cancelOperation Else ' Unknown value! Throw New ApplicationException( _ String.Format("Invalid BrochureOptions value, {0}", _ BrochureOptions.SelectedValue)) End If If BrochureOptions.SelectedValue = "2" OrElse _ BrochureOptions.SelectedValue = "3" Then ' "Remember" that we need to delete the old PDF file If (category.IsBrochurePathNull()) Then deletedCategorysPdfPath = Nothing Else deletedCategorysPdfPath = category.BrochurePath End If End If End Sub Protected Sub Categories_RowUpdated _ (sender As Object, e As GridViewUpdatedEventArgs) _ Handles Categories.RowUpdated ' If there were no problems and we updated the PDF file, ' then delete the existing one If e.Exception Is Nothing Then DeleteRememberedBrochurePath() End If

End Sub

Note como el controlador de evento RowUpdating usa una serie de sentencias condicionales para realizar la accin apropiada basado en el valor de la propiedad SelectedValue del RadioButtonList BrochureOptions. Con este cdigo en su lugar, podemos editar una categora y hacer que use su folleto actual, no use folleto o actualice a uno nuevo. Sigamos y probemos esto. Establezca puntos de ruptura en los controladores de eventos RowUpdating y RowUpdated para ver el sentido del flujo de trabajo. Paso 7. Actualizar una nueva Imagen La interfaz de edicin del ImageField Picture presenta un TextBox poblado con el valor de su propiedad DataImageUrlField. Durante el flujo de trabajo de la edicin, el GridView pasa un parmetro al ObjectDataSource con el nombre del parmetro que es valor de la propiedad DataImageUrlField y el valor del parmetro es decir el valor ingresado en el TextBox de la interfaz de edicin. Este comportamiento es adecuado cuando la imagen es guardada como un archivo en el sistema de archivos y el DataImageUrlField contiene la ruta completa de la imagen. Con tales circunstancias, la interfaz de edicin muestra la URL de la imagen en un TextBox, que el usuario puede cambiar y guardar de nuevo en la base de datos. Una vez aceptado, esta interfaz por defecto no permite al usuario cargar una nueva imagen, pero le permite cambiar la URL de la imagen del valor actual a otro. Sin embargo, para este tutorial la interfaz de edicin del ImageField por defecto no lo satisface porque los datos binarios Picture estn siendo almacenados directamente en la base de datos y la propiedad DataImageUrlField solo almacena el CategoryID. Para un mejor entendimiento de lo que sucede en nuestro tutorial cuando un usuario edita una fila con un ImageField, considere el siguiente ejemplo: un usuario edita una fila con CategoryID de 10, causando que el ImageField Picture se presente como un TextBox con el valor de 10. Imagine que el usuario cambie el valor en el TextBox a 50 y presione el botn Update. Se genera una devolucin de datos y el GridView inicialmente crea un parmetro llamado CategoryID con el valor de 50. Sin embargo antes que el GridView envi este parmetro (y los parmetros CategoryName y Description), este lo agrega en los valores de la coleccin DataKeys. Por lo tanto este sobrescribe el parmetro CategoryID con el valor CategoryID de la fila actual subyacente, es decir 10.

En concreto la interfaz de edicin no tiene ningn efecto sobre el flujo de trabajo de edicin para este tutorial porque los nombres de la propiedad DataImageUrlField del ImageField y el valor DataKey del grid son los mismos. Aunque el ImageField hace que sea fcil visualizar una imagen basado en los datos de la base de datos, no deseamos proporcionar un TextBox en la interfaz de edicin. En su lugar deseamos ofrecer un control FileUpload que el usuario final pueda usar para cambiar la imagen de la categora. A diferencia del valor BrochurePath, para estos tutoriales hemos decidido hacer que cada categora requiera una imagen. Por lo tanto no necesitamos permitir que el usuario indique que no hay una imagen asociada, pero el usuario puede cargar una nueva imagen o dejar la imagen actual como esta. Para personalizar la interfaz de edicin del ImageField, necesitamos convertirlo en un TemplateField. Desde la etiqueta inteligente del GridView, presione el enlace Editar Columnas y presione el enlace Convertir este campo en un TemplateField

Figura 16. Convertir el ImageField en un TemplateField Convertir el ImageField en un TemplateField en este administrador genera un TemplateField con dos plantillas. Como muestra la siguiente sintaxis declarativa, el ItemTemplate contiene un control web Image cuya propiedad ImageUrl es asignada usando sintaxis de enlace de datos basado en las propiedad DataImageUrlField y DataImageUrlFormatString. El EditItemTemplate contiene un TextBox cuya propiedad Text esta enlazada al valor especificado por la propiedad DataImageUrlField.

<asp:TemplateField> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Eval("CategoryID") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Image ID="Image1" runat="server" ImageUrl='<%# Eval("CategoryID", "DisplayCategoryPicture.aspx?CategoryID={0}") %>' /> </ItemTemplate> </asp:TemplateField>

Necesitamos actualizar el EditItemTemplate para que utilice un control FileUpload. Desde la etiqueta inteligente del GridView presione el enlace Editar Templates y seleccione el EditItemTemplate del TemplateField Picture en la lista desplegable. En la plantilla deber ver un TextBox, remuvalo. Luego arrastre un control FileUpload desde la caja de herramientas hasta la plantilla, estableciendo su propiedad ID en PictureUpload. Tambin agregue el texto Para cambiar la imagen de la categora, especifique una nueva imagen. Para conservar la misma imagen, deje el campo vacio

Figura 17. Agregue un control FileUpload al EditItemTemplate Despus de personalizar la interfaz de edicin, veamos nuestro progreso en un navegador. Cuando visualizamos una fila en modo de solo-lectura, la imagen de la categora es mostrada como antes, pero al presionar el botn Edit se presenta la columna imagen como un texto con un control FileUpload.

Figura 18. La interfaz de edicin incluye un control FileUpload Recuerde que el ObjectDataSource est configurado para llamar al mtodo

UpdateCategory de la clase CategoriesBLL que acepta como entrada los datos binarios de la imagen como un Byte.array. Sin embargo si este array es Nothing, se llama a la recarga alterna UpdateCategory, que usa la sentencia SQL UPDATE que no modifica la columna Picture, dejando intacta la imagen actual de la categora. Por lo tanto en el controlador de evento RowUpdating del GridView necesitamos referenciar mediante programacin el control FileUpload y determinar si se cargo algn archivo. Si ninguno fue cargado, no deseamos especificar un valor para el parmetro de entrada picture. Por otra parte si un archivo fue cargado en el control FileUpload, deseamos asegurarnos que es un archivo JPG. Si es as, podemos enviar los contenidos binarios al ObjectDataSource por medio del parmetro picture. Al igual que con el cdigo usado en el paso 6, mucho del cdigo necesario aqu ya existe en el controlador de evento ItemInserting del DetailsView. Por lo tanto, agrupamos la funcionalidad en comn dentro de un nuevo mtodo, ValidPictureUpload y actualizamos el controlador de evento ItemInserting para que utilice este mtodo. Agregue el siguiente cdigo al inicio del controlador de evento RowUpdating del GridView. Es importante que este cdigo venga antes del cdigo que guarda el archivo folleto ya que no deseamos guardar el folleto al sistema de archivos del servidor web si se ha cargado un archivo de imagen no valido.
' Reference the PictureUpload FileUpload Dim PictureUpload As FileUpload = _ CType(categories.Rows(e.RowIndex).FindControl("PictureUpload"), _ FileUpload) If PictureUpload.HasFile Then ' Make sure the picture upload is valid

If ValidPictureUpload(PictureUpload) Then e.NewValues("picture") = PictureUpload.FileBytes Else ' Invalid file upload, cancel update and exit event handler e.Cancel = True Exit Sub End If End If

El mtodo ValidPictureUpload(FileUpload) toma el control FileUpload como su nico parmetro de entrada y verifica la extensin del archivo para asegurarse que el archivo cargado es un JPG; este solamente es llamado si el archivo de imagen es cargado. Si no se ha cargado ningn archivo, entonces el parmetro Picture no est definido y por lo tanto usamos su valor por defecto de Nothing. Si una imagen fue cargada y ValidPictureUpload devuelve True, el parmetro Image es asignado a los datos binarios de la imagen cargada, si el mtodo devuelve false, el flujo de trabajo de actualizacin es cancelado y salimos del controlador de evento. El cdigo del mtodo ValidPictureUpload(FileUpload), que fue reagrupado desde el controlador de evento ItemInserting, es el siguiente:
Private Function ValidPictureUpload(ByVal PictureUpload As FileUpload) As Boolean ' Make sure that a JPG has been uploaded If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _ ".jpg", True) <> 0 AndAlso _ String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _ ".jpeg", True) <> 0 Then UploadWarning.Text = _ "Only JPG documents may be used for a category's picture." UploadWarning.Visible = True Return False Else Return True End If End Function

Paso 8. Remplazar las imgenes originales de las categoras con JPGs

Recuerde que las ocho imgenes de las categoras originales son archivos de mapa de bits unidos en un encabezado OLE. Ahora que hemos agregado la capacidad de editar una imagen de un registro existente, tmese un momento para remplazar estos mapas de bits con JPGs. Si desea continuar usando las imgenes actuales de las categoras, puede convertirlas a JPGs realizando los siguientes pasos: 1. Guarde las imgenes de mapa de bits en su disco duro. Visite la pgina UpdatingAndDeleting.aspx en su navegador y para cada una de las primeras ocho categoras, haga clic derecho sobre la imagen y seleccione guardar la imagen. 2. Abra la pagina en su editor de imagen. Puede usar por ejemplo Microsoft Paint 3. Guarde los mapas de bit como una imagen JPG 4. Actualice la imagen de la categora a travs de la interfaz de edicin, usando el archivo JPG. Despus de editar una categora y actualizar la imagen JPG, la imagen no se presentara en el navegador porque la pgina DisplayWithPicture.aspx esta omitiendo los primeros 78 bytes de las imgenes de las primeras 8 categoras. Arreglemos esto removiendo el cdigo que realiza la omisin del encabezado OLE. Despus de hacer esto, el controlador de evento Page_Load de DisplayCategoryPicture.aspx solo deber tener el siguiente cdigo:
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load Dim categoryID As Integer = _ Convert.ToInt32(Request.QueryString("CategoryID")) ' Get information about the specified category Dim categoryAPI As New CategoriesBLL() Dim categories As Northwind.CategoriesDataTable = _ categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID) Dim category As Northwind.CategoriesRow = categories(0) ' For new categories, images are JPGs... ' Output HTTP headers providing information about the binary data Response.ContentType = "image/jpeg" ' Output the binary data Response.BinaryWrite(category.Picture) End Sub

Nota: Las interfaces de insercin y eliminacin de la pgina UpdatingAndDeleting.aspx podran usar un poco mas de trabajo. Los BoundFields CategoryName y Description en el DetailsView y FormView deben ser convertidos en TemplateFields. Como el CategoryName no permite valores null, debe agregarse un RequiredFieldValidator y el TextBox Description probablemente ser convertido en un TextBox multilinea. Resumen Este tutorial completa nuestro vistazo sobre el trabajo con datos binarios. En este tutorial y los tres anteriores, vimos como los datos binarios pueden ser almacenados en el sistema de archivos o directamente dentro de la base de datos. Un usuario proporciona datos al sistema seleccionando un archivo desde su disco duro y cargndolo al servidor web, donde este puede ser guardado en el sistema de archivos o ingresarlo dentro de la base de datos. ASP.Net 2.0 incluye un control FileUpload que hace que proporcionar una interfaz sea tan fcil como arrastrar y desplegar. Sin embargo como notamos en el tutorial Carga de Archivos, el control FileUpload solamente es adecuado para cargas de archivo relativamente pequeas, idealmente que no excedan un megabyte. Tambin exploramos como asociar datos cargados con el modelo de datos subyacente, as como editar y eliminar los datos binarios de los registros existentes. Nuestro siguiente conjunto de tutoriales exploran varias tcnicas de almacenamiento en cache. Almacenar en cache proporcionan un medio para mejorar el rendimiento general de una aplicacin que toma los resultados de operaciones muy grandes y los almacena en una ubicacin a la que puede accederse rpidamente.

ALMACENAMIENTO DE DATOS EN CACHE

55.

ALMACENAMIENTO

DE

DATOS

EN

CACHE

CON

EL

OBJECTDATASOURCE
El almacenamiento en cache puede significar la diferencia entre una aplicacin web lenta y una veloz. Este tutorial es el primero de cuatro que tomaran en detalle el almacenamiento en cache. Aprenderemos conceptos claves de almacenamiento en cache y como aplicar el almacenamiento en cache a la capa de presentacin utilizando el control ObjectDataSource. Introduccion En la ciencia de las computadoras, almacenar en cache es el proceso de tomar datos o informacin que es muy costoso obtener y copiarla en una ubicacin que es de rpido acceso. Para las aplicaciones que manejan datos, las consultas mas largas y complejas comnmente son las que consumen mayor tiempo de ejecucin de la aplicacin. Asi el rendimiento de la aplicacin, a menudo puede ser mejorado almacenando los resultados de la costosa consulta de la base de datos en la memoria de la aplicacin. ASP.Net ofece una variedad de opciones de almacenamiento en cache. Una pagina web completa o el marcado presentado por un control de usuario puede ser almacenado en cache por medio de output caching. Los controles ObjectDataSource y SqlDataSource proporcionan capacidades de alamcenamiento de cache, permitiendo que los datos sean almacenados en cache a nivel de control. Y los data cache proporcionan una API de almacenamiento completa que permita a los desarrolladores de pagina almacenar mediante programacin objetos. En este tutorial y el los prximos tres examinaremos el uso de las funciones de alamcenamiento en cache del ObjectDataSource asi como los datos almacenados. Tambien en el inicio exploraremos como almacenar los datos en el lado de la aplicacin y como mantener los datos almacenados

56. ALMACENAMIENTO DE DATOS EN CACHE EN LA ARQUITECTURA

57. ALMACENAMIENTO DE DATOS EN CACHE AL INICIO DE LA APLICACIN

58. UTILIZACIN DE SQL PARA DEPENDENCIAS DE CACHE

MANEJO DE MAPAS DEL SITIO DE BASE DE DATOS

59. CONSTRUCCIN DE UN PROVEEDOR PERSONALIZADO DE MAPA DE SITIO QUE MANEJE LA BASE DE DATOS

TRABAJAR CON LOTES DE DATOS

60. ENVOLVER MODIFICACIONES DE BASE DE DATOS EN UNA TRANSACIN

61. ACTUALIZACIN DE LOTES

62. ELIMINACIN POR LOTES

63. INSERCIN POR LOTES

ESCENARIOS AVANZADOS DE ACCESO A DATOS

64.

CREACIN

DE

PROCEDIMIENTOS

ALMACENADOS

PARA

LOS

TABLEADAPTERS DEL DATASET TIPIADO


En los tutoriales anteriores hemos creado en nuestro cdigo sentencias SQL y luego las pasamos a la base de datos para que sean ejecutadas. Un enfoque alternativo es el uso de procedimientos almacenados, donde las sentencias SQL estn predefinidas en la base de datos. En este tutorial aprenderemos como hacer que el asistente del TableAdapter genere por nosotros nuevos procedimientos almacenados. Introduccin La capa de acceso a datos (DAL) de estos tutoriales utiliza DataSets tipiados. Como se discuti en el tutorial Creacin de una capa de acceso a datos los DataSets tipiados consisten de DataTables y TableAdapters fuertemente tipiados. Los DataTables representan las entidades lgicas en el sistema, mientras que los TableAdapters representan la interfaz con la base de datos subyacente para realizar el trabajo de acceso a datos. Esto incluye llenar los DataTables con datos, ejecutar consultas que devuelven valores escalares, e insertar, actualizar y eliminar registros de la base de datos. Los comandos SQL ejecutados por los TableAdapters pueden ser sentencias SQL adhoc, tales como SELECT columnList FROM TableName, o procedimientos almacenados. En nuestra arquitectura los TableAdapters utilizan sentencias SQL ad-hoc. Sin embargo, muchos desarrolladores y administradores de bases de datos, prefieren los procedimientos almacenados por sobre las sentencias SQL ad-hoc, esto por razones de seguridad, mantenimiento y actualizacin. Otros prefieren arduamente las instrucciones SQL ad-hoc por su flexibilidad. En mi trabajo, estoy ms a favor de los procedimientos almacenados que de las sentencias SQL ad-hoc, pero opte por utilizar las sentencias SQL ad-hoc para simplificar los tutoriales anteriores. Cuando definimos un TableAdapter o agregamos nuevos mtodos, el asistente del TableAdapter hace que sea fcil crear nuevos procedimientos almacenados, utilizar los procedimientos almacenados existentes as como crear sentencias SQL ad-hoc. En este tutorial examinaremos cmo hacer que el asistente del TableAdapter genere automticamente los procedimientos almacenados. En el siguiente tutorial veremos cmo configurar los mtodos del TableAdapter para que use los procedimientos almacenados existentes o creados manualmente.

Nota: Vea la entrada Don t Use Stored Procedures Yet? del blog de Rob Howard y la entrada Stored Procedures are Bad, M Kay? Para un debate ms vivo sobre los pros y contras de los procedimientos almacenados y sentencias SQL ad-hoc. Conceptos bsicos de los procedimientos almacenados Las funciones son una construccin comn para todos los lenguajes de programacin. Una funcin es una coleccin de instrucciones que se ejecutan cuando se invoca la funcin. Las funciones pueden aceptar parmetros de entrada y opcionalmente pueden devolver un valor. Los procedimientos almacenados son construcciones de bases de datos que comparten muchas similitudes con las funciones en los lenguajes de programacin. Un procedimiento almacenado se compone de un conjunto de instrucciones T-SQL que son ejecutadas cuando se llama al procedimiento almacenado. Un procedimiento almacenado puede aceptar ninguno o muchos parmetros de entradas y puede devolver valores escalares, parmetros de salida o ms comnmente, los conjuntos de resultados de las sentencias SELECT. Nota: Los procedimientos almacenados son a menudo conocidos como sprocs o SP. Los procedimientos almacenados se crean utilizando la sentencia T-SQL CREATE PROCEDURE. Por ejemplo la siguiente secuencia de comandos T-SQL crea un procedimiento almacenado llamado GetProductsByCategoryID, que toma un solo parmetro de entrada llamado @CategoryID y devuelve los campos de las columnas ProductID, ProductName, UnitPrice y Discontinued de la tabla Products que tienen asignado el valor de CategoryID:
CREATE PROCEDURE GetProductsByCategoryID ( @CategoryID int ) AS SELECT ProductID, ProductName, UnitPrice, Discontinued FROM Products WHERE CategoryID = @CategoryID

Una vez que el procedimiento almacenado ha sido creado, este puede ser llamado usando la siguiente sintaxis:

EXEC GetProductsByCategory categoryID

Nota:

En

el

siguiente

tutorial

examinaremos

la

creacin

de

procedimientos

almacenados por medio del IDE de Visual Studio. Sin embargo, para este tutorial dejaremos que el asistente del TableAdapter genere automticamente por nosotros los procedimientos almacenados. Adems de simplemente devolver datos, los procedimientos almacenados a menudo se utilizan para realizar varios comandos de la base de datos dentro del alcance de una sola transaccin. Por ejemplo, un procedimiento almacenado llamado DeleteCategory, podra tomar un parmetro @CategoryID y realizar dos sentencias DELETE: en primer lugar, una para eliminar los productos relacionados y una segunda para eliminar la categora especificada. Diversas sentencias dentro de un procedimiento almacenado no se ajustan automticamente en una transaccin. Las sentencias T-SQL adicionales deben ser emitidas para asegurar que los diferentes comandos del procedimiento almacenado sean tratados como una operacin atmica. En un prximo tutorial veremos cmo envolver los comandos de un procedimiento almacenado dentro del alcance de una sola transaccin. Cuando utilizamos procedimientos almacenados dentro de una arquitectura, los mtodos de la capa de acceso a datos invocan un procedimiento almacenado en particular, en lugar de emitir una sentencia SQL ad-hoc. Esto centraliza la localizacin de las sentencias SQL ejecutadas (en la base de datos) en lugar de tener que definirlas dentro de la arquitectura de nuestra aplicacin. Esta centralizacin sin duda, hace que sea ms fcil encontrar, analizar y ajustar las consultas y proporciona una imagen mucho ms clara en cuanto a donde y como se est utilizando la base de datos. Paso 1. Creacin de pginas web de escenarios avanzados de acceso a datos Antes de iniciar nuestra discusin sobre la creacin de una DAL que usa procedimientos almacenados, primero nos tomaremos un momento para crear las pginas Asp.Net que necesitaremos para este y los siguientes tutoriales del proyecto de nuestro sitio web. Comencemos creando una nueva carpeta llamada AdvancedDAL. Luego agregue las siguientes pginas Asp.Net en esa carpeta, asegurndose de asociar cada pgina con la pgina maestra Site.master:

Default.aspx NewSprocs.aspx ExistingSprocs.aspx JOINS.aspx AddingColumns.aspx ComputedColumns.aspx EncryptingConfigSections.aspx ManagedFunctionsAndSprocs.aspx

Figura 1. Agregar las pginas Asp.Net para los tutoriales de escenarios avanzados de acceso a datos Al igual que en las otras carpetas, en la carpeta AdvancedDAL, Default.aspx mostrar una lista de los tutoriales de esa seccin. Recordemos que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Por lo tanto, agregue este control de usuario a Default.aspx, arrastrndolo desde el explorador de soluciones hasta la vista diseo de la pgina.

Figura 2. Agregue el control de usuario SectionLevelTutorialListing.aspx a Default.aspx Por ltimo agregue estas pginas como entradas en el archivo Web.sitemap. En concreto agregue el siguiente marcado despus del <SiteMapNode> del trabajo con lotes de datos:
<siteMapNode url="/AdvancedDAL/Default.aspx" title="Advanced DAL Scenarios" description="Explore a number of advanced Data Access Layer scenarios."> <siteMapNode url="/AdvancedDAL/NewSprocs.aspx" title="Creating New Stored Procedures for TableAdapters" description="Learn how to have the TableAdapter wizard automatically create and use stored procedures." /> <siteMapNode url="/AdvancedDAL/ExistingSprocs.aspx" title="Using Existing Stored Procedures for TableAdapters" description="See how to plug existing stored procedures into a TableAdapter." /> <siteMapNode url="/AdvancedDAL/JOINs.aspx" title="Returning Data Using JOINs" description="Learn how to augment your DataTables to work with data returned from multiple tables via a JOIN query." /> <siteMapNode url="/AdvancedDAL/AddingColumns.aspx" title="Adding DataColumns to a DataTable" description="Master adding new columns to an existing DataTable." /> <siteMapNode url="/AdvancedDAL/ComputedColumns.aspx" title="Working with Computed Columns"

description="Explore how to work with computed columns when using Typed DataSets." /> <siteMapNode url="/AdvancedDAL/EncryptingConfigSections.aspx" title="Protected Connection Strings in Web.config" description="Protect your connection string information in Web.config using encryption." /> <siteMapNode url="/AdvancedDAL/ManagedFunctionsAndSprocs.aspx" title="Creating Managed SQL Functions and Stored Procedures" description="See how to create SQL functions and stored procedures using managed code." /> </siteMapNode>

Despus de actualizar el Web.Sitemap, tmese un momento para ver los tutoriales del sitio web a travs de un navegador. El men de la izquierda ahora incluye elementos para los tutoriales de escenarios avanzados de DAL.

Figura 3. El mapa del sitio ahora incluye las entradas para los tutoriales de escenarios avanzados de DAL Paso 2. Configuracin de un TableAdapter para crear nuevos procedimientos almacenados Para demostrar la creacin de una capa de acceso a datos que utiliza procedimientos almacenados en lugar de sentencias SQL ad-hoc crearemos un nuevo DataSet tipiado en la carpeta ~/App_Code/DAL llamado NorthwindWithSprocs.xsd. Como hemos ido paso a paso en el detalle de este proceso en los tutoriales anteriores, aqu procederemos rpidamente. Si estas atascado o necesitas seguir las instrucciones paso a paso de cmo crear y configurar un DataSet tipiado, refirase de nuevo al tutorial Creacin de una capa de acceso a datos. Agregue un nuevo DataSet al proyecto haciendo clic derecho sobre la carpeta DAL, elija agregar nuevo elemento y seleccione la plantilla DataSet, como se muestra en la figura 4.

Figura 4. Agregue un nuevo DataSet tipiado al proyecto llamado NorthwindWithSprocs.aspx Esto crear un nuevo DataSet tipiado, abra su diseador, cree un nuevo TableAdapter e inicie el asistente de configuracin del TableAdapter. El primer paso que nos pide el asistente de configuracin del TableAdapter es que seleccionemos la base de datos con la cual vamos a trabajar. La cadena de conexin de la base de datos Northwind debe aparecer en la lista desplegable. Seleccinela y haga clic en siguiente:

En la siguiente pantalla puede escoger la forma en que el TableAdapter debe acceder a la base de datos. En los tutoriales anteriores hemos elegido la primera opcin, Usar sentencias SQL. Para este tutorial seleccionamos la segunda opcin, Crear nuevos procedimientos almacenados y hacemos clic en siguiente.

Figura 5. De la instruccin al TableAdapter para que cree nuevos procedimientos almacenados Al igual que con el uso de sentencias SQL ad-hoc, en el siguiente paso se nos pide suministrar la sentencia SELECT para la consulta principal del TableAdapter. Pero en lugar de utilizar la sentencia SQL ingresada aqu para realizar una consulta ad-hoc directamente, el asistente del TableAdapter creara un procedimiento almacenado que contiene esta consulta SELECT. Utilice la siguiente consulta SELECT para este TableAdapter:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM Products

Figura 6. Introduzca la sentencia SELECT Nota: La consulta anterior en el difiere DataSet ligeramente tipiado de la consulta principal que del el

ProductsTableAdapter

Northwind.

Recordemos

ProductsTableAdapter en el DataSet tipiado Northwind incluye dos subconsultas correlacionadas para llevar el nombre de la categora y el nombre de la empresa para cada categora y proveedor de los productos. En el prximo tutorial Actualizacin del TableAdapter utilizando Joins veremos la adicin de estos datos relacionados con ese TableAdapter. Tmese un momento para presionar el botn de opciones avanzadas. Desde aqu se puede especificar si el asistente tambin debe generar las declaraciones insert, update y delete para el TableAdapter, si desea utilizar concurrencia optimista y si la tabla debe ser actualizada despus de las actualizaciones e inserciones. La opcin generar sentencias update, delete e insert esta seleccionada por defecto, djela seleccionada. Para este tutorial, dejaremos la opcin de usar control de concurrencia optimista sin seleccionar. Cuando hacemos que los procedimientos almacenados sean creados automticamente por el asistente del TableAdapter, la opcin actualizar la tabla de datos se omite. Independientemente de si esta opcin est marcada o no, los procedimientos almacenados de insercin y actualizacin, recuperan solamente el registro recin insertado o recin actualizado. Como veremos en el paso 3.

Figura 7. Dejar la opcin generar sentencias insert, update y delete seleccionada Nota: Si la opcin de usar concurrencia optimista est marcada, el asistente aade condiciones adicionales a la clausula WHERE que evita que los campos se actualicen si hubieron cambios en los otros campos. Refirase de nuevo al tutorial de Aplicacin de simultaneidad optimista para obtener ms informacin sobre el uso del control de concurrencia optimista predefinido del TableAdapter. Despus de ingresar la consulta SELECT y confirmar que la opcin generar sentencias Insert, Update y Delete esta activada, presione siguiente. La pantalla siguiente que se muestra en la figura 8, solicita los nombres de los procedimientos almacenados que el asistente creara para seleccionar, insertar, actualizar y eliminar datos. Cambie los nombres de estos procedimientos almacenados por Products_Select, Products_Insert, Products_Update y Products_Delete.

Figura 8. Cambie el nombre de los procedimientos almacenados.

Para ver el T-SQL que el asistente del TableAdapter utilizara para crear los cuatro procedimientos almacenados, haga clic en el botn vista previa de SQL Script. En el cuadro de dialogo Vista previa del script SQL usted puede guardar el script en un archivo o copiarlo al portapapeles.

Figura 9. Vista Previa de la secuencia de comandos SQL para generar los procedimientos almacenados Despus de nombrar los procedimientos almacenados, haga clic en siguiente para nombrar los mtodos correspondientes del TableAdapter. Al igual que cuando se utilizan las sentencias SQL ad-hoc, podemos crear mtodos para llenar un DataTable existente o devolver uno nuevo. Tambin podemos especificar si el TableAdapter debe incluir el modelo directo DB para insertar, actualizar y eliminar registros. Deje las tres casillas de verificacin marcadas, pero cambie el nombre del mtodo de devolucin de un DataTable a GetProducts (como se muestra en la figura 10).

Figura 10. Nombre los mtodos Fill y GetProducts Haga clic en siguiente para ver un resumen de los pasos que el asistente realizara. Complete el asistente presionando el botn finalizar. Una vez el asistente este completo, regresaremos al diseador del DataSet, el cual ahora incluye el ProductsDataTable.

Figura 11. El diseador del DataSet muestra el ProductsDataTable recin agregado

Paso 3. Examinar los procedimientos almacenados creados recientemente El asistente del TableAdapter utilizado en el paso 2, crea automticamente los procedimientos almacenados para seleccionar, insertar, actualizar y eliminar datos. Estos procedimientos almacenados pueden ser vistos o modificados a travs de Visual Studio yendo al explorador de servidores y profundizando en la carpeta de procedimientos almacenados de la base de datos. En la figura 12, se muestra, que la base de datos Northwind contiene cuatro nuevos procedimientos almacenados: Products_Delete, Products_Update, Products_Insert y Products_Select.

Figura 12. Los cuatro procedimientos almacenados creados en el paso 2 se pueden encontrar en la carpeta de procedimientos almacenados de la base de datos Nota: Si usted no ve el explorador de servidores, vaya al men Ver y seleccione la opcin explorador de servidores. Si no ve los procedimientos almacenados relacionados con los productos que se crearon en el paso 2, intente haciendo clic derecho sobre la carpeta procedimientos almacenados y elija actualizar. Para ver o modificar un procedimiento almacenado, haga doble clic en su nombre en el explorador de servidores o haga clic derecho sobre el nombre del procedimiento almacenado y seleccione abrir. La figura 13 muestra el procedimiento almacenado Products_Delete cuando se abre.

Figura 13. Los procedimientos almacenados se pueden abrir y modificar en Visual Studio Los contenidos tanto de los procedimientos almacenados Products_Delete y

Products_Select son bastante sencillos. Por otro lado los procedimientos almacenados Products_Insert y Products_Update advierten una inspeccin ms cercana ya que ambos realizan una sentencia SELECT despus de sus sentencias INSERT y UPDATE. Por ejemplo el siguiente SQL hace el procedimiento almacenado Products_Insert:
ALTER PROCEDURE dbo.Products_Insert ( @ProductName nvarchar(40), @SupplierID int, @CategoryID int, @QuantityPerUnit nvarchar(20), @UnitPrice money, @UnitsInStock smallint, @UnitsOnOrder smallint, @ReorderLevel smallint, @Discontinued bit ) AS SET NOCOUNT OFF; INSERT INTO [Products] ([ProductName], [SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) VALUES SELECT (@ProductName, ProductID, @SupplierID, @CategoryID, @QuantityPerUnit, QuantityPerUnit, @UnitPrice, UnitPrice, @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued); ProductName, SupplierID, CategoryID, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM Products WHERE (ProductID = SCOPE_IDENTITY())

El procedimiento almacenado acepta como parmetros de entrada las columnas Products que fueron devueltas por la consulta SELECT especificada en el asistente del TableAdapter y estos valores son usados en la sentencia INSERT. Despus de la sentencia INSERT, se utiliza una consulta SELECT que es usada para devolver los valores de las columnas de Products (incluido el ProductID) del registro recin agregado. Esta capacidad de actualizacin es til cuando se agrega un nuevo registro utilizando el modelo de actualizacin por lotes, ya que actualiza automticamente las propiedades ProductID de las instancias ProductsRow recin agregadas con los valores de incremento automticos asignados por la base de datos. El siguiente cdigo ilustra esta funcin. Este contiene un ProductsTableAdapter y un ProductsDataTable creados para el DataSet tipiado NorthwindWithSprocs. Un nuevo producto se agrega a la base de datos mediante la creacin de una instancia ProductsRow, suministrando sus valores y llamando al mtodo Update del TableAdapter, pasndolo al ProductsDataTable. Internamente, el mtodo Update del TableAdapter enumera las instancias ProductsRow pasndolas al ProductsDataTable (en este caso, solo hay una, la que acabamos de agregar) y realiza los comandos de insercin, actualizacin o eliminacin apropiados. En este caso el procedimiento almacenado Products_Insert es ejecutado, lo que agrega un nuevo registro a la tabla Products y devuelve los detalles del registro recin agregado. El valor ProductID de la instancia ProductsRow es actualizado. Despus que el mtodo Update se ha completado, podemos acceder al valor del ProductID del registro recin agregado por medio de la propiedad ProductID del ProductsRow.
' Create the ProductsTableAdapter and ProductsDataTable Dim productsAPI As New NorthwindWithSprocsTableAdapters.ProductsTableAdapter Dim products As New NorthwindWithSprocs.ProductsDataTable ' Create a new ProductsRow instance and set its properties Dim product As NorthwindWithSprocs.ProductsRow = products.NewProductsRow() product. ProductName = "New Product" product.CategoryID = 1 ' Beverages product.Discontinued = False ' Add the ProductsRow instance to the DataTable products.AddProductsRow(product) ' Update the DataTable using the Batch Update pattern productsAPI.Update(products)

' At this point, we can determine the value of the newly-added record's ProductID Dim newlyAddedProductIDValue as Integer = product.ProductID

El procedimiento almacenado Products_Update de manera similar incluye una sentencia SELECT despus de su sentencia UPDATE.
ALTER PROCEDURE dbo.Products_Update ( @ProductName nvarchar(40), @SupplierID int, @CategoryID int, @QuantityPerUnit nvarchar(20), @UnitPrice money, @UnitsInStock smallint, @UnitsOnOrder smallint, @ReorderLevel smallint, @Discontinued bit, @Original_ProductID int, @ProductID int ) AS SET NOCOUNT OFF; UPDATE [Products] SET [ProductName] = @ProductName, [SupplierID] = @SupplierID, [CategoryID] = @CategoryID, [QuantityPerUnit] = @QuantityPerUnit, [UnitPrice] = @UnitPrice, [UnitsInStock] = @UnitsInStock, [Discontinued] = @Discontinued WHERE (([ProductID] = @Original_ProductID)); SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM Products WHERE (ProductID = @ProductID) [UnitsOnOrder] = @UnitsOnOrder, [ReorderLevel] = @ReorderLevel,

Tenga en cuenta que este procedimiento contiene dos parmetros de entrada para el ProductID: @Original_ProductId y @ProductID. Esta funcionalidad se permite en los escenarios donde la clave principal puede ser cambiada. Por ejemplo en una base de datos empleado, cada registro de empleado puede utilizar el nmero de seguridad social del empleado como clave principal. Con el fin de cambiar el nmero de seguridad social de un empleado, tanto el nuevo nmero de seguridad social como el nmero original deben ser suministrados. Para la tabla Products esta funcionalidad no es necesaria, porque la columna ProductID es una columna IDENTITY y nunca debe ser cambiada. De hecho, la declaracin UPDATE en el procedimiento almacenado

Products_Update no incluye la columna ProductID en su lista de columnas. As, mientras @Original_ProductID se utiliza en la clausula WHERE de la sentencia UPDATE, este es superfluo para la tabla Products y podra ser remplazado por el parmetro @ProductID. Cuando modificamos los parmetros de un procedimiento almacenado es importante que los mtodos del TableAdapter que usen ese procedimiento almacenado tambin sean actualizados. Paso 4. Modificacin de los parmetros de un procedimiento almacenado y actualizacin del TableAdapter Como el parmetro @Original_ProductID es superfluo, removeremos este por completo del procedimiento almacenado Products_Update. Abra el procedimiento almacenado Products_Update, elimine el parmetro @Original_ProductID y en la clausula WHERE de la sentencia UPDATE, cambie el nombre del parmetro utilizado de @Original_ProductID a ProductID. Despus de realizar estos cambios, el T-SQL en el procedimiento almacenado debe tener el siguiente aspecto:
ALTER PROCEDURE dbo.Products_Update ( @ProductName nvarchar(40), @SupplierID int, @CategoryID int, @QuantityPerUnit nvarchar(20), @UnitPrice money, @UnitsInStock smallint, @UnitsOnOrder smallint, @ReorderLevel smallint, @Discontinued bit, @ProductID int ) AS SET NOCOUNT OFF; UPDATE [Products] = SET [ProductName] = @ProductName, = [SupplierID] = @SupplierID, = [CategoryID] = @CategoryID, [QuantityPerUnit] = @QuantityPerUnit, [UnitPrice] = @UnitPrice, [UnitsInStock] @UnitsInStock, [UnitsOnOrder] @UnitsOnOrder, [ReorderLevel] @ReorderLevel, [Discontinued] = @Discontinued WHERE (([ProductID] = @ProductID)); SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM Products WHERE (ProductID = @ProductID)

Para guardar estos cambios en la base de datos, haga clic en el icono guardar en la barra de herramientas o presione Ctrl+S. Hasta el momento el procedimiento almacenado Products_Update no espera un parmetro de entrada @Original_ProductId, pero el TableAdapter est configurado para pasarlo como un parmetro. Puede ver los parmetros que el TableAdapter enviara al procedimiento almacenado Products_Update seleccionando el TableAdapter en el diseador del DataSet, vaya a la ventana propiedades y haga clic en los puntos suspensivos en la coleccin parmetros del UpdateCommand. Esto nos lleva al cuadro de dialogo editor de la coleccin de parmetros que se muestra en la figura 14.

Figura 14. El editor de la coleccin de parmetros enumera los parmetros usados pasados al procedimiento almacenado Products_Update Puedes eliminar este parmetro de aqu, con solo seleccionar el parmetro @Original_ProductId de la lista de los miembros y hacer clic en el botn quitar. Como alternativa puede actualizar los parmetros utilizados por todos los mtodos, haga clic derecho sobre el TableAdapter en el diseador y seleccionando configurar. Esto abrir el asistente de configuracin del TableAdapter, mostrando los procedimientos almacenados usados para seleccionar, insertar, actualizar y eliminar, junto con los parmetros que los procedimientos almacenados esperan recibir. Si hace clic en la lista desplegable Update usted podr ver los parmetros de entrada que espera el procedimiento almacenado Products_Update que ahora ya no incluye @Original_ProductId (Ver Figura 15). Simplemente haga clic en finalizar para actualizar automticamente la coleccin de parmetros utilizados por el TableAdapter.

Figura 15. Usted puede utilizar alternativamente el asistente de configuracin del TableAdapter para actualizar la coleccin de parmetros de sus mtodos Paso 5. Agregar mtodos adicionales al TableAdapter Como se ilustra en el paso 2, cuando creamos un nuevo TableAdapter es fcil hacer que los correspondientes Para procedimientos esto, almacenados agregaremos sean un generados mtodo automticamente. Lo mismo ocurre cuando se aaden mtodos adicionales a un TableAdapter. ilustrar GetProductsByProductID(productID) al ProductsTableAdapter creado en el paso 2. Este mtodo tomara como parmetro de entrada un valor ProductID y devolver los detalles sobre el producto especificado. Para empezar haga clic derecho en el TableAdapter y elija Agregar consulta en el men contextual.

Figura 16. Agregar una nueva consulta al TableAdapter Esto iniciar el asistente para configuracin de consultas del TableAdapter, que primero solicitara como debe acceder el TableAdapter a la base de datos. Para crear un nuevo procedimiento almacenado, elija la opcin crear un nuevo procedimiento almacenado haga clic en siguiente.

Figura 17. Seleccione la opcin crear un nuevo procedimiento almacenado

La siguiente pantalla nos pide que indiquemos el tipo de consulta a ejecutar, si va devolver un conjunto de filas o un solo valor escalar, o va a realizar una sentencia INSERT, DELETE o UPDATE. Como el mtodo GetProductsByProductID(productID) devolver una fila, dejamos la opcin SELECT que devuelve la fila seleccionada y pulse siguiente.

Figura 18. Seleccione la opcin SELECT que devuelve filas La siguiente pantalla muestra la consulta principal del TableAdapter, que solo indica el nombre del procedimiento almacenado (dbo.Products_Select). Remplace el nombre del procedimiento almacenado con la siguiente declaracin SELECT, que devuelve todos los campos de un producto especificado:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice,

UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM Products WHERE ProductID = @ProductID

Figura 19. Remplace el nombre del procedimiento almacenado con una consulta SELECT La siguiente pantalla le pide el nombre del procedimiento almacenado que es creado. Escriba el nombre Products_SelectByProductID y haga clic en siguiente.

Figura 20. Nombre el nuevo procedimiento almacenado Products_SelectByProductID El ltimo paso del asistente nos permite cambiar los nombres de los mtodos generados, as como indicar si desea utilizar el enfoque de Llenar un DataTable o el enfoque de devolver un DataTable, o ambos. Para este mtodo dejaremos las dos opciones marcadas, pero cambiremos el nombre de los mtodos por FillByProductID y

GetProductByProductID. Haga clic en siguiente para ver un resumen de los pasos que el asistente realizara y luego haga clic en Finalizar para completar el asistente.

Figura 21. Cambie los nombres de los mtodos del TableAdapter por FillByProductID y GetProductByProductID Despus de completar el asistente, el TableAdapter tiene un nuevo mtodo disponible, GetProductByProductID(productID), que cuando es invocado ejecutara el procedimiento almacenado Products_SelectByProductID que acabamos de crear. Tmese un momento para ver este nuevo procedimiento almacenado desde el explorador de servidores profundizando en la carpeta procedimientos almacenados y abra Products_SelectByProductID (Si no lo ve, haga clic derecho en la carpeta procedimientos almacenados y seleccione actualizar). Tenga en cuenta que el procedimiento almacenado SelectByProductID toma @ProductID como un parmetro de entrada y ejecuta la sentencia SELECT que ingresamos en el asistente.
ALTER PROCEDURE dbo.Products_SelectByProductID ( @ProductID int ) AS SET NOCOUNT ON; SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM Products

WHERE ProductID = @ProductID

Paso 6. Crear una clase en la capa lgica de negocios A lo largo de esta serie de tutoriales nos hemos esforzado por mantener una arquitectura de capas en la cual la capa de presentacin realiza todas las llamadas a la capa lgica de negocios BLL. Con el fin de adherirse a esta decisin de diseo, primero tenemos que crear una clase BLL para el nuevo DataSet tipiado antes que podamos acceder a los datos de los productos desde la capa de presentacin. Cree un nuevo archivo de clase llamado ProductsBLLWithSprocs.vb en la carpeta ~/App_Code/BLL y agregue el siguiente cdigo:
Imports NorthwindWithSprocsTableAdapters <System.ComponentModel.DataObject()> _ Public Class ProductsBLLWithSprocs Private _productsAdapter As ProductsTableAdapter = Nothing Protected ReadOnly Property Adapter() As ProductsTableAdapter Get If _productsAdapter Is Nothing Then _productsAdapter = New ProductsTableAdapter() End If Return _productsAdapter End Get End Property <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, True)> _ Public Function GetProducts() As NorthwindWithSprocs.ProductsDataTable Return Adapter.GetProducts() End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductByProductID(ByVal productID As Integer) _ As NorthwindWithSprocs.ProductsDataTable Return Adapter.GetProductByProductID(productID) End Function <System.ComponentModel.DataObjectMethodAttribute _

(System.ComponentModel.DataObjectMethodType.Insert, True)> _ Public Function AddProduct _ (ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _ ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _ ByVal unitPrice As Nullable(Of Decimal), _ ByVal unitsInStock As Nullable(Of Short), _ ByVal unitsOnOrder As Nullable(Of Short), _ ByVal reorderLevel As Nullable(Of Short), _ ByVal discontinued As Boolean) _ As Boolean ' Create a new ProductRow instance Dim products As New NorthwindWithSprocs.ProductsDataTable() Dim product As NorthwindWithSprocs.ProductsRow = products.NewProductsRow() product.ProductName = productName If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull() Else product.QuantityPerUnit = quantityPerUnit End If If Not unitPrice.HasValue Then product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If If Not unitsOnOrder.HasValue Then product.SetUnitsOnOrderNull() Else product.UnitsOnOrder = unitsOnOrder.Value End If If Not reorderLevel.HasValue Then product.SetReorderLevelNull() Else product.ReorderLevel = reorderLevel.Value End If product.Discontinued = discontinued ' Add the new product products.AddProductsRow(product)

Dim rowsAffected As Integer = Adapter.Update(products) ' Return true if precisely one row was inserted, otherwise false Return rowsAffected = 1 End Function <System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateProduct (ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _ ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _ ByVal unitPrice As Nullable(Of Decimal), _ ByVal unitsInStock As Nullable(Of Short), _ ByVal unitsOnOrder As Nullable(Of Short), _ ByVal reorderLevel As Nullable(Of Short), _ ByVal discontinued As Boolean, ByVal productID As Integer) _ As Boolean Dim products As NorthwindWithSprocs.ProductsDataTable = _ _

Adapter.GetProductByProductID(productID) If products.Count = 0 Then ' no matching record found, return false Return False End If Dim product As NorthwindWithSprocs.ProductsRow = products(0) product.ProductName = productName If Not supplierID.HasValue Then product.SetSupplierIDNull() Else product.SupplierID = supplierID.Value End If If Not categoryID.HasValue Then product.SetCategoryIDNull() Else product.CategoryID = categoryID.Value End If If quantityPerUnit Is Nothing Then product.SetQuantityPerUnitNull() Else product.QuantityPerUnit = quantityPerUnit End If If Not unitPrice.HasValue Then

product.SetUnitPriceNull() Else product.UnitPrice = unitPrice.Value End If If Not unitsInStock.HasValue Then product.SetUnitsInStockNull() Else product.UnitsInStock = unitsInStock.Value End If If Not unitsOnOrder.HasValue Then product.SetUnitsOnOrderNull() Else product.UnitsOnOrder = unitsOnOrder.Value End If If Not reorderLevel.HasValue Then product.SetReorderLevelNull() Else product.ReorderLevel = reorderLevel.Value End If product.Discontinued = discontinued ' Update the product record Dim rowsAffected As Integer = Adapter.Update(product) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteProduct(ByVal productID As Integer) As Boolean Dim rowsAffected As Integer = Adapter.Delete(productID) ' Return true if precisely one row was deleted, otherwise false Return rowsAffected = 1 End Function End Class

Esta clase imita la semntica de la clase ProductsBLL de los tutoriales anteriores, pero usa los objetos ProductsTableAdapter y ProductsDataTable del DataSet NorthwindWithSprocs. Por ejemplo en lugar de tener una declaracin Imports NorthwindTableAdapters al comienzo del archivo clase como en ProductsBLL, la clase ProductsBLLWithSprocs utiliza Imports NorhtwindWithSprocsTableAdapters. Del mismo modo, los objetos ProductsDataTable y ProductsRow que se utilizan en esta clase tienen el prefijo con el espacio de nombres NorhtwindWithSprocs. La clase ProductsBLLWithSprocs proporciona dos mtodos de acceso a datos, GetProducts y GetProductByProductID y los mtodos para agregar, actualizar y eliminar una instancia de un solo producto.

Paso 7. Trabajar con el DataSet NorthwindWithSprocs desde la capa de presentacin Hasta este momento hemos creado una DAL que utiliza procedimientos almacenados para acceder y modificar los datos de la base de datos subyacente. Tambin hemos construido una BLL rudimentaria con mtodos para recuperar todos los productos o un producto en particular, junto con los mtodos para agregar, actualizar y eliminar productos. Para completar este tutorial vamos a crear una pgina Asp.Net que utilice la clase ProductsWithSprocs de la BLL para visualizar, actualizar y eliminar registros. Abra la pgina NewSprocs.aspx en la carpeta AdvancedDAL y arrastre un control GridView desde el cuadro de herramientas hasta el diseador, dndole el nombre de Products. Desde la etiqueta inteligente del GridView elija enlazar a un nuevo ObjectDataSource llamado ProductsDataSource. Configure el ObjectDataSource para que utilice la clase ProductsBLLWithSprocs, como se muestra en la figura 22.

Figura 22. Configure el ObjectDataSource para que utilice la clase ProductsBLLWithSprocs La lista desplegable de la pestaa SELECT tiene dos opciones, GetProducts y GetProductByProductID. Dado que queremos mostrar todos los productos en el GridView, seleccione el mtodo GetProducts. Las listas desplegables en las pestaas UPDATE, DELETE e INSERT cada una tienen un solo mtodo. Asegrese que cada una de las listas desplegables tiene su mtodo apropiado seleccionado y haga clic en finalizar.

Despus que se ha completado el asistente del ObjectDataSource, Visual Studio agregar BoundFields y un CheckBoxField al GridView para los datos de los campos de los productos. Habilite las funciones de insercin y actualizacin predefinidas del GridView marcando las opciones Habilitar paginacin y habilitar eliminacin presentes en la etiqueta inteligente.

Figura 23. La pgina contiene un GridView con el soporte de edicin y eliminacin habilitadas Como ya hemos discutido en los tutoriales anteriores, al finalizar el asistente del ObjectDataSource, Visual Studio establece la propiedad OldValuesParameterFormatString en original_{0}. Esto debe ser revertido a su valor por defecto de {0} con el fin que las caractersticas de modificacin de datos funcionen correctamente dados los parmetros esperados por los mtodos en nuestra BLL. Por lo tanto asegrese de ajustar la propiedad OldValuesParameterFormatString en {0} o quitar la propiedad totalmente de la sintaxis declarativa. Despus de completar el asistente de configuracin de orgenes de datos, habilite el soporte de edicin y eliminacin en el GridView y regrese la propiedad OldValuesParameterFormatString a su valor por defecto, el marcado declarativo de su pgina debe ser similar al siguiente:
<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ProductsDataSource"> <Columns>

<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" /> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="ProductsDataSource" runat="server" DeleteMethod="DeleteProduct" InsertMethod="AddProduct" SelectMethod="GetProducts" TypeName="ProductsBLLWithSprocs" UpdateMethod="UpdateProduct"> <DeleteParameters> <asp:Parameter Name="productID" Type="Int32" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" />

<asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> </InsertParameters> </asp:ObjectDataSource>

Hasta este momento podramos arreglar el GridView personalizando la interfaz de edicin para que incluya validacin, haciendo que las columnas SupplierID y CategoryID sean presentadas como DropDownLists y as sucesivamente. Tambin podramos aadir una confirmacin del lado del cliente en el botn borrar. Como estos temas han sido cubiertos en los tutoriales anteriores, no los cubriremos aqu. Independientemente de si mejoramos o no el GridView, pruebe las caractersticas principales en un navegador. En la figura 24 se muestra como la pgina muestra los productos en un GridView que ofrece los soportes de insercin y eliminacin por fila.

Figura 24. Los productos pueden ser visualizados, actualizados y eliminados del GridView

Resumen Los TableAdapter en un DataSet tipiado pueden tener acceso a los datos de una base de datos utilizando instrucciones SQL-ad hoc o por medio de procedimientos almacenados. Cuando se trabaja con procedimientos almacenados, pueden usarse procedimientos almacenados existentes o el asistente del TableAdapter puede ser instruido para crear nuevos procedimientos almacenados sobre la base de una consulta SELECT. En este tutorial vimos como hacer que los procedimientos almacenados se creen automticamente para nosotros. Aunque hacer que los procedimientos almacenados sean generados automticamente ayuda a ahorrar tiempo, hay ciertos casos en los que el procedimiento almacenado creado por el asistente no se alinea con lo que habramos creado por nosotros mismos. Un ejemplo es el procedimiento almacenado Products_Update, que espera los parmetros de entrada tanto @Original_ProductID y @ProductID a pesar que el parmetro @Original_ProductId es superfluo. En muchos escenarios los procedimientos almacenados pueden ya haber sido creados o podramos desear construirlos manualmente para tener un grado de control sobre los comandos de los procedimientos almacenados. En cualquier caso, nos gustara dar instrucciones al TableAdapter para que utilice los procedimientos almacenados para sus mtodos. Veremos cmo lograr esto en el siguiente tutorial.

65. UTILIZAR PROCEDIMIENTOS ALMACENADOS EXISTENTES PARA LOS TABLEADAPTERS DEL DATASET TIPIADO
En el tutorial anterior aprendimos como usar el asistente del TableAdapter para generar nuevos procedimientos almacenados. En este tutorial aprenderemos como el mismo asistente del TableAdapter puede trabajar con procedimientos almacenados ya existentes. Tambin aprenderemos como agregar manualmente nuevos procedimientos almacenados a nuestra base de datos. Introduccin En el tutorial anterior vimos como los TableAdapters del DataSet tipiado pueden ser configurados para usar procedimientos almacenados para acceder a los datos en lugar de sentencias SQL ad-hoc. En concreto examinamos como hacer que el asistente del TableAdapter cree automticamente estos procedimientos almacenados. Cuando presentamos una aplicacin heredada a ASP.Net 2.0 o cuando construimos un sitio web ASP.Net 2.0 alrededor de un modelo de datos existente, es probable que la base de datos ya contenga los procedimientos almacenados que necesita. Alternativamente podras preferir crear tus procedimientos almacenados a mano o a travs de alguna herramienta en lugar que el asistente del TableAdapter genere automticamente sus procedimientos almacenados. En este tutorial observaremos como configurar el asistente del TableAdapter para que utilice procedimientos almacenados existentes. Como la base de datos Northwind solo tiene un pequeo conjunto de procedimientos almacenados predefinidos, tambin observaremos los pasos necesarios para agregar manualmente nuevos procedimientos almacenados a la base de datos por medio del ambiente de Visual Studio. Nota: En el tutorial Envolver modificaciones de bases de datos dentro de una transaccin agregamos mtodos para soportar transacciones (BeginTransaction, CommitTransaction y as sucesivamente). De forma alternativa las transacciones pueden ser manejadas totalmente dentro de un procedimiento almacenado, lo cual no requiere ninguna modificacin al cdigo de la capa de acceso a datos. En este tutorial exploraremos los comandos T-SQL usados para ejecutar las sentencias de un procedimiento almacenado dentro del alcance de una transaccin. Paso 1. Agregar procedimientos almacenados a la base de datos Northwind

Visual Studio hace fcil agregar nuevos procedimientos almacenados a la base de datos. Agreguemos un nuevo procedimiento almacenado a la base de datos Northwind que devuelva todas las columnas de la tabla Products para aquellos que tengan un valor de CategoryID en particular. Desde la ventana del explorador de servidores, expanda la base de datos Northwind para que se muestren sus carpetas DataBase Diagrams, Tables, Views y as sucesivamente. Como vimos en el tutorial anterior, la carpeta Procedimientos Almacenados contiene los procedimientos almacenados existentes de la base de datos. Para agregar un nuevo procedimiento almacenado, sencillamente haga clic derecho en la carpeta procedimientos almacenados y seleccione la opcin agregar nuevos procedimientos almacenados del men contextual.

Figura 1. Haga clic derecho en la carpeta procedimientos almacenados y agregue un nuevo procedimiento almacenado Como muestra la figura 1, seleccionando la opcin Agregar nuevo procedimiento almacenado abrimos una ventana de script en Visual Studio con el esquema del script SQL necesario para crear el procedimiento almacenado. En nuestro trabajo profundizar este script y ejecutarlo, momento en el cual el procedimiento almacenado se agregar a la base de datos. Ingrese el siguiente script:
CREATE PROCEDURE dbo.Products_SelectByCategoryID ( @CategoryID int ) AS SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued

FROM Products WHERE CategoryID = @CategoryID

Cuando ejecutemos este script, agregaremos un nuevo procedimiento almacenado a la base de datos Northwind llamado Products_SelectByCategoryID. Este procedimiento almacenado acepta un solo parmetro de entrada (@CategoryID, de tipo int) y devuelve todos los campos para aquellos productos que tengan un mismo valor de CategoryID. Para ejecutar este script CREATE PROCEDURE y agregar el procedimiento almacenado a la base de datos, haga clic en el icono guardar en la barra de herramientas o presione Ctrl+S. Despus de realizar esto, se actualiza la carpeta Procedimientos Almacenados, mostrando el procedimiento almacenado recin creado. Tambin el script en la ventana cambiar sutilmente de CREATE PROCEDURE dbo.Products_SelectProductByCategoryID a ALTER PROCEDURE dbo.Products_SelectProductByCategoryID. CREATE PROCEDURE agrega un nuevo procedimiento almacenado a la base de datos, mientras que ALTER PROCEDURE actualiza uno existente. Como el inicio del script ha cambiado a ALTER PROCEDURE, cambiando los parmetros de entrada de los procedimientos almacenados o las sentencias SQL y haciendo clic en el icono guardar actualizaremos los procedimientos almacenados con estos cambios. La figura 2 muestra Visual Studio despus que el procedimiento almacenado Products_SelectByCategoryID ha sido guardado.

Figura 2. El procedimiento almacenado Products_SelectByCategory ha sido agregado a la base de datos

Paso 2. Configurar el TableAdapter para que use un procedimiento almacenado existente Ahora que el procedimiento almacenado Products_SelectByCategoryID ha sigo agregado a la base de datos, podemos configurar nuestra capa de acceso a datos para usar este procedimiento almacenado cuando uno de sus mtodos es invocado. En concreto, agregamos un mtodo GetProductsByCategoryID(<i22>CategoryID) al ProductsTableAdapter en el DataSet tipiado NorthwindWithSprocs que llame al procedimiento almacenado Products_SelectByCategoryID que recin creamos. Comenzamos abriendo el DataSet NorthwindWithSprocs. Haga clic derecho sobre el ProductsTableAdapter y seleccione Agregar Consulta para iniciar el asistente de configuracin de consultas del TableAdapter. En el tutorial anterior optamos por hacer que el TableAdapter creara un nuevo procedimiento almacenado por nosotros. Sin embargo, para este tutorial, deseamos enlazar el nuevo mtodo del TableAdapter al procedimiento almacenado Products_SelectByCategoryID existente. Por lo tanto, escoja la opcin usar procedimiento almacenado existente en el primer paso del asistente y luego haga clic en siguiente.

Figura 3. Seleccione la opcin usar procedimiento almacenado existente La siguiente pantalla nos proporciona una lista desplegable poblada con los procedimientos almacenados de nuestra base de datos. Seleccionando un procedimiento almacenado vemos sus parmetros de entrada a la izquierda y los

campos de datos devueltos (si los hay) en la derecha. Seleccione de la lista el procedimiento almacenado Products_SelectByCategoryID y haga clic en siguiente.

Figura 4. Seleccione el procedimiento almacenado Products_SelectByCategoryID La siguiente pantalla nos pregunta qu clase de datos es devuelto por el procedimiento almacenado y nuestra respuesta aqu determina el tipo devuelto por el mtodo del TableAdapter. Por ejemplo si indicamos que se devuelven datos tabulares, el mtodo devolver una instancia ProductsDataTable poblada con los registros devueltos por el procedimiento almacenado. Al contrario si indicamos que este procedimiento almacenado devuelve un solo valor, el TableAdapter devolver un Object al que se le asigna el valor de la primera columna del primer registro devuelto por el procedimiento almacenado Como el procedimiento almacenado Products_SelectByCategoryID devuelve todos los productos que pertenecen a una categora en particular, seleccione la primera opcin Datos Tabulares y haga clic en siguiente.

Figura 5. Indica que el procedimiento almacenado devuelve datos tabulares Todo lo que queda es indicar que enfoque de mtodo usaremos seguido por los nombres de estos mtodos. Deje seleccionado las opciones Fill a DataTable y Return a DataTable, pero cambie el nombre de los mtodos a FillByCategoryID y GetProductsByCategoryID. Luego haga clic en siguiente para revisar un resumen de las tareas que el asistente realizar. Si todo luce correcto, haga clic en Finalizar.

Figura 6. Nombre los mtodos FillByCategoryID y GetProductsByCategoryID Nota: Los mtodos del TableAdapter recin creados, FillByCategoryID y

GetProductsByCategoryID, esperan un parmetro de tipo integer. Este valor del

parmetro de entrada es pasado al procedimiento almacenado por medio de su parmetro @CategoryID. Si modifica los parmetros del procedimiento almacenado Products_SelectByCategory, tambin es necesario almacenar los parmetros para los mtodos del TableAdapter. Como discutimos en el tutorial anterior esto puede realizarse de una de las dos formas siguientes: agregando o removiendo manualmente los parmetros de la coleccin parameter o ejecutando el asistente del TableAdapter. Paso 3. Agregar un mtodo GetProductsByCategoryID(categoryID) a la BLL Con el mtodo GetProductsByCategoryID de la DAL completo, el siguiente paso es proporcionar acceso a este mtodo en la capa de acceso a datos. Abra el archivo de clase ProductsBLLWithSprocs y agregue el siguiente mtodo:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _ As NorthwindWithSprocs.ProductsDataTable Return Adapter.GetProductsByCategoryID(categoryID) End Function

El mtodo BLL sencillamente devuelve el ProductsDataTable devuelto desde el mtodo GetProductsByCategoryID del ProductsTableAdapter. El atributo DataObjectMethodAttribute proporciona metadatos usados por el asistente de configuracin de origen de datos del ObjectDataSource. En concreto, este mtodo aparecer en la lista desplegable de la pestaa SELECT. Paso 4. Mostrando productos por categora Para probar el procedimiento almacenado Products_SelectByCategoryID recin agregado y los mtodos DAL y BLL correspondientes, crearemos una pgina ASP.Net que contiene un DropDownList y un GridView. El DropDownList mostrar todas las categoras en la base de datos mientras que el GridView muestra los productos que pertenecen a la categora seleccionada. Nota: Creamos interfaces Maestro/Detalle usando DropDownLists en tutoriales anteriores. Para profundizar ms podemos ver como implementar un Informe Maestro/Detalle, DropDownList. refirindonos al tutorial Filtrado Maestro/Detalle con un

Abra la pgina ExistingSprocs.aspx en la carpeta AdvancedDAL y arrastre un DropDownList desde la barra de herramientas al diseador. Establezca la propiedad ID del DropDownList en Categories y su propiedad AutoPostBack en True. Luego, desde su etiqueta inteligente, enlace el DropDownList a un nuevo ObjectDataSource llamado CategoriesDataSource. Configure el ObjectDataSource para que recupere sus datos desde el mtodo GetCategories de la clase CategoriesBLL. Establezca las listas desplegables de las pestaas UPDATE, INSERT y UPDATE en Ninguno.

Figura 7. Recuperar los datos desde el mtodo GetCategories de la clase CategoriesBLL

Figura 8. Establezca las listas desplegables de las pestaas INSERT, UPDATE y DELETE en Ninguno

Despus de completar el asistente de configuracin del ObjectDataSource, configure la lista desplegable para mostrar el campo de datos CategoryName y use el campo CategoryID como el valor para cada ListItem. Hasta este momento, el marcado declarativo del DropDownList y el del

ObjectDataSource debern ser similares al siguiente:


<asp:DropDownList ID="Categories" runat="server" AutoPostBack="True" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID"> </asp:DropDownList> <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" TypeName="CategoriesBLL"> </asp:ObjectDataSource>

Luego,

arrastre

un

GridView a

hasta un

el

diseador,

colocndolo

debajo

del

DropDownList`. Establezca el ID del GridView en ProductsCategory y desde su etiqueta inteligente, enlcelo nuevo Configure sus datos ObjectDataSource el usando llamado ProductsByCategoryDataSource. haciendo esto recuperamos ObjectDataSource el mtodo

ProductsByCategoryDataSource para que utilice la clase ProductsBLLWithSprocs, GetProductsByCategoryID(categoryId). Como el GridView solamente ser usado para mostrar datos, establezca las listas desplegables de las pestaas UPDATE, INSERT y DELETE en ninguno y haga clic en siguiente.

Figura 9. Configurar el ObjectDataSource para que utilice la clase ProductsBLLWithSprocs

Figura 10. Recuperar los datos del mtodo GetProductsByCategoryID(categoryID) El mtodo seleccionado en la pestaa SELECT espera un parmetro, por lo cual en el paso final del asistente nos solicitar el origen del parmetro. Establezca la lista despegable del origen del parmetro en Control y seleccione el control Categories en la lista despegable de ControlID. Haga clic en finalizar para completar el asistente.

Figura 11. Use en la lista desplegable Categories como fuente del parmetro categoryID Una vez completado el asistente del ObjectDataSource, Visual Studio agregar BoundFields y CheckBoxField para cada uno de los campos de datos del producto. Sintase libre de personalizar estos campos como lo desee. Visite la pgina a travs de un navegador. Cuando visitamos la pgina, la categora Beverages es seleccionada y los productos correspondientes se muestran en el GridView. Cambiar la lista desplegable a una categora alternativa, como se muestra en la figura 12, origina una devolucin de datos y recarga el GridView con los productos de la categora recin seleccionada.

Figura 12. Se muestran los productos de la categora Produce

Paso 5. Envolver una sentencia de procedimientos almacenados dentro del alcance de una transaccin En el tutorial Envolver modificaciones de base de datos dentro de una transaccin discutimos las tcnicas para realizar una serie de sentencias de modificaciones de base de datos dentro del alcance de una transaccin. Recordemos que las modificaciones realizadas bajo el marco de una transaccin o bien tienen xito todas o fallan todas garantizando atomicidad. Las tcnicas para usar las transacciones incluyen: Las clases usan el espacio de nombres System.Transactions Contamos con que la capa de acceso a datos utilice clases ADO.Net como SqlTransaction y Agregamos los comandos de transaccin T-SQL directamente dentro del procedimiento almacenado. El tutorial Envolver modificaciones de base de datos dentro de una transaccin utiliz las clases ADO.Net en la DAL. El resto de este tutorial examina como manejar una transaccin usando sentencias T-SQL dentro de un procedimiento almacenado. Los tres comandos SQL claves para iniciar manualmente, agrupar y deshacer una transaccin son BEGIN TRANSACTION, COMMIT TRANSACTION y ROLLBACK TRANSACTION respectivamente. Al igual que con el enfoque ADO.Net, cuando usamos transacciones dentro de un procedimiento almacenado es necesario aplicar los siguientes enfoques: 1. Indicar el inicio de una transaccin 2. Ejecutar las sentencias SQL que comprenden la transaccin 3. Si hay un error en cualquiera de las sentencias desde el paso 2, deshacer la transaccin 4. Si todas las sentencias desde el paso 2 se completan sin error, recuperamos la transaccin Este enfoque puede ser implementado en sintaxis T-SQL usando la siguiente plantilla:
BEGIN TRY BEGIN TRANSACTION -- Start the transaction ... Perform the SQL statements that makeup the transaction ... -- If we reach here, success!

COMMIT TRANSACTION END TRY BEGIN CATCH -- Whoops, there was an error ROLLBACK TRANSACTION -- Raise an error with the -- details of the exception DECLARE @ErrMsg nvarchar(4000), @ErrSeverity int SELECT @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY() RAISERROR(@ErrMsg, @ErrSeverity, 1) END CATCH

La plantilla inicia definiendo un bloque TRY CATCH, un nuevo constructor para SQL Server 2005. Al igual que los bloques Try Catch en Visual Studio, el bloque SQL TRY CATCH ejecuta las sentencias en el bloque TRY. Si cualquier sentencia genera un error, el control es inmediatamente transferido a un bloque CATCH. Si no hay errores ejecutando las sentencias SQL que componen la transaccin, la sentencia COMMIT TRANSACTION rene los cambios y completa la transaccin. Sin embargo, si una de las sentencias genera un error, el ROLLBACK TRANSACTION en el bloque CATCH devuelve la base de datos a su previo al inicio de la transaccin. El procedimiento almacenado emite un error con el comando RAISERROR, que origina una excepcion SqlException para ser emitida en la aplicacin. Nota: Como el bloque TRY CATCH es nuevo para SQL Server 2005, la plantilla anterior no funcionara si est utilizando versiones anteriores de Microsoft SQL Server. Si usted no est utilizando SQL Server 2005, consulte Manejando transacciones en procedimientos almacenados SQL Server para una plantilla que trabaje con otras versiones de SQL Server. Observaremos un ejemplo concreto. Existe una restriccin de llave fornea entre las tablas Categories y Products, lo que significa que cada campo CategoryID en la tabla Products debe tener asignado un valor CategoryID en la tabla Categories. Cualquier accin que viole esta restriccin, as como el intento de eliminar una categora que tenga productos asociados, origina una violacin de llave externa. Para verificar esto,

visitamos nuevamente el ejemplo de Actualizacin y Eliminacin de datos binarios existentes en la seccin Trabajando con datos binarios (~/BinaryData/UpdatingAndDeleting.aspx). Esta pgina muestra cada categora en el sistema junto con los botones de eliminacin y edicin (Ver Figura 13), pero si intenta eliminar una categora que tenga productos asociados como Bebidas, la eliminacin fallar debido a una violacin en la restriccin de llave externa.

Figura 13. Cada categora es mostrada en un GridView con botones de edicin y eliminacin

Figura 14. No puede eliminar una categora que tenga productos existentes Imaginemos pues que deseamos permitir que las categoras sean eliminadas sin importar si tienen o no productos asociados con ellas. En caso que una categora con

productos sea eliminada, imagine que deseemos borrar sus productos existentes (aunque otra opcin sencillamente sera establecer los valores CategoryID de sus productos en Nulls). Esta funcionalidad puede ser implementada por medio de reglas de cascada de restriccin de llave fornea. De forma alternativa podramos crear un procedimiento almacenado que acepte un parmetro de entrada @CategoryID y cuando sea invocado, elimine explcitamente todos los productos asociados y luego la categora especificada. Nuestro primer intento de procedimiento almacenado deber tener un aspecto como el siguiente:
CREATE PROCEDURE dbo.Categories_Delete ( @CategoryID int ) AS -- First, delete the associated products... DELETE FROM Products WHERE CategoryID = @CategoryID -- Now delete the category DELETE FROM Categories WHERE CategoryID = @CategoryID

Aunque sin duda esto eliminar definitivamente la categora y los productos asociados, no lo hace bajo el marco de la transaccin. Imagnese que hay una restriccin de llave fornea en Categories, que prohbe la eliminacin de un valor @CategoryID en particular. El problema est en el caso que todos los productos sean borrados antes de eliminar la categora. El resultado final es que para cada categora, este procedimiento almacenado deber remover todos sus productos mientras que la categora deber mantenerse debido a que todava tiene registros relacionados en alguna otra tabla. Sin embargo, si el procedimiento almacenado fue envuelto dentro del alcance de la transaccin, la eliminacin de la tabla Products desencadenar una eliminacin fallida en Categories. El siguiente script del procedimiento almacenado usa una transaccin para asegurar la atomicidad entre las dos sentencias DELETE.
CREATE PROCEDURE dbo.Categories_Delete ( @CategoryID int ) AS BEGIN TRY BEGIN TRANSACTION -- Start the transaction -- First, delete the associated products...

DELETE FROM Products WHERE CategoryID = @CategoryID -- Now delete the category DELETE FROM Categories WHERE CategoryID = @CategoryID -- If we reach here, success! COMMIT TRANSACTION END TRY BEGIN CATCH -- Whoops, there was an error ROLLBACK TRANSACTION -- Raise an error with the -- details of the exception DECLARE @ErrMsg nvarchar(4000), @ErrSeverity int SELECT @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY() RAISERROR(@ErrMsg, @ErrSeverity, 1) END CATCH

Tmese un momento para agregar el procedimiento almacenado Categories_Delete a la base de datos Northwind. Refirase de nuevo al paso 1 para las instrucciones de cmo agregar un procedimiento almacenado a la base de datos. Paso 6. Actualizar el CategoriesTableAdapter Aunque agregamos el procedimiento almacenado Categories_Delete a la base de datos, la DAL actualmente est configurada para utilizar sentencias SQL ad-hoc para realizar la eliminacin. Necesitamos actualizar el CategoriesTableAdapter e indicarle que en su lugar utilice el procedimiento almacenado Categories_Delete. Nota: Anteriormente en este Pero tutorial, el estuvimos solo trabajando tena una con el

DataSetNorhtwindWithSprocs.

DataSet

entidad,

ProductsDataTable y necesitamos trabajar con las categoras. Por lo tanto para el resto de este tutorial cuando hablemos de la capa de acceso a datos nos referiremos al DataSet Northwind, que creamos en el primer tutorial de Creacin de una capa de acceso a Datos.

Abra el DataSet Northwind, seleccione el CategoriesTableAdapter y vaya a la ventana propiedades. La ventana propiedades mostrar el InsertCommand, UpdateComand, DeleteCommand y SelectCommand usados por el TableAdapter, as como su nombre e informacin de conexin. Expanda la propiedad DeleteCommand para ver sus detalles. Como muestra la figura 15, la propiedad CommandType del DeleteCommand est establecida en Texto, que indica que se enva el texto en la propiedad CommandText como una sentencia SQL ad-hoc.

Figura 15. Seleccione el CategoriesTableAdapter en el diseador para ver sus propiedades en la ventana propiedades Para cambiar estas definiciones, seleccione el texto (DeleteCommand) en la ventana Propiedades y escoja (New) de la lista desplegable. Esto limpiara las definiciones para las propiedades CommandText, CommandType y Parameters. Luego establezca la propiedad CommandType en Procedimientos Almacenados y luego escriba el nombre del procedimiento almacenado en CommandText (dbo.Categories_Delete). Si usted se asegura de ingresar las propiedades en este orden, primero el CommandType y luego el CommandText, Visual Studio se asegurar de poblar automticamente la coleccin Parameters. Si no ingresa las propiedades en este orden, deber agregar manualmente los parmetros a travs del editor de la coleccin Parameters. De cualquier modo, es prudente hacer clic en los puntos suspensivos de la propiedad Parameters para ejecutar el Editor de coleccin Parameters y verificar que los cambios en la definicin de los

parmetros se han realizado y es correcta (Ver Figura 16). Si no observa ningn parmetro en el cuadro de dialogo, agregue el parmetro @CategoryID manualmente (no necesita agregar el parmetro @RETURN_VALUE).

Figura 16. Asegrese que las definiciones de los parmetros son correctas Una vez la DAL ha sido actualizada, eliminando una categora automticamente borramos todos los productos asociados a ella y lo realiza bajo el marco de una transaccin. Para verificar esto regresamos a la pgina de Actualizacin y Eliminacin de datos binarios existentes y hacemos clic en el botn eliminar de una de las categoras. Con un solo clic la categora y todos sus productos asociados sern eliminados. Nota: Antes de probar el procedimiento almacenado Categories_Delete, el cual elimina un nmero de productos que pertenezca a una categora en particular, podra ser prudente realizar una copia de seguridad de nuestra base de datos. Si est utilizando la base de datos NOTRHWND.MDF en App_Data, sencillamente cierre Visual Studio y copie los archivos MDF y LDF de App_Data en otra carpeta. Despus de probar esta funcionalidad, puede restaurar la base de datos cerrando Visual Studio y remplazando los archivos MDF y LDF actuales en App_Data con las copias de seguridad. Resumen Aunque el asistente del TableAdapter genera automticamente los procedimientos almacenado por nosotros, muchas veces podramos tener ya creados los procedimientos almacenados o desear crearlos de forma manual o con otra

herramienta. Para adaptarnos a estos escenarios el TableAdapter puede ser configurado para que apunte a un procedimiento almacenado existente. En este tutorial observamos como agregar manualmente procedimientos almacenados a la base de datos por medio del ambiente de Visual Studio y como enlazar los mtodos del TableAdapter a estos procedimientos almacenados. Tambin examinamos las sentencias T-SQL y los enfoques script usados para iniciar, agrupar y revertir transacciones desde un procedimiento almacenado.

66. ACTUALIZAR EL TABLEADAPTER PARA UTILIZAR JOINs


Cuando se trabaja con una base de datos es comn que la solicitud de datos se transmita a travs de varias tablas. Para recuperar datos de dos tablas diferentes podemos utilizar una subconsulta correlacionada o una operacin JOIN. En este tutorial se comparan las subconsultas correlacionadas y la sintaxis de JOIN antes de ver cmo crear un TableAdapter que incluya un JOIN en su consulta principal. Introduccin Con las bases de datos relacionales, los datos con los que estamos interesados en trabajar a menudo se propagan a travs de varias tablas. Por ejemplo, cuando se muestra la informacin de los productos es posible que deseemos la lista de cada producto con su correspondiente categora y nombre de proveedor. La tabla Products tiene los valores de CategoryID y SupplierID, pero la categora actual y los nombres de los proveedores se encuentran en las tablas Categories y Suppliers respectivamente. Para recuperar la informacin de otra tabla relacionada, podemos utilizar subconsultas correlacionadas o JOINs. Una subconsulta correlacionada es una consulta SELECT anidada que referencia las columnas en la consulta externa. Por ejemplo en el tutorial Creacin de una capa de acceso a datos hemos utilizado dos subconsultas correlacionadas en la consulta principal del ProductsTableAdapter para recuperar los nombres del proveedor y la categora para cada producto. Un JOIN es una construccin SQL que combina las filas relacionadas de dos tablas diferentes. Nosotros usamos un JOIN en el tutorial Consulta de datos con un control SqlDataSource para mostrar la informacin de cada categora junto a cada producto. La razn por la cual nos hemos abstenido de usar JOINs con los TableAdapters se debe a las limitaciones en el asistente del TableAdapter para generar automticamente las correspondientes sentencias INSERT, UPDATE y DELETE. Ms especficamente si la consulta principal del TableAdapter contiene JOINs, el TableAdapter no puede generar automticamente las sentencias SQL ad-hoc o los procedimientos almacenados para sus propiedades InsertCommand, UpdateCommand y DeleteCommand. En este tutorial vamos a comparar brevemente y contrastaremos las subconsultas correlacionadas y los JOINs antes de explorar la forma de crear un TableAdapter que incluya JOINs en su consulta Principal.

Comparar y contrastar las consultas correlacionadas y los JOINs Recordemos que el ProductsTableAdapter creado en el primer tutorial en el DataSet Northwind utiliza subconsultas correlacionadas para devolver cada producto con su correspondiente nombre de proveedor y categora. La consulta principal del ProductsTableAdapter se muestra a continuacin.
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products

Las dos subconsultas correlacionadas (SELECT CategoryName FROM Categories WHERE


Categories.CategoryID = Products.CategoryID) y (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) son consultas SELECT que devuelven un solo

valor por producto como una columna adicional en la lista de columnas de la sentencia SELECT externa. Alternativamente un JOIN puede ser usado para recuperar el nombre del proveedor y categora de cada producto. La siguiente consulta devuelve el mismo resultado anterior, pero utiliza JOINs en lugar de subconsultas.
SELECT ProductID, ProductName, Products.SupplierID, Products.CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, Categories.CategoryName, Suppliers.CompanyName as SupplierName FROM Products LEFT JOIN Categories ON Categories.CategoryID = Products.CategoryID LEFT JOIN Suppliers ON Suppliers.SupplierID = Products.SupplierID

Un JOIN combina los registros de una tabla con los registros de otra tabla basado en algn criterio. Por ejemplo, en la consulta anterior el LEFT JOIN Categories ON Categories.CategoryID = Products.CategoryID indica a SQL Server combinar cada

registro del producto con el registro de categora cuyo CategoryID coincida con el valor CategoryID del producto. El resultado de la combinacin nos permite trabajar con los campos de la categora correspondiente para cada producto (por ejemplo, CategoryName). Nota: Los JOINs comnmente son usados cuando consultamos datos desde bases de datos relacionales. Si eres nuevo en el uso de la sintaxis de JOIN o necesita profundizar un poco ms en su uso. Recomiendo el SQL Join tutorial de W3 Schools. Adems de las secciones Subquery Fundamentals y JOIN Fundamentals de la seccin de SQL Books Online Como los JOINs y las subconsultas correlacionadas pueden ser utilizadas para recuperar los datos relacionados de otras tablas, muchos desarrolladores se quedan rascndose la cabeza preguntndose qu mtodo utilizar. Todos los gurs de SQL con los que hablado han dicho ms o menos lo mismo, que no importa porque en cuanto al rendimiento de SQL Server se producen planes de ejecucin idnticos. Su consejo entonces es utilizar la tcnica con la que usted y su equipo se sientan ms cmodos. Merece destacar que despus de impartir este consejo los expertos de inmediato expresan su preferencia por los JOINs sobre las subconsultas correlacionadas. Cuando construimos una capa de acceso a datos usando DataSet tipiados, las herramientas trabajan mejor cuando usamos subconsultas. En concreto, el asistente del TableAdapter no genera automticamente las sentencias INSERT, DELETE y UPDATE correspondientes si la consulta principal contiene JOINs, pero si generara automticamente estas sentencias si se utilizan subconsultas correlacionadas. Para explorar este defecto, creamos un DataSet tipiado temporal en la carpeta ~/App_Code/DAL. Durante el asistente de configuracin del TableAdapter, escogemos utilizar consultas SQL ad-hoc e ingresamos la siguiente consulta SELECT (Ver Figura 1):
SELECT ProductID, ProductName, Products.SupplierID, Products.CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, Categories.CategoryName, Suppliers.CompanyName as SupplierName FROM Products LEFT JOIN Categories ON Categories.CategoryID = Products.CategoryID

LEFT JOIN Suppliers ON Suppliers.SupplierID = Products.SupplierID

Figura 1. Escriba una consulta principal que contenga JOINs De forma predeterminada, el TableAdapter creara automticamente las sentencias INSERT, UPDATE y DELETE basado en la consulta principal. Si hace clic en el botn Avanzado, se puede ver que esta opcin ya esta activada. A pesar de este ajuste, el TableAdapter no ser capaz de crear las sentencias INSERT, UPDATE y DELETE porque la consulta principal contiene un JOIN.

Figura 2. Escriba una consulta principal que contenga JOINs Haga clic en Finalizar para completar el asistente. Hasta este momento el diseador de su DataSet incluir un solo TableAdapter con un DataTable con columnas para cada uno de los campos devueltos en la lista de columnas de la consulta SELECT. Esto incluye el CategoryName y SupplierName, como muestra la figura 3.

Aunque el DataTable tiene las columnas correspondientes, el TableAdapter carece de valores para sus propiedades InsertCommand, UpdateCommand y DeleteCommand. Para confirmar esto haga clic sobre el TableAdapter en el diseador y luego vaya a la ventana propiedades. All podr ver que las propiedades InsertCommand, UpdateCommand y DeleteCommand se establecen en ninguno.

Figura 3. El DataTable incluye una columna para cada campo devuelto en la lista de columnas

Figura 4.Las propiedades InsertCommand, DeleteCommand y UpdateCommand se establecen en Ninguno Para evitar este inconveniente puede proporcionar manualmente las instrucciones SQL y los parmetros para las propiedades InsertCommand, DeleteCommand y UpdateCommand por medio de la ventana propiedades. Como alternativa, podramos empezar configurando la consulta principal del TableAdapter para que no contenga ningn JOINs. Esto permitir que las sentencias INSERT, UPDATE y DELETE se generen automticamente por nosotros. Despus de completar el asistente, podramos entonces actualizar manualmente el SelectCommand del TableAdapter por medio de la ventana propiedades de modo que incluya la sintaxis JOIN. Si bien este enfoque funciona, es muy frgil cuando utilizamos consultas SQL-ad hoc, porque cada vez que la consulta principal del TableAdapter es reconfigurada por medio del asistente, las sentencias INSERT, UPDATE y DELETE se vuelven a crear. Esto significa que todas personalizaciones que se hayan hecho se perdern si hace clic derecho sobre el TableAdapter, selecciona configurar y completa de nuevo el asistente. La fragilidad de las sentencias INSERT, UPDATE y DELETE del TableAdapter generadas automticamente, afortunadamente es limitada a las sentencias ad-hoc. Si el TableAdapter utiliza procedimientos almacenados, o puede personalizar de el los SelectCommand, DeleteCommand, InsertCommand UpdateCommand

procedimientos almacenados y volver a ejecutar el asistente de configuracin del TableAdapter sin temor a que los procedimientos almacenados sean modificados. Durante los prximos pasos crearemos un TableAdapter que inicialmente utiliza una consulta principal que omite cualquier JOINs para que los correspondientes procedimientos almacenados de insercin, actualizacin y eliminacin sean generados automticamente. Luego se actualiza el SelectCommand para que utilice un JOIN que devuelve las columnas adicionales de las tablas relacionadas. Por ltimo crearemos una clase correspondiente en la capa lgica de negocios y demostraremos el uso del TableAdapter en una pgina web Asp.Net. Paso 1. Creacin del TableAdapter mediante una consulta principal simplificada Para este tutorial vamos a crear un DataSet y un DataTable fuertemente tipiados para la tabla Employees en el DataSet NorthwindWithSprocs. La tabla Employees contiene un

campo ReportsTo que especifica el EmployeeID del encargado de los empleados. Por ejemplo, el empleado Anne Dodsworth tiene un valor ReportsTo de 5, que es el EmployeeID de Steven Buchanan. En consecuencia, Anne le reporta a Steven su manager. Junto con la presentacin del valor de ReportsTo de cada empleado, tambin puede ser que se deseemos recuperar el nombre de su director. Esto se puede lograr mediante un JOIN. Pero si usamos un JOIN cuando creamos inicialmente el TableAdapter, el asistente se opone a la generacin automtica de las capacidades de insercin, actualizacin y eliminacin correspondientes. Por lo tanto empezaremos creando un TableAdapter cuya consulta principal no contenga ningn JOINs. Luego en el paso 2, vamos a actualizar la consulta principal del procedimiento almacenado para recuperar el nombre del manager a travs de un JOIN. Comencemos abriendo el DataSet tipiado NorthwindWithSprocs en la carpeta ~/App_Code/DAL. Haga clic derecho sobre el diseador, seleccione la opcin agregar del men contextual y de clic sobre la opcin TableAdapter del men. Esto abrir el asistente de configuracin del TableAdapter. Como se muestra en la Figura 5, seleccionamos que el asistente cree nuevos procedimientos almacenados y hacemos clic en siguiente. Para un repaso sobre la creacin de nuevos procedimientos almacenados desde el asistente del TableAdapter, consulte en el tutorial de Creacin de nuevos procedimientos almacenados para los DataSet tipiados.

Figura 5. Seleccione la opcin Crear nuevos procedimientos almacenados Utilice la siguiente sentencia SELECT para la consulta principal de TableAdapter:

SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees

Como esta consulta no incluye ningn JOINs, el asistente del TableAdapter creara automticamente el procedimiento almacenado con las sentencias INSERT, UPDATE y DELETE correspondientes, as como un procedimiento almacenado para ejecutar la consulta principal. El siguiente paso nos permite nombrar los procedimientos almacenados del TableAdapter. Utilice los nombres Employees_Select, Employees_Insert, Employees_Update y Employees_Delete como se muestra en la figura 6.

Figura 6.Nombre los procedimientos almacenados del TableAdapter El ltimo paso nos lleva a los nombres de los mtodos del TableAdapter. Utilice como nombres de los mtodos Fill y GetEmployees. Tambin asegrese de activar la casilla de la opcin crear mtodos para enviar actualizaciones directamente a la base de datos (GenerateDBDirectMethods).

Figura 7. Nombre los mtodos Fill y GetEmployees del TableAdapter Despus de completar el asistente, tmese un momento para examinar los procedimientos almacenados en la base de datos. Usted deber ver cuatro nuevos: Employees_Select, Employees_Insert, Employees_Update y Employees_Delete. Luego inspeccione el EmployeesDataTable y EmployeesTableAdapter que acabamos de crear. El DataTable contiene una columna por cada campo devuelto en la consulta principal. Haga clic en el TableAdapter y luego vaya a la ventana propiedades. All podr ver que las propiedades InsertCommand, para DeleteCommand llamar a los y UpdateCommand estn configuradas correctamente procedimientos almacenados

correspondientes.

Figura 8. El TableAdapter contiene capacidades de insercin, actualizacin y eliminacin

Con los procedimientos almacenados de insercin, actualizacin y eliminacin creados automticamente y las propiedades InsertCommand, UpdateCommand y DeleteCommand configuradas correctamente, estamos listos para personalizar el procedimiento almacenado del SelectCommand para recuperar la informacin adicional relacionada con los jefes de cada empleado. En concreto, tenemos que actualizar el procedimiento almacenado Employees_Select para que utilice un JOIN que devuelva los valores de FirstName y LastName del manager. Luego que el procedimiento almacenado ha sido actualizado, necesitamos actualizar el DataTable para que incluya estas columnas adicionales. Hacemos frente a estas dos tareas en los pasos 2 y 3. Paso 2. Personalizar el procedimiento almacenado para incluir un JOIN Empecemos yendo al explorador de servidores, profundice en la carpeta Procedimientos almacenados de la base de datos Northwind y abra el procedimiento almacenado Employees_Select. Si no ve este procedimiento almacenado, haga clic derecho sobre la carpeta Procedimientos Almacenados y elija actualizar. Actualice el procedimiento almacenado para que utilice un LEFT JOIN que devuelva el nombre y apellido del supervisor:
SELECT Employees.EmployeeID, Employees.LastName, Employees.FirstName, Employees.Title, Employees.HireDate, Employees.ReportsTo, Employees.Country, Manager.FirstName as ManagerFirstName, Manager.LastName as ManagerLastName FROM Employees LEFT JOIN Employees AS Manager ON Employees.ReportsTo = Manager.EmployeeID

Luego de actualizar la sentencia SELECT, guarde los cambios, yendo al men Archivo y seleccione Guardar Employees_Select. De forma alternativa, puede dar clic en el icono Guardar de la barra de herramientas o presionando Ctrl+S. Despus de guardar nuestros cambios, haga clic derecho sobre el procedimiento almacenado Employees_Select y seleccione ejecutar. Esto ejecuta el procedimiento almacenado y muestra sus resultados en la ventana salida (Ver Figura 9).

Figura 9. Los resultados de los procedimientos almacenados son mostrados en la ventana salida Paso 3. Actualizar las columnas de los DataTables Hasta el momento el procedimiento almacenado Employees_Select devuelve los valores de ManagerFirstName y ManagerLastName, pero el EmployeesDataTable no contiene estas columnas. Estas columnas que faltan se pueden agregar de dos formas: Manualmente: Haga clic derecho sobre el DataTable en el diseador del DataSet y en el men agregar, seleccione columna. Luego puede nombrar la columna y sus propiedades acordemente. Automticamente: El asistente de configuracin del TableAdapter actualizara las columnas del DataTable para reflejar los campos devueltos por el SelectCommand del procedimiento almacenado. Cuando se utilizan sentencias SQL ad-hoc, el asistente elimina las propiedades InsertCommand, UpdateCommand y DeleteCommand ya que el SelectCommand contiene un JOIN. Sin embargo, cuando se utilizan procedimientos almacenados, estas propiedades de comandos se mantienen intactas. Hemos explorado aadir manualmente columnas DataTable en los tutoriales anteriores Maestro-Detalle utilizando una lista con vietas de los registros maestros y un DetailsView de detalle y Carga de Archivos y volveremos a ver este proceso con mayor detalle en nuestro prximo tutorial. Sin embargo; para este tutorial, usaremos el enfoque automtico por medio del asistente de configuracin del TableAdapter.

Comience por hacer clic derecho sobre el EmployeesTableAdapter y seleccione configuracin en el men contextual. El resultado iniciara el asistente de configuracin del TableAdapter, el cual enumera los procedimientos almacenados que se utilizan para seleccionar, actualizar, insertar y eliminar, junto con sus valores devueltos y parmetros (si los hay). La figura 10 muestra el asistente. Aqu podemos ver que ahora el Employees_Select devuelve los campos ManagerFirstName y ManagerLastName.

Figura 10. El asistente muestra la lista de columnas actualizada para el procedimiento almacenado Employees_Select Complete el asistente haciendo clic en Finalizar. Al regresar al diseador del DataSet, el EmployeesDataTable ManagerLastName. incluye dos columnas adicionales: ManagerFirstName y

Figura 11. El EmployeesDataTable contiene dos columnas Para ilustrar que la actualizacin del Employees_Select est en vigor y que las capacidades de insercin, actualizacin y eliminacin del TableAdapter siguen siendo funcionales, crearemos una pgina web que permita a los usuarios ver y eliminar empleados. Sin embargo, antes de crear este tipo de pginas, primero tenemos que crear una nueva clase en la capa lgica de negocios para trabajar con los empleados del DataSet NorthwindWithSprocs. En el paso 4 crearemos una clase EmployeesBLLWithSprocs. En el paso 5, utilizaremos esta clase desde una pgina ASP.Net. Paso 4. Implementacin de la capa lgica de negocios Crear un nuevo archivo de clase en la carpeta ~/App_Code/BLL con el nombre de EmployeesWithSprocs.vb. Esta clase imita la semntica de la clase EmployeesBLL existente, solo que esta nueva clase proporciona menos mtodos y usa el DataSet NorthwindWithSprocs (En lugar del DataSet Northwind). Agrega el siguiente cdigo a la clase EmployeesBLLWithSprocs.
Imports NorthwindWithSprocsTableAdapters <System.ComponentModel.DataObject()> _ Public Class EmployeesBLLWithSprocs Private _employeesAdapter As EmployeesTableAdapter = Nothing Protected ReadOnly Property Adapter() As EmployeesTableAdapter Get If _employeesAdapter Is Nothing Then _ employeesAdapter = New EmployeesTableAdapter() End If

Return _employeesAdapter End Get End Property <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, True)> _ Public Function GetEmployees() As NorthwindWithSprocs.EmployeesDataTable Return Adapter.GetEmployees() End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteEmployee(ByVal employeeID As Integer) As Boolean Dim rowsAffected = Adapter.Delete(employeeID) 'Return true if precisely one row was deleted, otherwise false Return rowsAffected = 1 End Function End Class

La propiedad Adapter de la clase EmployeesBLLWithSprocs devuelve una instancia EmployeesTableAdapter del DataSet NorthwindWithSprocs. Esta es usada por los mtodos GetEmployees y DeleteEmployee de la clase. El mtodo GetEmployees, invoca al mtodo GetEmployees del EmployeesTableAdapter correspondiente el cual llama al procedimiento almacenado Employees_Select y devuelve sus resultados en un EmployeesDataTable. El mtodo DeleteEmployee llama de forma similar al mtodo Delete del EmployeesTableAdapter que invoca el procedimiento almacenado Employees_Delete. Paso 5. Trabajar con datos en la capa de presentacin Con la clase EmployeesBLLWithSprocs completa, estamos listos para trabajar con los datos de los empleados desde una pgina Asp.Net. Abra la pgina JOINs.aspx en la carpeta AdvancedDAL y arrastre un GridView desde la caja de herramientas hasta el diseador, estableciendo su propiedad ID en Employees. Luego desde la etiqueta inteligente del GridView, enlace el GridView a un nuevo control ObjectDataSource llamado EmployeesDataSource. Configure el ObjectDataSource para que utilice la clase EmployeesBLLWithSprocs y desde las pestaas SELECT y DELETE, asegrese que los mtodos GetEmployees y DeleteEmployee sean seleccionados desde las dos listas desplegables. Haga clic en Finalizar para completar la configuracin del ObjectDataSource.

Figura 12. Configuracin del ObjectDataSource para que utilice la clase EmployeesBLLWithSprocs

Figura 13. Haga que el ObjectDataSource utilice los mtodos GetEmployees y DeleteEmployee Visual Studio agregar un BoundField al GridView por cada una de las columnas del EmployeesDataTable. Elimine todos estos BoundFields a excepcin de Title LastName, FirstName, ManagerFirstName y ManagerLastName y cambie la propiedad HeaderText de los ltimos cuatro BoundFields a Nombre, Apellidos, Nombre del Supervisor y Apellido del Supervisor, respectivamente.

Para permitir que los usuarios eliminen a los empleados desde esta pgina, tenemos que hacer dos cosas. En primer lugar indicar al GridView para que proporcione la capacidad de eliminacin, marcando la opcin Habilitar Eliminacin desde su etiqueta inteligente. En segundo lugar, cambie el valor de la propiedad OldValuesParameterFormatString del valor fijado por el asistente del ObjectDataSource (original_{0}) a su valor predeterminado ({0}). Despus de realizar estos cambios, el marcado declarativo del GridView y del ObjectDataSource debe ser similar al siguiente:
<asp:GridView ID="Employees" runat="server" AutoGenerateColumns="False" DataKeyNames="EmployeeID" DataSourceID="EmployeesDataSource"> <Columns> <asp:CommandField ShowDeleteButton="True" /> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:BoundField DataField="LastName" HeaderText="Last Name" SortExpression="LastName" /> <asp:BoundField DataField="FirstName" HeaderText="First Name" SortExpression="FirstName" /> <asp:BoundField DataField="ManagerFirstName" HeaderText="Manager's First Name" SortExpression="ManagerFirstName" /> <asp:BoundField DataField="ManagerLastName" HeaderText="Manager's Last Name" SortExpression="ManagerLastName" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="EmployeesDataSource" runat="server" DeleteMethod="DeleteEmployee" OldValuesParameterFormatString="{0}" SelectMethod="GetEmployees" TypeName="EmployeesBLLWithSprocs"> <DeleteParameters> <asp:Parameter Name="employeeID" Type="Int32" /> </DeleteParameters> </asp:ObjectDataSource>

Pruebe la pgina accediendo a ella por medio del navegador. Como se muestra en la figura 14, la pgina mostrar una lista de cada empleado con el nombre de su supervisor (suponiendo que tiene uno).

Figura 14. El JOIN en el procedimiento almacenado Employees_Select devuelve el nombre de los supervisores Al hacer clic en el botn eliminar, se inicia el flujo de trabajo de eliminacin, que culmina en la ejecucin del procedimiento almacenado Employees_Delete. Sin embargo, el intento de la sentencia DELETE en el procedimiento almacenado produce un error debido a la violacin de restriccin de clave externa (Ver Figura 15). En concreto, cada empleado tiene uno ms registros en la tabla Orders, provocando el fracaso de la eliminacin.

Figura 15. Eliminar un empleado que tiene rdenes asociadas origina una violacin a la restriccin de clave externa Para permitir que un empleado se pueda eliminar:

Actualice la restriccin de clave externa a eliminacin en cascada Eliminar manualmente los registros de la tabla Orders del empleado que desea eliminar, o Actualizar el procedimiento almacenado Employees_Delete para eliminar primero los registros relacionados en la tabla Orders antes de eliminar el registro de Employees. Hablamos de esta tcnica en el tutorial Uso de procedimientos almacenados existentes para el TableAdapter del DataSet tipiado

Resumen Cuando se trabaja con bases de datos relacionales, es comn que las consultas saquen sus datos de varias tablas relacionadas. Las subconsultas correlacionadas y los JOINs ofrecen dos tcnicas diferentes para acceder a datos de tablas relacionadas en una consulta. En los tutoriales anteriores, hemos hecho mayor uso de las subconsultas correlacionadas, debido a que el TableAdapter no puede generar de forma automtica, las sentencias DELETE, UPDATE e INSERT de las consultas que involucran JOINs. Si bien estos valores se pueden proporcionar de forma manual, cuando se utilizan sentencias SQL ad-hoc, las personalizaciones se sobrescriben cuando el asistente del TableAdapter se haya completado. Afortunadamente, los TableAdapters creados usando procedimientos almacenados, no sufren de la misma fragilidad que los creados utilizando sentencias SQL ad-hoc. Por lo tanto es factible crear un TableAdapter cuya consulta principal utiliza un JOIN cuando se utilizan procedimientos almacenados. En este tutorial hemos visto como crear un TableAdapter. Comenzamos usando una consulta SELECT sin JOIN para la consulta principal del TableAdapter para que los correspondientes procedimientos de insercin, actualizacin y eliminacin se creen automticamente. Con la configuracin inicial del TableAdapter completa, mejoramos el SelectCommand del procedimiento almacenado para que utilice un JOIN y volvemos a ejecutar el asistente de configuracin del TableAdapter para que actualice las columnas del EmployeesDataTable. Volver a ejecutar el asistente de configuracin del TableAdapter actualiza

automticamente las columnas del EmployeesDataTable para reflejar los campos de datos devueltos por el procedimiento almacenado Employees_Select. De forma alternativa podra haber aadido estas columnas al DataTable de forma manual. Exploraremos como agregar columnas manualmente en el siguiente tutorial.

67. AGREGAR COLUMNAS ADICIONALES AL DATATABLE


Cuando usamos el asistente del TableAdapter para crear un DataSet tipiado, el DataTable correspondiente contiene las columnas devueltas por la consulta principal de la base de datos. Pero hay ocasiones cuando el DataTable necesita incluir columnas adicionales. En este tutorial aprendemos porque los procedimientos almacenados son recomendados cuando necesitamos columnas adicionales del DataTable. Introduccin Cuando agregamos un TableAdapter a un DataSet tipiado, el esquema del DataTable correspondiente es determinado por la consulta principal del TableAdapter. Por ejemplo si la columna principal devuelve los campos de datos A,B y C, el DataTable tendr tres columnas correspondientes llamadas A,B y C. Adems de su consulta principal, un TableAdapter puede incluir consultas adicionales, que devuelvan tal vez un subconjunto de datos basados en algn parmetro. Por ejemplo, adems de la consulta principal del ProductsTableAdapter, que devuelve la informacin de todos los productos, este tambin contiene mtodos como GetProductsByCategoryID(categoryID) y GetProductByProductID(productID) que recuperan informacin especfica de un producto basado en el parmetro suministrado. El modelo que tenemos del esquema del DataTable refleja la consulta principal del TableAdapter, trabaja bien si todos los mtodos del TableAdapter devuelven los mismos o menos campos de datos que los campos especificados en la consulta principal. Si un mtodo del TableAdapter necesita recuperar campos de datos adicionales, entonces en consecuencia debemos expandir el esquema del DataTable. En el tutorial Maestro/Detalle usando una lista con vietas de registros maestros y un DataList de detalles agregamos un mtodo al CategoriesTableAdapter que devuelve los campos de datos CategoryID, CategoryName y Description definidos en la consulta principal, mas NumberOfProducts, un campo de datos adicional que reporta el nmero de productos asociados con cada categora. Agregamos manualmente una nueva columna al CategoriesDataTable con el fin de capturar el valor del campo de datos NumberOfProducts de este nuevo mtodo. Como discutimos en el tutorial Cargando archivos debemos tener gran cuidado con los TableAdapters que usen sentencias SQL ad-hoc y tengan mtodos cuyos campos de datos no coinciden exactamente con la consulta principal. Si el asistente de

configuracin del TableAdapter es ejecutado nuevamente, esto actualizara todos los mtodos del TableAdapter para que su lista de campos de datos coincida con la consulta principal. En consecuencia, cualquier mtodo con listas de columnas personalizadas ser convertido a la lista de columnas de la consulta principal y no regresaran los datos esperados. Este problema no surge cuando usamos procedimientos almacenados. En este tutorial observaremos como extender el esquema del DataTable para incluir columnas adicionales. Debido a las falencias del TableAdapter cuando usamos sentencias SQL ad-hoc, en este tutorial usaremos procedimientos almacenados. Refirase a los tutoriales Creacin de nuevos procedimientos almacenados para los TableAdapter del DataSet y Uso de procedimientos almacenados existentes para los TableAdapter del DataSet tipiado para ms informacin sobre configuracin de un TableAdapter para usar procedimientos almacenados. Paso 1. Agregar una columna PriceQuartile al ProductsDataTable En el tutorial Creacin de nuevos procedimientos para los TableAdapters del DataSet tipiado creamos un DataSet tipiado llamado NorthwindWithSprocs. Este DataSet actualmente contiene dos DataTables: ProductsDataTable y EmployeesDataTable. El ProductsTableAdapter tiene los siguientes tres mtodos: GetProducts la consulta principal, devuelve todos los registros de la tabla Products. GetProductsByCategoryID(categoryID) devuelve todos los productos de una categora especfica. GetProductByProductID(productID) devuelve un producto en particular con el productID especificado. La consulta principal y los dos mtodos adicionales devuelven el mismo conjunto de campos de datos es decir todas las columnas de la tabla Products. No hay subconsultas correlacionadas o datos relacionados recuperados por JOINs de las tablas Categories o Suppliers. Por lo tanto el ProductsDataTable tiene una columna correspondiente para cada campo en la tabla Products. Para este tutorial, agregaremos un mtodo al ProductsTableAdapter llamado

GetProductsWithPriceQuartile que devuelve todos los productos. Adems de los campos

de datos por defecto, GetProductsWithPriceQuartile incluir un campo de datos PriceQuartile que indica que trimestre el precio del producto disminuyo. Por ejemplo, aquellos productos cuyos precios son los ms costosos que el 25% tendrn un valor PriceQuartile de 1, para aquellos cuyo precio disminuyo por debajo del 25% tendr un valor de 4. Sin embargo, antes de preocuparnos por la creacin del procedimiento almacenado para recuperar esta informacin, primero necesitamos actualizar el ProductsDataTable para que incluya una columna que almacene los resultados PriceQuartile cuando se use el mtodo GetProductsWithPriceQuartile. Abra el DataSet NorthwindWithSprocs y haga clic derecho sobre el ProductsDataTable. Seleccione agregar del men contextual y luego escoja columna.

Figura 1. Agregar una nueva columna al ProductsDataTable Esto agregar una nueva columna al DataTable llamada Column1 de tipo System.String. Necesitamos actualizar el nombre de esta columna a PriceQuartile y su tipo a System.Int32 ya que esta ser usada para almacenar un nmero entre 1 y 4. Seleccione la columna recin agregada en el ProductsDataTable y desde la ventana propiedades, establezca la propiedad Nombre a PriceQuartile y la propiedad DataType en System.Int32.

Figura 2. Defina las propiedades Name y DataType de la columna Como muestra la figura 2, existen propiedades adicionales que pueden ser definidas, como si los valores en una columna deben ser nicos, si la columna es una columna de incremento automtico, o si la base de datos permite o no valores NULLs y as sucesivamente. Dejaremos estas propiedades como estn establecidas por defecto. Paso 2. Creacin del mtodo GetProductsWithPriceQuartile Ahora que el ProductsDataTable ha sido actualizado para incluir la columna PriceQuartile, estamos listos para crear el mtodo GetProductsWithPriceQuartile. Comenzamos haciendo clic derecho sobre el TableAdapter y seleccionando agregar consulta del men contextual. Esto iniciara el asistente de configuracin de consultas del TableAdapter, que inicialmente nos pregunta si deseamos usar sentencias SQL adhoc o un procedimiento almacenado nuevo o existente. Como todava no tenemos un procedimiento almacenado que devuelva el dato del precio trimestral, permitiremos que el TableAdapter crear este procedimiento almacenado por nosotros. Seleccione la opcin crear nuevo procedimiento almacenado y haga clic en siguiente.

Figura 3. Indique al asistente del TableAdapter que cree un procedimiento almacenado por nosotros En la siguiente pantalla, mostrada en la figura 4, el asistente nos pregunta qu tipo de consulta deseamos agregar. Como el mtodo GetProductsWithPriceQuartile devuelve todas las columnas y registros de la tabla Products, seleccione la opcin SELECT que devuelve filas y haga clic en siguiente.

Figura 4. Nuestra consulta ser una sentencia SELECT que devuelve mltiples filas A continuacin se le pedir la consulta SELECT. Ingrese la siguiente consulta en el asistente:
SELECT ProductID, ProductName, SupplierID, CategoryID,

QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, NTILE(4) OVER (ORDER BY UnitPrice DESC) as PriceQuartile FROM Products

La consulta anterior utiliza la nueva funcin NTILE de SQL Server 2005 que divide los resultados en cuatro grupos donde los grupos son determinados por los valores UnitPrice ordenados en orden descendente. Desafortunadamente el constructor de consultas no conoce como analizar la palabra clave OVER y muestra un error cuando analiza la consulta anterior. Por lo tanto ingrese la anterior consulta directamente en el cuadro de texto del asistente sin utilizar el constructor de consultas. Nota: Para mayor informacin sobre NTILE y otras funciones de SQL Server 2005, ver Returning Ranked Results with Microsoft SQL Server 2005 y la Ranking Functions section en la SQL Server 2005 Books Online. Despus de ingresar la consulta SELECT y dar clic en siguiente nos pregunta si deseamos proporcionar un nombre para el procedimiento almacenado que acabamos de crear. Nombre el nuevo procedimiento almacenado Products_SelectWithPriceQuartile y haga clic siguiente.

Figura 5. Nombre los procedimientos Products_SelectWithPriceQuartile

Por ltimo se nos solicita nombrar los mtodos del TableAdapter. Dejaremos ambas casillas de verificacin seleccionadas Fill un DataTable y Return DataTable y nombraremos a los mtodos FillWithPriceQuartile y GetProductsWithPriceQuartile.

Figura 6. Nombre los mtodos del TableAdapter y haga clic en Finalizar Con la consulta SELECT especificada y los mtodos del TableAdapter y Procedimientos Almacenados nombrados, hacemos clic en finalizar para completar el asistente. En este punto recibiremos una advertencia o dos del asistente diciendo que el constructor o sentencias OVER SQL no es soportado. Estas advertencias pueden ser ignoradas. Despus de completar el asistente, el TableAdapter deber incluir los mtodos FillWithPriceQuartile y GetProductsWithPriceQuartile y la base de datos debe incluir un procedimiento almacenado llamado Products_SelectWithPriceQuartile. Tmese un momento para verificar que el TableAdapter realmente contiene este nuevo mtodo y que el procedimiento almacenado ha sido agregado correctamente a la base de datos. Cuando verifiquemos la base de datos, si no ve el procedimiento almacenado intente haciendo clic derecho sobre la carpeta Procedimientos Almacenados y seleccione actualizar.

Figura 7. Verifique que un nuevo mtodo ha sido agregado al TableAdapter

Figura 8. Asegrese que la base de datos contenga el procedimiento almacenado Products_SelectWithPriceQuartile Nota: Uno de los beneficios de usar procedimientos almacenados en lugar de sentencias SQL ad-hoc es que al ejecutar nuevamente el asistente de configuracin del TableAdapter no modificaremos la lista de columnas de los procedimientos almacenados. Verifique esto haciendo clic derecho sobre el TableAdapter, seleccionando la opcin configurar del men contextual para iniciar el asistente y luego

haga clic en finalizar para completarlo. Luego vaya a la base de datos y vea el procedimiento almacenado Products_SelectWithPriceQuartile. Note que su lista de columnas no ha sido modificada. Si hubiramos estado utilizando sentencias SQL adhoc, al ejecutar el asistente de configuracin del TableAdapter modificaramos esta lista de columnas de la consulta para que coincida con la lista de columnas de la consulta principal, eliminando la sentencia NTILE de la consulta usada por el mtodo GetProductsWithPriceQuartile. Cuando el mtodo GetProductsWithPriceQuartile de la capa de acceso a datos es invocado, el TableAdapter ejecuta el procedimiento almacenado Products_SelectWithPriceQuartile y agrega una fila al ProductsDataTable por cada registro devuelto. Los campos de datos devueltos por el procedimiento almacenado son asignados a las columnas del ProductsDataTable. Como hay un campo de datos PriceQuartile devuelto por el procedimiento almacenado, su valor es asignado a la columna PriceQuartile del ProductsDataTable. Para aquellos mtodos del TableAdapter cuyas consultas no devuelven el campo de datos PriceQuartile, el valor de la columna PriceQuartile es el valor especificado por su propiedad DefaultValue. Como muestra la figura 2, este valor est establecido en DBNull, por defecto. Si prefiere un valor diferente al establecido por defecto, simplemente establezca la propiedad DefaultValue acordemente. Solo asegrese que el valor por defecto es un valor valido para los DataTypes de la columna (por ejemplo System.Int32 para la columna PriceQuartile). Hasta el momento hemos desarrollado los pasos necesarios para agregar una columna adicional al DataTable. Para verificar que esta columna adicional trabaje como esperamos, crearemos una pgina ASP.Net adicional que muestre el nombre, precio y precio trimestral de cada producto. Aunque antes de realizar esto, primero necesitamos actualizar la capa lgica de negocios para incluir un mtodo que llame al mtodo GetProductsWithPriceQuartile de la DAL. Despus actualizamos la BLL en el paso 3 y luego creamos la pgina ASP.Net en el paso 4. Paso 3. Ampliar la capa lgica de negocios Antes de usar el nuevo mtodo GetProductsWithPriceQuartile en la capa de presentacin, debemos primero agregar un mtodo correspondiente a la BLL. Abra el archivo de clase ProductsBLLWithSprocs y agregue el siguiente cdigo:

<System.ComponentModel.DataObjectMethodAttribute_ (System.ComponentModel.DataObjectMethodType.Select, False)> _ Public Function GetProductsWithPriceQuartile() As NorthwindWithSprocs.ProductsDataTable Return Adapter.GetProductsWithPriceQuartile() End Function

Al igual que los otros mtodos que recuperan datos en ProductsBLLWithSprocs, el mtodo GetProductsWithPriceQuartile sencillamente llama al mtodo GetPriceWithPriceQuartile correspondiente en la DAL y devuelve sus resultados. Paso 4. Mostrar la informacin PriceQuartile en una pgina web ASP.Net Con la adicin a la BLL completa, estamos listos para crear una pgina ASP.Net que muestre el precio trimestral de cada producto. Abra la pgina AddingColumns.aspx de la carpeta AdvancedDAL y arrastre un GridView desde la barra de herramientas hasta el diseador, definiendo su propiedad ID en Products. Desde la etiqueta inteligente del GridView, enlcelo a un nuevo ObjectDataSource llamado ProductsDataSource. Configure el ObjectDataSource para que utilice el mtodo GetProductsWithPriceQuartile de la clase ProductsBLLWithSprocs. Como este ser un GridView de solo lectura, establezca sus pestaas UPDATE, INSERT y DELETE en Ninguno.

Figura 9. Configurar el ObjectDataSource para que utilice la clase ProductsBLLWithSprocs

Figura 10. Recuperar la informacin de los productos con el meto GetProductsWithPriceQuartile Despus de completar el asistente de configuracin del origen de datos, Visual Studio agregar automticamente un CheckBoxField o un BoundField al GridView para cada uno de los campos de datos devueltos por el mtodo. Uno de estos campos de datos es PriceQuartile, que es la columna que agregamos al DataTable en el paso1. Edite los campos del GridView, eliminando todos los BoundFields a excepcin de ProductName, UnitPrice y PriceQuartile. Configure el formato del BoundField UnitPrice como moneda y haga los BoundFields UnitPrice y PriceQuartile estn alineados a la derecha y centrados, respectivamente. Finalmente, actualice las propiedades Header de los BoundFields restantes a Product, Price y Price Quartile, respectivamente. Tambin seleccione la casilla de verificacin habilitar ordenacin en la etiqueta inteligente del GridView. Despus de estas modificaciones, el GridView y el marcado declarativo del ObjectDataSource lucirn como el siguiente:
<asp:GridView ID="Products" runat="server" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ProductsDataSource"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" />

<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice"> <ItemStyle HorizontalAlign="Right" /> </asp:BoundField> <asp:BoundField DataField="PriceQuartile" HeaderText="Price Quartile" SortExpression="PriceQuartile"> <ItemStyle HorizontalAlign="Center" /> </asp:BoundField> </Columns> </asp:GridView> <asp:ObjectDataSource ID="ProductsDataSource" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsWithPriceQuartile" TypeName="ProductsBLLWithSprocs"> </asp:ObjectDataSource>

La figura 11 muestra esta pgina cuando la visitamos por medio de un navegador. Note que inicialmente los productos son ordenados por su precio en orden descendiente con cada producto y su valor PriceQuartile asignado apropiadamente. Por supuesto estos datos pueden ser ordenados por otro criterio con el valor de la columna PriceQuartile de los productos rankeado con respecto al nombre (Ver Figura 12).

Figura 11. Los productos estn ordenados por sus precios

Figura 12. Los productos estn ordenados por sus nombres

Nota: Con unas pocas lneas de cdigo podramos ampliar el GridView para dar colorear las filas de los productos basados en el valor de su PriceQuartile. Podramos colorear aquellos productos en el primer trimestre con un color verde claro, aquellos del segundo trimestre con un color azul claro y as sucesivamente. Los nimo para que se tomen un momento y agreguen esta funcionalidad. Si necesita recordar cmo dar formato a un GridView, consulte el tutorial Formato personalizado basado en los datos. Un enfoque alternativo usar otro TableAdapter Como vimos en este tutorial, cuando agregamos un mtodo al TableAdapter que devuelve campos de datos que no son especificados en la consulta principal, podemos agregar al DataTable las correspondientes columnas. Sin embargo tal enfoque solo funciona bien si hay un nmero pequeo de mtodos en el TableAdapter que devuelven campos de datos diferentes y si los campos de datos alternativos no varan demasiado de la consulta principal. En lugar de agregar columnas al DataTable, puede agregar otro TableAdapter al DataSet que contenga los mtodos del primer TableAdapter y devuelva diferentes campos de datos. Para este tutorial en lugar de agregar la columna PriceQuartile al ProductsDataTable (que solo es usado el mtodo por el mtodo GetProductsWithPriceQuartile), podramos haber agregado una TableAdapter adicional al DataSet llamado ProductsWithPriceQuartileTableAdapter que es usado por el procedimiento almacenado Products_SelectWithPriceQuartile como su consulta principal. Las pginas ASP.Net necesarias para obtener informacin del producto con el precio trimestral podran usar el ProductsWithPriceQuartileTableAdapter, mientras que aquellas que no lo necesitan podran continuar usando el ProductsTableAdapter. Agregando un nuevo TableAdapter, los DataTable permanecen inmovibles y sus columnas reflejan exactamente los campos de datos devueltos por los mtodos de sus TableAdapter. Sin embargo, los TableAdapters adicionales pueden introducir tareas repetitivas y funcionalidad. Por ejemplo, si las pginas ASP.Net que muestran la columna PriceQuartile y tambin el necesitan proporcionar y soporte de insercin, tener sus actualizacin propiedades eliminacin, ProductsWithPriceQuartile necesitar

InsertCommand,

UpdateCommand

DeleteCommand

configuradas

correctamente. Aunque estas propiedades reflejan las del ProductsTableAdapter esta configuracin introduce un paso adicional. Adems ahora hay dos formas de actualizar,

eliminar y agregar un producto a la base de datos, por medio de las clases ProductsTableAdapter y ProductsWithPriceQuartileTableAdapter. La descarga para este tutorial incluye una clase ProductsWithPriceQuartileTableAdapter en el DataSet NothwindWithSprocs que muestra este enfoque alternativo. Resumen En la mayora de los escenarios, todos los mtodos en un TableAdapter devolvern el mismo conjunto de campos de datos, pero muchas veces podramos necesitar que uno o dos mtodos en particular que recuperen un campo adicional. Por ejemplo, en el tutorial Maestro/Detalle usando una lista de registros maestros con vietas y un DataList de detalles agregamos un mtodo al CategoriesTableAdapter que adems de los campos de datos de la consulta principal, devolver un campo NumberOfProducts que reporta el nmero de productos asociados con cada categora. En este tutorial observamos la adicin de un mtodo en el ProductsTableAdapter que devuelva un campo PriceQuartile adems de los campos de datos de la consulta principal. Para recuperar los campos de datos adicionales devueltos por los mtodos del TableAdapter necesitamos agregar las columnas correspondientes al DataTable. Si planeas agregar manualmente las columnas al DataTable, es recomendable que el TableAdapter utilice procedimientos almacenados. Si el TableAdapter utiliza sentencias SQL ad-hoc, si alguna vez el asistente se ejecuta nuevamente todas las listas de campos de datos se revertirn a los campos de datos devueltos por la consulta principal. Este problema no se extiende a los procedimientos almacenados, por lo cual son recomendados y usados en este tutorial.

68. TRABAJAR CON COLUMNAS CALCULADAS


Cuando creamos una tabla de base de datos, Microsoft SQL Server le permite definir columnas calculadas, cuyo valor se calcula a partir de una expresin que generalmente hace referencia a otros valores en el mismo registro de la base de datos. Estos valores de base de datos son de solo lectura, lo que requiere consideraciones especiales cuando se trabaja con TableAdapters. En este tutorial aprenderemos a afrontar los desafos planteados con las columnas calculadas. Introduccin Microsoft SQL Server permite las columnas calculadas, que son columnas cuyos valores se calculan a partir de una expresin que por lo general hace referencia a valores de otras columnas de la misma tabla. Como ejemplo, un modelo de datos de seguimiento de tiempos podra tener una tabla denominada ServiceLog con las columnas ServicePerformed, EmployeeID, Rate y Duration, entre otras. Aunque la cantidad debida por el punto de servicio (velocidad* tiempo de duracin) puede ser calculada a travs de una pgina web o interfaz de programacin, podra ser til incluir una columna en la tabla ServiceLog llamada AmountDue que reporte est informacin. Esta columna se puede crear como una columna normal, pero tendra que ser actualizada en todo momento, si los valores de la columna Rate o Duration cambian. Un mejor enfoque seria hacer de la columna AmountDue una columna calculada mediante la expresin Rate*Duration. Si lo hace hara que SQL Server calcule automticamente el valor de la columna AmountDue cada vez que se haga referencia a ella en una consulta. Como el valor de una columna calculada est determinado por una expresin, estas columnas son de solo lectura y por lo tanto no pueden tener valores asignados a ellos en las sentencias INSERT y UPDATE. Sin embargo cuando las columnas son parte de la consulta principal del TableAdapter que utiliza sentencias SQL ad-hoc, son incluidas automticamente en las sentencias INSERT y UPDATE que son generadas automticamente. En consecuencia, las consultas INSERT y UPDATE del TableAdapter y las propiedades InsertCommand y UpdateCommand se deben actualizar para eliminar las referencias a las columnas calculadas. Uno de los retos de la utilizacin de columnas calculadas con un TableAdapter que utilice sentencias SQL ad-hoc, es que las consultas INSERT y UPDATE del TableAdapter se generan automticamente cada vez que el asistente de configuracin del

TableAdapter

se completa.

Por

lo

tanto, las

columnas

calculadas removidas

manualmente de las consultas INSERT y UPDATE volvern a aparecer si el asistente se vuelve a ejecutar. Aunque los procedimientos almacenados no sufren de esta fragilidad, tienen sus propias peculiaridades de las que nos ocuparemos en el paso 3. En este tutorial agregaremos una columna calculada a la tabla Suppliers en la base de datos Northwind y luego crearemos el TableAdapter correspondiente para trabajar con esta tabla y su columna calculada. Haremos que nuestro TableAdapter utilice procedimientos almacenados en lugar de sentencias SQL ad-hoc para que nuestras personalizaciones no se pierdan cuando se utilice el asistente de configuracin del TableAdapter. Paso 1. Agregar una columna calculada a la tabla Suppliers La base de datos Northwind no tiene columnas calculadas, as que tendremos que aadir una nosotros mismos. Para este tutorial agregaremos una columna calculada a la tabla Suppliers llamada FullContactName que devuelve el nombre del contacto, el titulo y la empresa para la que trabaja en el siguiente formato: ContactName( ContactTitle, CompanyName). Esta columna puede ser usada en reportes cuando mostramos informacin acerca de los proveedores. Comencemos abriendo la definicin de la tabla Suppliers haciendo clic derecho sobre la tabla Suppliers en el explorador de servidores y elija Abrir definicin de tabla en el men contextual. Esto mostrara las columnas de la tabla y sus propiedades, como su tipo de datos, si permiten Nulls y as sucesivamente. Para agregar una columna calculada, comience escribiendo el nombre de la columna en la definicin de tabla. Luego introduzca su expresin en el cuadro de texto (Formula) en la seccin especificacin de columna calculada en la ventana de propiedades de columna (Ver Figura 1). Nombre a la columna calculada FullContactName y utilice la siguiente expresin:
ContactName + ' (' + CASE WHEN ContactTitle IS NOT NULL THEN ContactTitle + ', ' ELSE '' END + CompanyName + ')'

Note que las cadenas en SQL pueden ser concatenadas usando el operador +. La sentencia CASE puede ser usada como un condicional en un lenguaje de programacin tradicional. En la expresin anterior la sentencia SELECT puede ser leda como: Si

ContactTitle no es Null entonces el valor de salida de ContactName debe ser concatenado con una coma, de lo contrario no emita nada. Para ms informacin sobre la utilidad de la declaracin Case, ver el poder de las sentencias CASE. Nota: En lugar de usar una sentencia CASE aqu, de forma alternativa podramos usar ISNULL(ContactTitle,). ISNULL(checkExpression, replacementValue) devuelve checkExpression si no es Null, o de lo contrario devuelve replacementValue. Aunque ISNULL o CASE trabajarn igual en esta instancia, hay escenarios ms complicados donde la flexibilidad de la sentencia CASE no puede ser igualada por ISNULL. Despus de agregar esta columna calculada la pantalla debe ser similar a la captura de pantalla de la Figura 1.

Figura 1. Agregar una columna calculada de nombre FullContactName a la tabla Suppliers Despus de nombrar la columna calculada e ingresar su expresin, guarde los cambios en la tabla haciendo clic en el icono guardar de la barra de herramientas, pulsando Ctrl+S, o yendo al men, archivo y seleccionando Guardar Suppliers.

Al guardar la tabla debemos actualizar el explorador de servidores, incluyendo la columna recin agregada en la lista de columnas de la tabla Suppliers. Por otra parte, la expresin escrita en el cuadro de texto (Formula) se ajusta automticamente a una expresin equivalente que sustituye un espacio en blanco innecesario rodeando los nombres de las columnas entre corchetes ([]), e incluye entre parntesis para mostrar de manera ms explcita el orden de las operaciones:
(((([ContactName]+' (')+case when [ContactTitle] IS NOT NULL then [ContactTitle]+', ' else '' end)+[CompanyName])+')')

Para mayor informacin sobre las columnas calculadas en Microsoft SQL Server, refirase a la documentacin tcnica. Tambin verifique como especificar columnas calculadas para un tutorial paso a paso a travs de la creacin de columnas calculadas. Nota: Por defecto las columnas calculadas no son almacenadas fsicamente en la tabla pero en su lugar son calculadas cada vez que se hace referencia a ellas en una consulta. Sin embargo, seleccionando la casilla de verificacin IsPersited indicamos a SQL Server que almacene fsicamente la columna calculada en la tabla. Hacer esto permite crear un ndice en la columna calculada, el cual puede mejorar el rendimiento de las consultas que usan el valor de la columna calculada en sus clausulas WHERE. Ver Crear ndices en las columnas calculadas para mayor informacin. Paso 2. Ver los valores de las columnas calculadas Antes de empezar a trabajar en la capa de acceso a datos, nos tomaremos un minuto para ver los valores de FullContactName. Desde el explorador de servidores, haga clic derecho en la tabla Suppliers y seleccione Nueva consulta del men contextual. Esto abrir la ventana de consultas que nos solicita escoger que tablas incluir en la consulta. Agregue la tabla Suppliers y haga clic en cerrar. Luego seleccione las columnas CompanyName, ContactName, ContactTitle y FullContactName de la tabla Suppliers. Finalmente haga clic en el icono del signo de admiracin rojo en la barra de herramientas para ejecutar la consulta y ver los resultados. Como muestra la figura 2, los resultados incluyen FullContactName, el cual muestra las columnas CompanyName, ContactName y ContactTitle usando el formato ContactName(ContactTitle, CompanyName).

Figura 2. El FullContactName utiliza el formato ContactName(ContactTitle, CompanyName) Paso 3. Agregar el SuppliersTableAdapter a la capa de acceso a datos Con el fin de trabajar con la informacin del proveedor en nuestra aplicacin primero necesitamos crear un TableAdapter y un DataTable en nuestra DAL. Idealmente esto debe realizarse a travs del seguimiento de pasos examinados en tutoriales anteriores. Sin embargo trabajar con columnas calculadas introduce una pequea fragilidad que merece discusin. Si est usando un TableAdapter que utilice sentencias SQL ad-hoc, puede

sencillamente incluir la columna calculada en la consulta principal del TableAdapter empleando el asistente de configuracin del TableAdapter. Sin embargo esto generar automticamente las sentencias INSERT y UPDATE que incluirn la columna calculada. Si intenta ejecutar uno de estos mtodos, se generar un SqlException con el mensaje La columna ColumnName no puede ser modificada porque es una columna calculada o es el resultado de una operacin JOIN. Aunque la sentencia INSERT y UPDATE puede ser ajustada manualmente por medio de las propiedades InsertCommand y UpdateCommand del TableAdapter, estas personalizaciones se perdern cuando se ejecute nuevamente el asistente de configuracin del TableAdapter. Debido a la fragilidad de los TableAdapters que utilizan sentencias SQL ad-hoc, es recomendable que usemos procedimientos almacenados cuando trabajamos con columnas calculadas. Si est utilizando procedimientos almacenados existentes,

sencillamente configuremos el TableAdapter como discutimos en el tutorial Usar procedimientos almacenados existentes para los TableAdapters del DataSet tipiado. Sin embargo si hace que el asistente del TableAdapter cree los procedimientos almacenados por usted, es importante que inicialmente omita cualquier columna calculada en la consulta principal. Si incluye una columna calculada en la consulta principal, el asistente de configuracin del TableAdapter le informar una vez haya finalizado, que no se puede crear el procedimiento almacenado correspondiente. En resumen tenemos que configurar inicialmente el TableAdapter usando una consulta principal libre de columnas calculadas y luego actualizar manualmente el procedimiento almacenado correspondiente y el SelectCommand del TableAdapter para incluir la columna calculada. Este enfoque es similar a uno usado en el tutorial Actualizar el TableAdapter para usar JOINs. Para este tutorial, agregaremos un nuevo TableAdapter y haremos que cree automticamente los procedimientos almacenados por nosotros. Por consiguiente omitimos inicialmente la columna calculada FullContactName de la consulta principal. Comenzamos abriendo el DataSet NorthwindWithSprocs en la carpeta

~/App_Code/DAL. Haga clic derecho en el diseador y desde el men contextual, seleccione agregar un nuevo TableAdapter. Esto iniciara el asistente de configuracin del TableAdapter. Especifique la base de datos con la cual consultaremos los datos (NORTHWNDConnectionString desde Web.Config) y haga clic en siguiente. Como aun no hemos creado ningn procedimiento almacenado para consultar o modificar la tabla Suppliers, seleccione la opcin crear nuevo procedimiento almacenado para que el asistente lo cree por nosotros y haga clic en siguiente.

Figura 3. Seleccione la opcin crear nuevos procedimientos almacenados El paso siguiente nos solicita la consulta principal. Ingrese la siguiente consulta, la cual devuelve las columnas SupplierID, CompanyName, ContactName y ContactTitle para cada proveedor. Tenga en cuenta que esta consulta omite a propsito la columna calculada (FullContactName); actualizaremos el procedimiento almacenado correspondiente para incluir esta columna en el paso 4.
SELECT SupplierID, CompanyName, ContactName, ContactTitle FROM Suppliers

Despus de ingresar la consulta principal y presionar siguiente, el asistente nos permitir nombrar los cuatro procedimientos almacenados que generar. Llmelos Suppliers_Select, Suppliers_Insert, Suppliers_Update y Supplier_Delete, como muestra la figura 4.

Figura 4.Personalice los nombres de los procedimientos almacenados generados automticamente El paso siguiente del asistente nos permite nombrar los mtodos del TableAdapter y especificar el enfoque usado para acceder y actualizar los datos. Dejaremos las tres casillas de verificacin seleccionadas, pero cambiaremos el nombre del mtodo GetData a GetSuppliers. Haga clic en finalizar para completar el asistente.

Figura 5. Cambie el nombre del mtodo GetData a GetSuppliers

Luego de hacer clic en finalizar, el asistente crear los cuatro procedimientos almacenados y agregara el DataTable y el TableAdapter correspondiente al DataSet tipiado. Paso 4. Incluir la columna calculada en la consulta principal del TableAdapter Ahora necesitamos actualizar el TableAdapter y el DataTable creado en el paso 3 para incluir la columna calculada FullContactName. Esto involucra dos pasos: 1. Actualizar el procedimiento almacenado Suppliers_Select para devolver la columna calculada FullContactName, y 2. Actualizar el DataTable para incluir una columna FullContactName correspondiente. Comience navegando por el explorador de servidores y profundice en la carpeta Procedimientos Almacenados. Abra el procedimiento almacenado Suppliers_Select y actualice la consulta SELECT para que incluya la columna calculada FullContactName:
SELECT SupplierID, CompanyName, ContactName, ContactTitle, FullContactName FROM Suppliers

Guarde los cambios del procedimiento almacenado dando clic en el icono guardar en la barra de herramientas, presione Ctrl+S o seleccione la opcin guardar Suppliers-Select del men Archivo. Luego regrese al diseador del DataSet, haga clic derecho sobre el

SuppliersTableAdapter y escoja configurar en el men contextual. Tenga en cuenta que el procedimiento Suppliers_Select ahora incluye la columna FullContactName en su coleccin DataColumns.

Figura 6. Ejecute el asistente de configuracin del TableAdapter para actualizar las columnas del DataTable Haga clic en Finalizar para completar el asistente. Esto agregar automticamente una columna correspondiente al SuppliersDataTable. El asistente del TableAdapter es suficientemente inteligente para detectar que la columna FullContactName es una columna calculada y por lo tanto de solo lectura. Por consiguiente establece la propiedad ReadOnly de la columna en True. Para verificar esto, seleccione la columna del SuppliersDataTable y luego vaya a la ventana propiedades (Ver Figura 7). Tenga en cuenta que las propiedades DataType y MaxLength de la columna tambin se establecen acordemente.

Figura 7. La columna FullContactName est marcada como de solo-lectura Paso 5. Agregar el mtodo GetSupplierBySupplierID al TableAdapter Para este tutorial crearemos una pgina ASP.Net que muestre los proveedores en un GridView actualizable. En tutoriales anteriores hemos actualizado un solo registro desde la capa lgica de negocios para recuperar ese registro en particular de la DAL como un DataTable fuertemente tipiado, actualizando sus propiedades y luego enviamos el DataTable actualizado de regreso a la DAL para que propague los cambios a la base de datos. Para realizar esto el primer paso, recuperar el registro que est siendo actualizado desde la DAL, primero necesitamos agregar el mtodo GetSupplierBySupplierID(supplierID) de la DAL. Haga clic derecho sobre el SuppliersTableAdapter en el diseador del DataSet y seleccione la opcin agregar consulta en el men contextual. Como hicimos en el paso 3, dejaremos que el asistente genere un nuevo procedimiento almacenado por nosotros, seleccionando la opcin Crear nuevo procedimiento almacenado (refirase de nuevo a la figura 3 para una captura de pantalla de este paso del asistente). Como este devolver un registro con mltiples columnas, indique que desea usar una consulta SQL que es una consulta SQL que devuelve filas y haga clic en siguiente.

Figura 8. Seleccione la opcin SELECT que devuelve filas El paso siguiente nos solicitara la consulta que usara este mtodo. Ingrese la siguiente sentencia, que devuelve los mismos campos de datos que la consulta principal pero para un proveedor en particular.
SELECT SupplierID, CompanyName, ContactName, ContactTitle, FullContactName FROM Suppliers WHERE SupplierID = @SupplierID

La siguiente pantalla nos pregunta el nombre del procedimiento almacenado que se generar automticamente. Nombre este procedimiento almacenado Suppliers_SelectBySupplierID y haga clic en siguiente.

Figura 9. Nombre el procedimiento almacenado Suppliers_SelectBySupplierID

Por ltimo el asistente nos preguntar por el enfoque de acceso a datos y los nombres de los mtodos que usa el TableAdapter. Dejaremos ambas casillas seleccionadas, pero cambiaremos el nombre de los mtodos FillBy y GetDataBy por FillBySupplierID y GetBySupplierId, respectivamente.

Figura 10. Nombre los mtodos del TableAdapter FillBySupplierID y GetSupplierBySupplierID Haga clic en finalizar para completar el asistente. Paso 6. Creacin de la capa lgica de negocios Antes de crear una pgina ASP.Net que utilice la columna calculada creada en el paso 1, primero necesitamos agregar los mtodos correspondientes en la BLL. Nuestra pgina ASP.Net la cual crearemos en el paso 7, permitir a los usuarios visualizar y actualizar la informacin de los proveedores. Por lo tanto necesitamos que nuestra BLL proporcione como mnimo un mtodo que obtenga todos los proveedores y otro que actualice un proveedor en particular. Creamos un nuevo archivo de clase llamado SuppliersBLLWithSprocs en la carpeta ~/App_Code/BLL y agregamos el siguiente cdigo:
Imports NorthwindWithSprocsTableAdapters <System.ComponentModel.DataObject()> Public Class SuppliersBLLWithSprocs Private _suppliersAdapter As SuppliersTableAdapter = Nothing Protected ReadOnly Property Adapter() As SuppliersTableAdapter Get

If _suppliersAdapter Is Nothing Then _ suppliersAdapter = New SuppliersTableAdapter() End If Return _suppliersAdapter End Get End Property <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Select, True)> _ Public Function GetSuppliers() As NorthwindWithSprocs.SuppliersDataTable Return Adapter.GetSuppliers() End Function <System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Update, True)> _ Public Function UpdateSupplier(companyName As String, contactName As String, _ contactTitle As String, supplierID As Integer) As Boolean Dim suppliers As NorthwindWithSprocs.SuppliersDataTable = _ Adapter.GetSupplierBySupplierID(supplierID) If suppliers.Count = 0 Then ' no matching record found, return false Return False End If Dim supplier As NorthwindWithSprocs.SuppliersRow = suppliers(0) supplier.CompanyName = companyName If contactName Is Nothing Then supplier.SetContactNameNull() Else supplier.ContactName = contactName End If If contactTitle Is Nothing Then supplier.SetContactTitleNull() Else supplier.ContactTitle = contactTitle End If ' Update the product record Dim rowsAffected As Integer = Adapter.Update(supplier) ' Return true if precisely one row was updated, otherwise false Return rowsAffected = 1 End Function End Class

Como las otras clases BLL, SuppliersBLLWithSprocs tiene una propiedad Protected Adapter que devuelve una instancia de la clase SuppliersTableAdapter junto con dos mtodos Public: GetSuppliers y UpdateSupplier. El mtodo GetSuppliers llama y recupera el SuppliersDataTable devuelto por el mtodo GetSupplier correspondiente en la capa de acceso a datos. El mtodo UpdateSupplier recupera la informacin de un proveedor en particular que est siendo actualizado por medio de un llamado al mtodo GetSupplierBySupplierID(supplierID). Este luego actualiza las propiedades CategoryName, ContactName y ContactTitle y realiza estos cambios en la base de datos llamando al mtodo Update de la capa de acceso a datos, pasando el objeto SuppliersRow modificado. Nota: A excepcin de SupplierID y CompanyName, todas las columnas en la tabla Suppliers permiten valores NULLs. Por lo tanto si pasamos parmetros contactName o contactTitle iguales a Nothing necesitamos establecer las propiedades ContactName y ContactTitle a un valor de base de datos NULL usando los mtodos SetContactNameNull y SetContactTitleNull, respectivamente. Paso 7. Trabajando con la columna calculada desde la capa de presentacin: Con la columna calculada agregada a la tabla Suppliers y la DAL y BLL actualizadas acordemente, estamos listos para construir una pgina ASP.Net que trabaje con la columna calculada FullContactName. Comenzamos abriendo la pgina ComputedColumns.aspx en la carpeta AdvancedDAL y arrastramos un GridView desde la barra de herramientas al diseador. Establezca la propiedad ID del GridView en Suppliers y desde su etiqueta inteligente enlcelo a un nuevo ObjectDataSource llamado SuppliersDataSource. Configure el ObjectDataSource para que use la clase SuppliersBLLWithSprocs que agregamos en el paso 6 y haga clic en Siguiente.

Figura 11. Configure el ObjectDataSource para que utilice la clase SuppliersBLLWithSprocs Hay solamente dos mtodos definidos en la clase SuppliersBLLWithSprocs: GetSuppliers y UpdateSupplier. Asegrese que estos dos mtodos estn especificados en las pestaas SELECT y UPDATE respectivamente y luego haga clic en Finalizar para completar la configuracin del ObjectDataSource. Al finalizar el asistente de configuracin del ObjectDataSource, Visual Studio agregar un BoundField para cada uno de los campos de datos devueltos. Remueva el Boundfield SupplierID y cambie la propiedad HeaderText de los BoundFields CompanyName, ContactName, ContactTitle y FullContactName a Company, Contact Name, Title y Full Contact Name respectivamente. Desde la etiqueta inteligente, seleccione la casilla de verificacin Habilitar Edicin para habilitar las capacidades de edicin predefinidas del GridView. Adems de agregar los BoundFields al GridView, finalizar el asistente de origen de datos tambin ocasiona que la propiedad OldValuesParameterFormatString se establezca en original_{0}. Cambie esta definicin a su valor por defecto {0}. Despus de realizar esas modificaciones al GridView y ObjectDataSource, su marcado declarativo lucir de forma similar al siguiente:
<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False" DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource">

<Columns> <asp:CommandField ShowEditButton="True" /> <asp:BoundField DataField="CompanyName" HeaderText="Company" SortExpression="CompanyName" /> <asp:BoundField DataField="ContactName" HeaderText="Contact Name" SortExpression="ContactName" /> <asp:BoundField DataField="ContactTitle" HeaderText="Title" SortExpression="ContactTitle" /> <asp:BoundField DataField="FullContactName" HeaderText="Full Contact Name" SortExpression="FullContactName" ReadOnly="True" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="SuppliersDataSource" runat="server" SelectMethod="GetSuppliers" TypeName="SuppliersBLLWithSprocs" UpdateMethod="UpdateSupplier"> <UpdateParameters> <asp:Parameter Name="companyName" Type="String" /> <asp:Parameter Name="contactName" Type="String" /> <asp:Parameter Name="contactTitle" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> </UpdateParameters> </asp:ObjectDataSource>

Luego visite la pgina por medio de un navegador. Como muestra la figura 12, cada proveedor es mostrado en un GridView que incluye la columna FullContactName, cuyo valor es sencillamente la concatenacin de las otras tres columnas formateadas como ContactName (ContactTitle, CompanyName).

Figura 12. Cada proveedor es mostrado en el GridView Dando clic en el botn editar de un proveedor en particular generamos una devolucin de datos y hacemos que esa fila se presente en su interfaz de edicin (Ver Figura 13). La primeras tres columnas presentan por defecto en su interface de edicin un control TextBox cuya propiedad Text tiene establecido el valor del campo de datos. Sin embargo, la columna FullContactName permanece como texto. Cuando los BoundFields fueron agregados al GridView para completar el asistente de configuracin de origen de datos, la propiedad ReadOnly del BoundField FullContactName fue establecida en True, por lo tanto la columna FullContactName correspondiente en el SuppliersDataTable tiene su propiedad ReadOnly establecida en True. Como vimos en el paso 4, la propiedad ReadOnly de FullContactName fue establecida en True porque el TableAdapter detecto que la columna era una columna calculada.

Figura 13. La columna FullContactName no es editable Continuemos y actualice el valor de una o ms columnas editables y haga clic en actualizar. Note como el valor de FullContactName es actualizado automticamente para reflejar el cambio.

Nota: El GridView actualmente usa BoundFields para los campos editables originando la interfaz de edicin por defecto. Como el campo CompanyName es requerido, deber convertirse en un TemplateField que incluya un RequiredFieldValidator. Dejaremos esto como ejercicio para el lector interesado. Consulte el tutorial Agregar controles de validacin para las interfaces de edicin e insercin para seguir las instrucciones paso a paso sobre convertir un BoundField en un TemplateField y agregar controles de validacin. Resumen Cuando definimos el esquema para una tabla, Microsoft SQL Server permite la inclusin de columnas calculadas. Estas columnas cuyos valores son calculados desde una expresin que usualmente referencia los valores de otras columnas en el mismo registro. Como los valores para las columnas calculadas estn basados en una expresin, solamente son de lectura y no puede asignrsele un valor en una sentencia UPDATE e INSERT. Esto produce desafos cuando usamos una columna calculada en la consulta principal de un TableAdapter que trata de generar automticamente las sentencias INSERT, UPDATE y DELETE correspondientes. En este tutorial discutimos tcnicas para eludir los desafos planteados por las columnas calculadas. Particularmente usamos procedimientos almacenados en nuestro TableAdapter para eludir las debilidades inherentes en los TableAdapters que usan sentencias SQL ad-hoc. Cuando creamos procedimientos almacenados con el asistente del TableAdapter es importante que inicialmente omitamos cualquier columna calculada en la consulta principal porque su presencia evitara la modificacin de datos de los procedimientos almacenados que estn siendo generados. Luego que el TableAdapter ha sido configurado inicialmente, su procedimiento almacenado SelectCommand puede ser modificado para incluir cualquier columna calculada.

69. CONFIGURACION DE LA CONEXION DE LA CAPA DE ACCESO A DATOS Y DEFINICIONES A NIVEL DE COMANDO


Los TableAdapters dentro de un DataSet tipiado toman automticamente la conexin a la base de daros, generan comandos y poblan un DataTable con los resultados. Sin embargo hay ocasiones en que deseamos tomar estos detalles por nosotros mismos y en este tutorial aprenderemos como acceder a la conexin de la base de datos y a las definiciones a nivel de comando en el TableAdapter. Introduccin A travs de esta serie de tutoriales hemos usado DataSets tipiados para implementar la capa de acceso a datos y los objetos de negocio de nuestra arquitectura en capas. Como discutimos en el primer tutorial, los DataTables del DataSet tipiado sirven como depsitos de datos mientras que los TableAdapters actan como envoltorios para comunicarse con la base de datos y as recuperar y modificar los datos subyacentes. Los TableAdapters encapsulan la complejidad involucrada en trabajar con la base de datos y guarda por nosotros el cdigo escrito para conectarse a la base de datos, generar un comando o llenar un DataTable con los resultados. Sin embargo muchas veces necesitamos excavar en las profundidades del TableAdapter y escribir cdigo que trabaje directamente con los objetos ADO.Net. Por ejemplo en el tutorial Envolver modificaciones de base de datos dentro de una transaccin agregamos mtodos al TableAdapter para iniciar, realizar y devolver transacciones ADO.Net. Estos mtodos usan un objeto SqlTransaction interno creado manualmente que fue asignado a los comandos SqlCommand del TableAdapter. En este tutorial examinaremos como acceder a la conexin de la base de datos y a las definiciones a nivel de comando en el TableAdapter. En concreto, agregaremos funcionalidad al ProductsTableAdapter que nos permite acceder a la cadena de conexin subyacente y a la definicin de tiempo de espera de ejecucin de los comandos. Trabajar con datos usando ADO.Net El Microsoft .Net Framework contiene una gran cantidad de clases diseadas especficamente para trabajar con datos. Estas clases encontradas dentro del espacio de nombres System.Data, son conocidas como clases ADO.Net. Algunas de estas clases

bajo el marco de ADO.Net estn ligadas a un proveedor de datos en particular. Puede pensar en un proveedor de datos como un canal de comunicacin que permite que la informacin fluya entre las clases ADO.Net y los datos subyacentes almacenados. Los proveedores generales son OleDb y ODBC que son proveedores que estn especficamente diseados para un sistema de base de datos en concreto. Por ejemplo, aunque es posible conectar una base de datos Microsoft SQL Server usando el proveedor OleDb, el proveedor SqlClient es mucho ms eficiente ya que fue diseado y optimizado especficamente para SQL Server. Cuando accedemos mediante programacin a los datos, es comnmente usado el siguiente enfoque: 1. Establecer una conexin a la base de datos. 2. Generar un comando. 3. Para consultas SELECT, se trabaja con los registros resultantes. Hay clases ADO.Net separadas para realizar cada uno de estos pasos. Por ejemplo, para conectar una base de datos que usa el proveedor SqlClient, se usa la clase SqlConnection. Para generar un comando INSERT, UPDATE, DELETE o SELECT a la base de datos, usamos la clase SqlCommand. A excepcin del tutorial Envolver Modificaciones de base de datos en una transaccin, no hemos tenido que escribir cdigo ADO.Net de bajo nivel porque el TableAdapter genera automticamente el cdigo por nosotros para incluir la funcionalidad necesaria para conectarse a la base de datos, generar comandos, recuperar datos y poblar los datos en los DataTables. Sin embargo muchas veces necesitamos personalizar estas definiciones de bajo nivel. En los siguientes pasos, examinaremos como aprovechar los objetos ADO.Net usados internamente por los TableAdapters. Paso 1. Examinar con la propiedad Connection Cada TableAdapter tiene una propiedad Connection que especifica la informacin de conexin a la base de datos. El tipo de datos de esta propiedad y su valor ConnectionString son determinados por las selecciones hechas en el asistente de configuracin del TableAdapter. Recuerde que cuando agregamos un TableAdapter al DataSet tipiado este asistente nos pregunta por el origen de la base de datos (Ver Figura 1). La lista desplegable en este primer paso incluye aquellas bases de datos

especificadas en los archivos de configuracin as como cualquier otra base de datos que este en las conexiones de datos del Explorador de Servidores. Si la base de datos que desea usar no existe en la lista desplegable, puede especificar una nueva conexin de base de datos presionando el botn Nueva Conexin y proporcionando la informacin de conexin necesaria.

Figura 1. El primer paso del asistente de configuracin del TableAdapter Tommonos un momento para inspeccionar el cdigo de la propiedad Connection del TableAdapter. Como vimos en el tutorial Creacin de una capa de acceso datos podemos ver el cdigo generado por el TableAdapter en la ventana Ver Clase, profundizando en la clase apropiada y luego presionando dos veces sobre el nombre del miembro. Navegue a la ventana Ver Clases, vaya al men Ver y seleccione Ver Clase (o presionado Ctrl+Shift+C). En la parte superior de la ventana Ver Clases, profundice en el espacio de nombres NorthwindTableAdapters y seleccione la clase ProductsTableAdapter. Esto le mostrara los miembros de ProductsTableAdapter en la parte superior de Ver Clases, como se muestra en la figura 2. Presionando dos veces la propiedad Connection veremos su cdigo.

Figura 2. Presione dos veces sobre la propiedad Connection en la ventana Ver Clase para visualizar su cdigo generado La propiedad Connection del TableAdapter y el otro cdigo relacionado con la conexin es el siguiente:
Private _connection As System.Data.SqlClient.SqlConnection Private Sub InitConnection() Me._connection = New System.Data.SqlClient.SqlConnection Me._connection.ConnectionString = _ ConfigurationManager.ConnectionStrings("NORTHWNDConnectionString").ConnectionString End Sub Friend Property Connection() As System.Data.SqlClient.SqlConnection Get If (Me._connection Is Nothing) Then Me.InitConnection

End If Return Me._connection End Get Set Me._connection = value If (Not (Me.Adapter.InsertCommand) Is Nothing) Then Me.Adapter.InsertCommand.Connection = value End If If (Not (Me.Adapter.DeleteCommand) Is Nothing) Then Me.Adapter.DeleteCommand.Connection = value End If If (Not (Me.Adapter.UpdateCommand) Is Nothing) Then Me.Adapter.UpdateCommand.Connection = value End If Dim i As Integer = 0 Do While (i < Me.CommandCollection.Length) If (Not (Me.CommandCollection(i)) Is Nothing) Then CType(Me.CommandCollection(i), _ System.Data.SqlClient.SqlCommand).Connection = value End If i = (i + 1) Loop End Set End Property

Cuando la clase del TableAdapter es creada, el miembro variable _connection es igual a Nothing. Cuando se accede a la propiedad Connection, esta primero verifica si el miembro variable _connection ha sido creado. Si no lo ha hecho, se invoca al mtodo InitConnection, el cual crea _connection y establece su propiedad ConnectionString al valor de la cadena de conexin especificada en el primer paso del asistente de configuracin del TableAdapter. La propiedad Connection tambin puede ser asignada al objeto SqlConnection. Haciendo esto asociamos el nuevo objeto SqlConnection con cada objeto SqlCommand del TableAdapter. Paso 2. Exponiendo las definiciones a nivel de conexin

La informacin de conexin debe permanecer encapsulada dentro del TableAdapter y no debe ser accesible por otras capas en la arquitectura de la aplicacin. Sin embargo, hay muchos escenarios donde la informacin a nivel de conexin del TableAdapter debe ser accesible o personalizable por una consulta, un usuario o una pgina ASP.Net. Ampliaremos el ProductsTableAdapter del DataSet Northwind para que incluya una propiedad ConnectionString que pueda ser usada por la capa lgica de negocios para leer o cambiar la cadena de conexin usada por el TableAdapter. Nota: Una cadena de conexin es una cadena que especifica la informacin de conexin de la base de datos, como el proveedor que usa, la localizacin de la base de datos, las credenciales de autentificacin y otras definiciones relacionadas con la base de datos. Para una lista de los enfoques de cadenas de conexin usados por una variedad de datos almacenados y proveedores, vea ConnectionStrings.com Como discutimos en el tutorial Creacin de una capa de acceso a datos, las clases generadas por el DataSet tipiado pueden ser ampliadas para que usen clases parciales. Primero cree una nueva subcarpeta llamada ConnectionAndCommandSettings dentro de la carpeta ~/App_Code/DAL.

Figura 3. Agregue una subcarpeta llamada ConnectionAndCommandSettings Agregue cdigo: un nuevo archivo y clase escriba el llamado siguiente

ProductsTableAdapter.ConnectionAndCommandSettings.vb

Namespace NorthwindTableAdapters Partial Public Class ProductsTableAdapter Public Property ConnectionString() As String Get Return Me.Connection.ConnectionString End Get Set(ByVal value As String) Me.Connection.ConnectionString = value End Set End Property End Class End Namespace

Esta clase parcial agrega una propiedad Public llamada ConnectionStrings a la clase ProductsTableAdapter que permite a cualquier capa leer o actualizar la cadena de conexin para la conexin subyacente del TableAdapter. Con esta clase parcial creada (y guardada), abra la clase ProductsBLL. Vaya a uno de los mtodos existentes y escriba Adapter, luego presione la clave periodo para abrir IntelliSense. Deber ver la nueva propiedad ConnectionString valida en IntelliSense, lo que significa que puede acceder a ella mediante programacin y ajustar su valor desde la BLL Exponiendo el objeto Connection completo Esta clase parcial expone solamente una propiedad del objeto de conexin subyacente: ConnectionString. Si desea hacer que el objeto completo de conexin sea vlido mas all de las fronteras del TableAdapter, puede cambiar el nivel de proteccin de la propiedad Connection. El cdigo generado automticamente que examinamos en el paso 1 muestra que la propiedad Connection del TableAdapter est marcada como Friend, lo que significa que solamente pueden acceder a ella clases del mismo ensamblador. Sin embargo esto puede ser cambiado por medio de la propiedad ConnectionModifier del TableAdapter. Abra el DataSet tipiado Northwind, presione sobre el encabezado del

ProductsTableAdapter en el diseador y navegue a la ventana propiedades. Aqu veremos el ConnectionModifier establecido en su valor por defecto, Assembly. Para

hacer que la propiedad Connection sea vlida fuera del ensamblador del DataSet tipiado, cambiamos la propiedad ConnectionModifier a Public.

Figura 4. El nivel de accesibilidad de la propiedad Connection puede ser configurada por medio de la propiedad ConnectionModifier Guarde el DataSet y regrese a la clase ProductsBLL. Igual que antes, vamos a uno de los mtodos existentes y escribimos Adapter, luego presionamos la clave periodo para habilitar el IntelliSense. Esta lista incluir una propiedad Connection, lo que significa que ahora podemos leer mediante programacin o asignar cualquier definicin a nivel de conexin desde la BLL. Paso 3. Examinar las propiedades relacionadas con los comandos Un TableAdapter consiste de una consulta principal que por defecto genera automticamente sus sentencias INSERT, UPDATE y DELETE. Las sentencias INSERT, UPDATE y DELETE de esta consulta principal son implementadas en el cdigo del TableAdapter como un objeto adaptador de datos ADO.Net por medio de la propiedad Adapter. Al igual que con su propiedad Connection, el tipo de la propiedad Adapter est determinado por el proveedor de datos usado. Como en estos tutoriales usamos el proveedor SqlClient, la propiedad Adapter es de tipo SqlDataAdapter. La propiedad Adapter del TableAdapter tiene tres propiedades de tipo SqlCommand que utiliza para generar las sentencias INSERT, UPDATE y DELETE: InsertCommand UpdateCommand

DeleteCommand

Un objeto SqlCommand es responsable de enviar una consulta en particular a la base de datos y tiene propiedades como: CommandText, que indica la sentencia SQL ad-hoc o el procedimiento almacenado a ejecutar y Parameters; que es una coleccin de objetos SqlParameter. Como vimos anteriormente en el tutorial Creacin de una capa de acceso a datos, estos objetos command pueden ser personalizados por medio de la ventana Propiedades. Adems de la consulta principal, el TableAdapter puede incluir un nmero variable de mtodos que cuando son invocados envan un comando especfico a la base de datos. El objeto command de la consulta principal y los objetos command de todos los mtodos adicionales son almacenados en la propiedad CommandCollection del TableAdapter. Tommonos un momento para ver el cdigo generado por el ProductsTableAdapter en el DataSet Northwind para estas dos propiedades y sus miembros variables soportados as como sus mtodos de ayuda:
Private WithEvents _adapter As System.Data.SqlClient.SqlDataAdapter Private Sub InitAdapter() Me._adapter = New System.Data.SqlClient.SqlDataAdapter ... Code that creates the InsertCommand, UpdateCommand, ... ... and DeleteCommand instances - omitted for brevity ... End Sub Private ReadOnly Property Adapter() As System.Data.SqlClient.SqlDataAdapter Get If (Me._adapter Is Nothing) Then Me.InitAdapter End If Return Me._adapter End Get End Property Private _commandCollection() As System.Data.SqlClient.SqlCommand Private Sub InitCommandCollection() Me._commandCollection = New System.Data.SqlClient.SqlCommand(8) {} ... Code that creates the command objects for the main query and the ... ... ProductsTableAdapters other eight methods - omitted for brevity ... End Sub

Protected ReadOnly Property CommandCollection() As System.Data.SqlClient.SqlCommand() Get If (Me._commandCollection Is Nothing) Then Me.InitCommandCollection End If Return Me._commandCollection End Get End Property

El cdigo para las propiedades Adapter y CommandCollection imita cercanamente a los de la propiedad Connection. Hay miembros variables que almacenan los objetos usados por las propiedades. Las propiedades Get acceden al inicio verificando si las variables de los miembros correspondientes son Nothing. Si es as, se llama a un mtodo de iniciacin que crea una instancia del miembro variable y asigna las propiedades esenciales relacionadas con los comandos. Paso 4. Exponer las definiciones a nivel de comando Idealmente la informacin a nivel de comando debe permanecer encapsulada dentro de la capa de acceso a datos. Sin embargo esta informacin podra ser necesaria en otras capas de la arquitectura, esta puede ser expuesta por medio de clases parciales como con las definiciones a nivel de conexin. Como el TableAdapter solamente tiene una propiedad Connection, el cdigo para exponer las definiciones a nivel de conexin es bastante sencillo. Las cosas son un poco ms complicadas cuando modificamos definiciones a nivel de comando porque el TableAdapter tiene mltiples objetos command un InsertCommand, UpdateCommand y DeleteCommand junto con un nmero variable de objetos command en la propiedad CommandCollection. Cuando actualizamos definiciones a nivel de comando estas definiciones necesitan ser propagadas a todos los objetos command. Por ejemplo, hay ciertas consultas en el TableAdapter que toman un tiempo extraordinariamente largo para ejecutarse. Cuando usamos el TableAdapter para ejecutar una de estas consultas, podramos desear incrementar la propiedad CommandTimeout del objeto Command. Esta propiedad especifica el nmero de segundos de espera para que el comando sea ejecutado y por defecto es 30.

Para permitir que la propiedad CommandTimeout sea ajustada por la BLL, agregue el siguiente mtodo Public al ProductsDataTable usando el archivo de clase parcial creado en el paso 2 (ProductsTableAdapter.ConnectionAndCommandSettings.vb):
Public Sub SetCommandTimeout(ByVal timeout As Integer) If Me.Adapter.InsertCommand IsNot Nothing Then Me.Adapter.InsertCommand.CommandTimeout = timeout End If If Me.Adapter.DeleteCommand IsNot Nothing Then Me.Adapter.DeleteCommand.CommandTimeout = timeout End If If Me.Adapter.UpdateCommand IsNot Nothing Then Me.Adapter.UpdateCommand.CommandTimeout = timeout End If For i As Integer = 0 To Me.CommandCollection.Length 1 If Me.CommandCollection(i) IsNot Nothing Then Me.CommandCollection(i).CommandTimeout = timeout End If Next End Sub

Este mtodo puede ser invocado desde la capa BLL o la capa de presentacin estableciendo el tiempo de espera de ejecucin del comando para todos los comandos generados por la instancia TableAdapter. Nota: Las propiedades Adapter y CommandCollection son marcadas como Private, lo que significa que solamente pueden ser accesibles desde el cdigo dentro del TableAdapter. A diferencia de la propiedad Connection, los modificadores de acceso no son configurables. Por lo tanto si necesita exponer propiedades a nivel de comando para otras capas en nuestra arquitectura debe usar una clase parcial como discutimos anteriormente para proporcionar un mtodo Public o una propiedad que lea o escriba los objetos Command Private. Resumen Los TableAdapters dentro de un DataSet tipiado sirven para encapsular los detalles de acceso a datos y su complejidad. Usando los TableAdapters, no tenemos que preocuparnos por escribir cdigo ADO.Net para conectar la base de datos, generar un comando o poblar los resultados en un DataTable. Esto es manejado automticamente.

Sin embargo hay ocasiones en que necesitamos personalizar las especificaciones ADO.Net de bajo nivel, como cambiar la cadena de conexin o la conexin por defecto o los valores del tiempo de espera de los command. Las propiedades Connection, Adapter y CommandCollection son generadas automticamente por el TableAdapter, pero son por defecto Friend o Private. Esta informacin interna puede ser expuesta ampliando el TableAdapter para que use clases parciales que incluyan mtodos Public o propiedades. Adems el acceso a la propiedad Connection del TableAdapter puede ser configurada por medio de la propiedad ConnectionModifier del TableAdapter.

70. PROTECCIN DE LAS CADENAS DE CONEXIN Y OTRA INFORMACIN DE CONFIGURACIN


Una aplicacin ASP.Net generalmente almacena informacin de configuracin en un archivo Web.config. Alguna de esta informacin es delicada y necesita proteccin. Por defecto este archivo no es suministrado a un visitante del sitio web, pero un administrador o un hacker podran tener acceso al sistema de archivos del servidor web y ver los contenidos de este archivo. En este tutorial aprenderemos que ASP.Net 2.0. nos permite proteger la informacin delicada encriptando secciones del archivo Web.config. Introduccin La informacin de configuracin de aplicaciones ASP.Net normalmente es almacenada en un archivo XML llamado Web.config. En el transcurso de estos tutoriales hemos actualizado el Web.config un montn de veces. Por ejemplo cuando creamos el DataSet tipiado Nothwind, la informacin de la cadena de conexin fue agregada automticamente a Web.config en la seccin <connectionStrings>. Despus en el tutorial de Pginas Maestras y Navegacin del Sitio, actualizamos manualmente Web.config agregando un elemento <pages> que indica que todas las pginas ASP.Net en nuestro proyecto deben usar el tema DataWebControls. Como Web.config puede contener datos delicados como cadenas de conexin, es importante que los contenidos del Web.config permanezcan seguros y ocultos de los visitantes no autorizados. Por defecto, cualquier solicitud HTTP a un archivo con extensin .config es manejado por el motor ASP.Net, que devuelve el mensaje Este tipo de pgina no es suministrado como muestra la figura 1. Esto significa que los visitantes no pueden ver el contenido de nuestro archivo Web.config simplemente ingresando en la barra de direccin de nuestro navegador http://www.YourServer.com/Web.config.

Figura 1. Visitar Web.config por medio de un navegador devuelve un mensaje de este tipo de pgina no puede ser presentada Pero que sucede si un hacker es capaz de encontrar algn otro medio que le permita ver los contenidos de nuestro archivo Web.config? Que podra hacer un atacante con esta informacin y que pasos pueden ser tomados para adelantarnos y proteger la informacin delicada dentro de Web.config? Afortunadamente, la mayora de secciones en Web.config no contienen informacin delicada. Que dao puede hacer un atacante que perpetra si conoce el nombre del Tema usado por defecto por nuestras pginas ASP.Net? Sin embargo ciertas secciones de Web.config, contienen informacin delicada que puede incluir cadenas de conexin, nombres de usuario, contraseas, nombres de servidores, llaves encriptadas, entre otros. Esta informacin es normalmente encontrada en las siguientes secciones de Web.config: <appSettings> <connectionStrings> <identity> <sessionState>

En este tutorial veremos tcnicas para proteger aquella informacin de configuracin delicada. Como veremos la versin .NET Framework 2.0 incluye unas configuraciones de sistema protegidas que hacen encriptaciones y desencriptaciones mediante programacin de secciones de configuracin seleccionadas de una forma fcil. Nota: Este tutorial se concluye con la observacin de las recomendaciones de Microsoft para conectarse a una base de datos desde una aplicacin ASP.Net. Adems de

encriptar sus cadenas de conexin, puede ayudar solidificar su sistema asegurndose que se conecta a una base de datos de una manera segura. Paso 1. Explorar las opciones de configuracin Protected de ASP.Net 2.0 ASP.Net 2.0 incluye un sistema de configuracin protegido para encriptar y desencriptar informacin de configuracin. Esto incluye mtodos en el .NET Framework que pueden ser usados para encriptar y desencriptar mediante programacin la informacin de configuracin. El sistema de configuracin protegida usa el modelo proveedor, que le permite a los desarrolladores escoger que implementacin criptogrfica es usada. El .NET Framework navega con dos proveedores de configuracin protegida: RSAProtectedConfigurationProvider- usa el algoritmo asimtrico RSA para encriptar y desencriptar. DPAPIProtectedConfigurationProvider- usa la proteccin de datos de Windows API(DPAPI) para encriptar y desencriptar. Como el sistema de configuracin protegido implementa el enfoque de diseo del proveedor, es posible crear su propio proveedor de configuracin protegido y conectarlo en nuestra aplicacin. Ver Implementar un proveedor de configuracin protegido para ms informacin sobre este proceso. Los proveedores RSA y DPAPI usan llaves para sus rutinas de encriptacin y desencriptacin, estas llaves pueden ser almacenadas en la maquina o a nivel de usuario. Las llaves a nivel de maquina son ideales para escenarios donde la aplicacin web se ejecuta en nuestro servidor dedicado o si hay mltiples aplicaciones en un servidor que necesitan compartir informacin encriptada. Las llaves a nivel de usuario son una opcin ms segura en ambientes de almacenamiento compartidos donde otras aplicaciones en el mismo servidor deben desencriptar las secciones de configuracin protegidas de la aplicacin. En los ejemplos de este tutorial usaremos el proveedor DPAPI y las llaves a nivel de mquina. Especficamente veremos la encriptacin de la seccin <connectionStrings> en Web.config aunque el sistema de configuracin protegida puede ser usado para encriptar cualquiera de las secciones Web.config.

Nota: los

Los nombres

proveedores de proveedor

RSAProtectedConfigurationProvider RsaProtectedConfigurationProvider Cuando encriptamos

y y o o actual Puede carpeta

DPAPIProtectedConfigurationProvider son registrados en el archivo machine.config con DataProtectionConfigurationProvider, proveedor apropiado y respectivamente.

desencriptamos informacin de configuracin necesitamos suministrar el nombre de (RsaProtectedConfigurationProvider en lugar del tipo en de nombre la DPAPIProtectedConfigurationProvider). machine.config DataProtectionConfigurationProvider) (RSAProtectedConfigurationProvider encontrar el archivo

$WINDOWS$\Microsoft.NET\Framework\version\CONFIG. Paso 2. Encriptar y Desencriptar mediante programacin las secciones de configuracin Con unas pocas lneas de cdigo podemos encriptar o desencriptar una seccin de configuracin en particular usando un proveedor especfico. Como veremos pronto, el cdigo simplemente necesita referenciar mediante programacin la seccin de configuracin apropiada, llamar a su mtodo ProtectSection o UnprotectSection y luego llamar al mtodo Save para persistir sus cambios. Por otra parte, el .NET Framework incluye una utilidad de ayuda de lnea de comando que puede encriptar y desencriptar la informacin de configuracin. Exploraremos esta utilidad de lnea de comando en el paso 3. Para ilustrar la informacin de configuracin protegida mediante programacin, crearemos una pgina ASP.Net que incluya botones para encriptar y desencriptar la seccin <connectionStrings> en Web.config. Comience abriendo la pgina EncryptingConfigSections.aspx en la carpeta

AdvancedDAL. Arrastre un control TextBox desde la caja de herramientas hasta el diseador, estableciendo su propiedad ID en WebConfigContents, su propiedad TextMode en MultiLine y sus propiedades Width y Rows en 95% y 15 respectivamente. Este control TextBox mostrar los contenidos de Web.config permitindonos ver rpidamente si los contenidos estn encriptados o no. Por supuesto, en una aplicacin real nunca desearamos mostrar los contenidos de Web.config.

Debajo del TextBox agregue dos controles Button llamados EncryptConnStrings y DecryptConnStrings. Establezca sus propiedades Text en Encriptar cadenas de conexin y Desencriptar cadenas de conexin. Hasta este punto nuestra pantalla lucir similar a la Figura 2.

Figura 2. Agregue un TextBox y dos controles web Button a la pgina Luego, necesitamos escribir el cdigo que cargue y muestre el contenido de Web.config en el TextBox WebConfigContents cuando la pgina sea cargada por primera vez. Agregue el siguiente cdigo a la clase de cdigo subyacente de la pgina. Este cdigo agrega un mtodo llamado DisplayWebConfig y es llamado desde el controlador de evento Page_Load cuando Page.IsPostBack es False:
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load 'On the first page visit, call DisplayWebConfig method If Not Page.IsPostBack Then DisplayWebConfig() End If End Sub Private Sub DisplayWebConfig() 'Reads in the contents of Web.config and displays them in the TextBox Dim webConfigStream As StreamReader = _ File.OpenText(Path.Combine(Request.PhysicalApplicationPath, "Web.config"))

Dim configContents As String = webConfigStream.ReadToEnd() webConfigStream.Close() WebConfigContents.Text = configContents End Sub

El mtodo DisplayWebConfig usa la clase File para abrir el archivo Web.config de la aplicacin, la clase StreamReader para leer sus contenidos dentro de una cadena y la clase Path para generar la ruta fsica al archivo Web.config. Estas tres clases se encuentran en el espacio de nombres System.IO. Consecuentemente necesitamos agregar una sentencia Imports System.IO en la parte superior de la clase subyacente o prefijar los nombres de las clases con System.IO. Luego necesitamos agregar controladores de eventos para el evento Click de los dos controles web Button y agregar el cdigo necesario para encriptar y desencriptar la seccin <connectionStrings> usando una llave a nivel de maquina con el proveedor DPAPI. Desde el diseador presionamos dos veces cada uno de los botones y agregamos un controlador de evento Click en la clase de cdigo subyacente, luego agregamos el siguiente cdigo:
Protected Sub EncryptConnStrings_Click(sender As Object, e As EventArgs) _ Handles EncryptConnStrings.Click 'Get configuration information about Web.config Dim config As Configuration = _ WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath) ' Let's work with the <connectionStrings> section Dim connectionStrings As ConfigurationSection = _ config.GetSection("connectionStrings") If connectionStrings IsNot Nothing Then ' Only encrypt the section if it is not already protected If Not connectionStrings.SectionInformation.IsProtected Then ' Encrypt the <connectionStrings> section using the ' DataProtectionConfigurationProvider provider connectionStrings.SectionInformation.ProtectSection( _ "DataProtectionConfigurationProvider") config.Save() ' Refresh the Web.config display DisplayWebConfig() End If

End If End Sub Protected Sub DecryptConnStrings_Click(sender As Object, e As EventArgs) _ Handles DecryptConnStrings.Click ' Get configuration information about Web.config Dim config As Configuration = _ WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath) ' Let's work with the <connectionStrings> section Dim connectionStrings As ConfigurationSection = _ config.GetSection("connectionStrings") If connectionStrings IsNot Nothing Then ' Only decrypt the section if it is protected If connectionStrings.SectionInformation.IsProtected Then ' Decrypt the <connectionStrings> section connectionStrings.SectionInformation.UnprotectSection() config.Save() ' Refresh the Web.config display DisplayWebConfig() End If End If End Sub

El cdigo usado en los dos controladores de evento es casi idntico. Ambos inician obteniendo informacin acerca del archivo Web.config de la aplicacin actual por medio del mtodo OpenWebConfiguration de la clase WebConfigurationManager. Este mtodo recupera el archivo de configuracin web para la ruta fsica especificada. Luego se accede a la seccin <connectionStrings> del archivo Web.config por medio del mtodo GetSection(sectionName) ConfigurationSection. El objeto ConfigurationSection incluye una propiedad SectionInformation que de la clase Configuration, que devuelve un objeto

proporciona informacin adicional y funcionalidad en lo que se refiere a la seccin de configuracin. Como muestra el cdigo anterior, podemos determinar si la seccin de configuracin esta encriptada verificando la propiedad IsProtected del la propiedad SectionInformation. Por otra parte, la seccin puede ser encriptada o desencriptada por medio de los mtodos ProtectSection(provider) y UnprotectSection de la propiedad SectionInformation.

El mtodo ProtectSection(provider) acepta como entrada una cadena que especifica el nombre del proveedor de configuracin protegida que usaremos cuando encriptemos. En el controlador de evento del botn EncryptConnString pasamos DataProtectionConfigurationProvider al mtodo ProtectSection(provider) para que el proveedor DPAPI sea usado. El mtodo UnprotectSection puede determinar el proveedor que estamos usando para encriptar la seccin de configuracin y por lo tanto no requiere parmetros de entrada. Despus de llamar al mtodo ProtectSection(provider) o UnprotectSection, debemos llamar al mtodo Save del objeto Configuration para persistir los cambios. Una vez que la informacin de configuracin ha sido encriptada o desencriptada y los cambios guardados, llamamos a DisplayWebConfig para cargar los contenidos de Web.Config actualizados dentro del control TextBox. Una vez que hemos ingresado el cdigo anterior, probmoslo visitando la pgina EncryptingConfigSections.aspx por medio de un navegador. Inicialmente debemos ver una pgina que muestra los contenidos de Web.Config con la seccin <connectionStrings> mostrada en texto plano (Ver Figura 3).

Figura 3. Agregar un TextBox y dos controles web Button a la pagina Ahora presione el botn Encriptar cadenas de conexin. Si la solicitud de validacin est habilitada, el marcado devuelto desde el TextBox WebConfigContents producir una HttpRequestValidationException que muestra el mensaje, Un valor Request.Form potencialmente peligroso fue detectado desde el cliente. La validacin de solicitud que

es habilitada por defecto en ASP.Net 2.0, prohbe devoluciones de datos que incluyan HTML no codificado y est diseada para ayudar a prevenir el ataque de scripts ingresados. Esta verificacin puede ser deshabilitada a nivel de pgina o aplicacin. Para desactivar esto para la pgina, establecemos ValidateRequest en False en la directiva @Page. La directiva @Page es encontrada en la parte superior del marcado declarativo de la pgina.
<%@ Page ValidateRequest="False" ... %>

Para ms informacin sobre la validacin de solicitud, su propsito y como deshabilitarla a nivel de aplicacin o pgina as como codificar el marcado HTML, vea Validacin de Solicitud Previniendo ataques de Script Despus de deshabilitar la validacin de solicitud en la pgina, probemos presionando nuevamente el botn Encriptar cadenas de conexin. En la devolucin de datos se accede al archivo de configuracin y su seccin <connectionStrings> ser encriptada usando el proveedor DPAPI. Luego el TextBox es actualizado para mostrar el nuevo contenido del Web.config. Como muestra la figura 4, la informacin de <connectionStrings> ahora esta encriptada.

Figura 4. Presionando el botn Encriptar cadenas de conexin encriptamos la seccin <connectionString> La seccin <connectionString> encriptada generada en mi computador es la siguiente, aunque algo del contenido del elemento <ChiperData> ha sido removido por brevedad:

<connectionStrings configProtectionProvider="DataProtectionConfigurationProvider"> <EncryptedData> <CipherData> <CipherValue>AQAAANCMnd8BFdERjHoAwE/...zChw==</CipherValue> </CipherData> </EncryptedData> </connectionStrings>

Nota: El elemento <connectionStrings> especifica el proveedor usado para realizar la encriptacin (DataProtectionConfigurationProvider). Esta informacin es usada por el mtodo UnprotectSection cuando es presionado el botn Desencriptar cadena de conexin Cuando se accede a la informacin de la cadena de conexin desde Web.config por cualquier cdigo que escribimos, un control SqlDataSource o el cdigo generado automticamente por los TableAdapters en nuestra DataSet tipiado, este automticamente es encriptado. En concreto no necesitamos ningn cdigo extra o una lgica para desencriptar la seccin <connectionString> encriptada. Para demostrar esto visitamos uno de los tutoriales anteriores, como el tutorial de visualizacin sencilla de la seccin Reporte Bsico (~/Basicreporting/SimpleDisplay.aspx). Como muestra la figura 5, el tutorial trabaja exactamente como esperamos, lo que indica que la informacin de la cadena de conexin encriptada est siendo desencriptada automticamente por la pgina ASP.Net.

Figura 5. La capa de acceso a datos desencripta automticamente la informacin de la cadena de conexin Para revertir la seccin <connectionStrings> de nuevo a su presentacin de texto plano, presionamos el botn Desencriptar cadenas de conexin. En la devolucin de datos debemos ver la cadena de conexin en Web.config en un texto plano. Hasta este punto su pantalla lucir similar a cuando visitamos por primera vez esta pgina (Ver Figura 3). Paso 3. Encriptar las secciones de configuracin usando aspnet_regiis.exe El .NET Framework incluye una variedad de herramientas de lnea de comando en la carpeta $WINDOWS$\Microsoft.NET\Framework\version\. En el tutorial Usando dependencias de cache SQL, para una instancia, vimos como usar la herramienta de lnea de comando aspnet_regsql.exe para agregar la infraestructura necesaria para las dependencias de cache de SQL. Otra herramienta de lnea de comando til en esta carpeta es la herramienta de registro ASP.Net IIS (aspnet_regiis.exe). Como su nombre lo implica, la herramienta de registro ASP.Net IIS es principalmente usada para registrar una aplicacin ASP.Net 2.0 con el servidor web de grado profesional de Microsoft IIS. Adems de las funciones relacionadas con IIS, la herramienta de registro ASP.Net IIS puede ser usada para encriptar o desencriptar secciones de configuracin especficas en Web.config.

La siguiente sentencia muestra la sintaxis general usada para encriptar una seccin de configuracin con la herramienta de lnea de comando aspnet_regiis.exe:
aspnet_regiis.exe -pef section physical_directory -prov provider

Section es la seccin de configuracin para encriptar (como connectionStrings), el physical_directory es la ruta fsica completa al directorio raz de la aplicacin web y provider es el nombre del proveedor de configuracin protegida que usaremos (como DataProtectionConfigurationProvider). De forma alterna si la aplicacin web est registrada en IIS puede ingresar la ruta virtual en lugar de la ruta fsica usando la siguiente sintaxis:
aspnet_regiis.exe -pe section -app virtual_directory -prov provider

El siguiente ejemplo aspnet_regiis.exe encripta la seccin <connectionStrings> usando el proveedor DPAPI con una llave a nivel de maquina:
aspnet_regiis.exe pef "connectionStrings" "C:\Websites\ASPNET_Data_Tutorial_73_VB" -prov "DataProtectionConfigurationProvider"

De forma similar la herramienta de lnea de comando aspnet_regiis.exe puede ser usada para desencriptar secciones de configuracin. En lugar de usar el interruptor pef, usamos pdf (o en lugar de _pe, usamos pd). Tambien note que el nombre del proveedor no est necesariamente desencriptado.
aspnet_regiis.exe -pdf section physical_directory -- or aspnet_regiis.exe -pd section -app virtual_directory

Nota: Como estamos usando el proveedor DPAPI, que usa llaves especificas para el computador, debemos ejecutar aspnet_regiis.exe desde la misma mquina en que las pginas web estn siendo servidas. Por ejemplo, si estamos ejecutando este programa de lnea de comando desde la mquina de desarrollo local y luego cargamos el archivo web.config encriptado al servidor de produccin, el servidor de produccin no ser capaz de desencriptar la informacin de la cadena de conexin ya que esta encriptada

usando llaves especficas para la mquina de desarrollo. El proveedor RSA no tiene esta limitacin as como es posible exportar llaves RSA a otra mquina. Entendiendo las opciones de autenticacin de base de datos Antes que cualquier aplicacin pueda generar consultas SELECT, INSERT, UPDATE o DELETE para una base de datos de SQL Server, la base de datos primero debe identificar el solicitante. Este proceso es conocido como autenticacin y SQL Server proporciona dos mtodos de autenticacin: Autenticacin de Windows el proceso bajo el cual la aplicacin se est ejecutando y es usado para comunicarse con la base de datos. Cuando ejecutamos una aplicacin ASP.Net por medio del servidor de desarrollo de VisualStudio 2005, las aplicaciones ASP.Net asumen la identificacin del usuario identificado actualmente. Para aplicaciones ASP.Net en Microsoft Internet Information Server (IIS), las aplicaciones ASP.Net usualmente asumen la identificacin de domainName\MachineName o domainName\NETWORK SERVICE, aunque esto puede ser personalizado. Autenticacin SQL los valores de ID de usuario y contrasea son suministrados como credenciales para la autenticacin. Con la autenticacin SQL el ID de usuario y su contrasea son proporcionados en la cadena de conexin. La autenticacin Windows es preferida sobre la autentificacin SQL porque es ms segura. Con la autenticacin Windows la cadena de conexin es libre de un usuario y contrasea y si el servidor web y el servidor de base de datos residen en diferentes maquinas, las credenciales no son enviadas por la red en un texto plano. Sin embargo, con la autenticacin SQL, las credenciales de autenticacin son fuertemente codificadas en la cadena de conexin y son transmitidas desde el servidor web al servidor de base de datos en un texto plano. Estos tutoriales han usado autenticacin de Windows. Usted puede decir el modo de autenticacin que est siendo usado inspeccionando la cadena de conexin. La cadena de conexin en Web.config para nuestros tutoriales ha sido:
Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True; User Instance=True

La Integrated Security=True y carece de un nombre de usuario y contrasea lo que indica que la autenticacin de Windows est siendo utilizada. En algunas cadenas de conexin el trmino Trusted Connection=Yes o Integrated Security=SSPI es usado en lugar de Integrated Security=True, pero todas las tres indican el uso de autentificacin Windows. El siguiente ejemplo muestra una cadena de conexin que usa autenticacin SQL. Note las credenciales involucradas dentro de la cadena de conexin:
Server=serverName; Database=Northwind; uid=userID; pwd=password

Imagine que un atacante es capaz de ver el archivo Web.config de nuestra aplicacin. Si usa autentificacin SQL para conectarse a la base de datos que es accesible desde Internet, el atacante puede usar esta cadena de conexin para conectarse a nuestra base de datos por medio de SQL Management Studio o desde las pginas ASP.Net de nuestro sitio web. Para ayudar a mitigar este problema, encriptamos la informacin de la cadena de conexin en Web.config usando el sistema de configuracin protegida. Nota: Para mayor informacin sobre los tipos de autenticacin validos en SQL Server, Vea Construccin Segura de aplicaciones web: Autentificacin, Autorizacin y Comunicacin Segura. Para ms ejemplos de cadenas de conexin que muestran la diferencia entre la sintaxis de autenticacin de Windows y SQL, refirase a ConnectionStrings.com Resumen Por defecto los archivos con la extensin .config en una aplicacin ASP.Net no pueden ser accesibles por medio de un navegador. Este tipo de archivos no son recuperados porque contienen informacin delicada, como cadenas de conexin de bases de datos, nombres de usuarios y contraseas, y as sucesivamente. El sistema de configuracin protegida de ASP.Net 2.0 ayuda a proteger la informacin delicada permitiendo que secciones especificadas de configuracin sean encriptadas. Existen dos proveedores de configuracin protegida predefinidos: uno que usa el algoritmo RSA y uno que usa API de proteccin de datos de Windows (DPAPI).

En este tutorial vimos como encriptar y desencriptar definiciones de configuracin usando el proveedor DPAPI. Esto puede ser realizado mediante programacin, como vimos en el paso 2, o por medio de la herramienta de lnea de comando aspnet_regiis.exe que explicamos en el paso 3.

71. DEPURACIN DE PROCEDIMIENTOS ALMACENADOS


Las ediciones Visual Studio Professional y Team System permiten establecer puntos de inspeccin e intervenir dentro de los procedimientos almacenados con SQL Server, haciendo la depuracin de los procedimientos almacenados tan fcil como la depuracin del cdigo de la aplicacin. Este tutorial tambin demuestra la depuracin de la base de datos y la depuracin de la aplicacin o los procedimientos almacenados. Introduccion Visual Studio proporciona una rica experiencia de depuracin, con solo pulsar unas pocas teclas o clics del mouse, es posible usar puntos de interrupcin para detener la ejecucin de un programa y examinar su estado y flujo de control. Junto con el cdigo de depuracin de la aplicacin, Visual Studio ofrece soporte soporte para depurar procedimientos almacenados dentro de SQL Server. Al igual como los puntos de ruptura pueden se establecidos dentro de la clase de cdigo subyacente de la pagina ASP.Net o en las clases de la capa lgica de negocios, asi tambin pueden ser colocados dentro de los procedimientos almacenados. En este tutorial veremos la inspeccin paso a paso dentro de los procedimientos almacenados del Explorador de Servidores en Visual Studio, asi como la definicin de puntos de ruptura que puedan ser presioandos cuando los procedimientos almacenados sean llamados desde la ejecucin de la aplicacin ASP.Net. Conceptos de depuracin en SQL Server Microsoft SQL Server 2005 fue diseado para proporcionar integracin el Common Language Runtime (CLR), que es el tiempo de ejecucin usado por todos los ensambladores .NET. Consecuentemente, SQL Server 2005 soporta objetos de base de datos administrados. Esto es que podemos crear objetos de base de dtos como procedimientos almacenados y Funciones definidas por el ususario (UDFs) como mtodos en una clase Visual Basic. Esto nos permite que los procedimeitnos almacenados y UDFs utilicen funcionalidad en el .NET Framework y desde nuestras clases personalizadas. Por supuesto SQL Server 2005 tambien proporciona soporte para objetos de base de datos T-SQL. SQL Server 2005 ofrece soporte de depuracin par los objetos de base de datos administrados y T-SQL. Sin mebargo estos objetos solo pueden ser depurados por

medio de las ediciones de Visua Studio 2005 Professional y Team Systems. En este tutorial examinaremos la depuracin de objetos de base de datos T-SQL. El siguiente tutorial nos muestra como depurar objetos de bases de datos administrados.

72. CREACIN DE PROCEDIMIENTOS ALMACENADOS Y FUNCIONES DEFINIDAS POR EL USUARIO CON MANAGED CODE
Microsoft SQL Server 2005 se integra con el .NET Common Language Runtime para permitir a los desarrolladores crear objetos de base de datos por medio de managed code. Este tutorial muestra como crear procedimientos almacenados administrados y funciones de usuario administradas con su cdigo Visual Basic o C#. Tambien veremos las ediciones de Visual Studio le permiten depurar los objetos de base de datos administrados. Introduccin Las bases de datos como las de Microsoft SQL Server 2005 usan el lenguaje TransactStructured Query (T-SQL) para insertar, modificar y recuperar datos. La mayora de sistemas de base de datos incluyen constructores para agrupar una serie de sentencias SQL que luego pueden ser ejecutadas como una sola y reutilizable unidad. Los procedimietnos almacenados son un ejemplo. Otro son las Funciones definidas por el usuario (UDFs), un constructor que examinaremos con mayor detalle en el paso 9. En su esencia, SQL es diseado para trabajar con conjuntos de datos. Las sentencias SELECT, UPDATE y DELETE se aplican inherentemente a todos los registros en la tabla correspondiente y son limitados nicamente por sus clausulas WHERE. Aun hay muchos lenguajes cuyas funciones fueron disenadas para trabajar con un registro a la vez y para maniplar datos escalares. Los CURSORs permiten que un grupo de registros sea manejado uno a la vez. Las funciones de manipulacin de Strings como LEFT, CHARINDEX y PATINDEX trabajan con datos escalares. SQL tambin incluye sentencias de control de flujo como IF y WHILE. Antes de Microsoft SQL Server 2005, los procedimientos almacenados y los UDFs solo podan ser definidos como una coleccin de sentencias T-SQL. Sin embargo, SQL Server 2005 fue diseado para proporcionar informacin con el Lenguaje Common Runtime (CLR), que es el tiempo de ejecucin usado por todos los ensambladores .NET. Consecuentemente los procedimientos almacenados y UDFs en una base de datos 2005 pueden ser creados usando cdigo administrado. Esto es, usted puede crear un procedimiento almacenado o UDF como un mtodo en una clase Visual Basic. Esto habilita estos procedimientos almacenados y UDFs para utilizarlos funcionalmente en el .NET Framework y desde sus propias clases personalizadas.

En este tutorial veremos como crear procedimientos almacenados administrados y funciones definidas por el usuario y como integrarlas dentro de nuestra base de datos Northwind. Nota: Los objetos de base de datos administrados ofrecen algunas ventajas sobre sus contrapartes SQL. La riqueza del lenguaje, familiaridad y la habilidad de reusar cdigo y lgica existente son las principales ventajas. Pero los objetos de base de datos administrados son menos eficientes cuando trabajan con conjuntos de datos que no involucran mucha lgica de procedimiento. Para una discusin mas amplia acerca de las ventajas de usar cdigo administrado versus T-SQL, verifique las ventajas de usar cdigo administrado versus T-SQL. Paso 1. Mover la base de datos fuera de la carpeta App_Data Todos nuestros tutoriales anteriores hasta el momento han usado un archivo de base de datos Microsoft SQL Server 2005 Express Edition en la carpeta App_Data de la aplicacin web. Colocar la base de datos en App_Data simplifica la distibucion y ejecucin de estos tutoriales ya que todos los archivos estn localizados dentro de un directorio y no requieren pasos de configuracin adicionales para probar el tutorial. Sin embargo para este tutorial moveremos la base de datos Northwind fuera de la carpeta App_Data y registraremos esta explcitamente con la instancia de base de dtos SQL Server 2005 Express Edition. Aunque podemos desarrollar los pasos para este tutorial con la base de datos en la carpeta App_Data, un numero de pasos son mucho ms simples registrando explcitamente la base de datos con la instancia de base de datos de SQL Server 2005 Express Edition. La descarga para este tutorial tiene dos archivos de base de datos NORTHWND.MDF y NORTHWND_log.LDF colocados en una carpeta llamada DataFiles. Si esta siguiendo al paso la implementacin de estos tutoriales, cierre Visual Studio y mueva los archivos NORTHWND.MDF y NORTHWND_log.LDF desde la carpeta App_Data a una carpeta fuera del sitio web. Una vez que los archivos de base de datos han sido movidos a otro folder, necesitamos registrar la base de datos Northwind con la instancia de base de datos SQL Server 2005 Express Edition. Esto puede realizarse desde SQL Server Management Studio. Si tienes una edicin no-express de SQL Server 2005 instalada en su computador entonces ya tienen instalado SQL Server Management Studio. Si

solamente tienes SQL Server 2005 Express Edition instalada en su computador entonces debe tomarse un momento e instalar Microsoft SQL Server Management Studio Express. Inicie SQL Server Management Studio. Como se muestra en la figura 1, Management Studio comienza preguntando a que servidor desea conectarse: ingrese localhost\SQLExpress para el nombre del servidor, seleccione Autentificacion de Windows en la lista desplegable de Autenticacion y haga clic en conectar.

Figura 1. Conectar a la instancia de base de datos apropiada Una vez conectado la ventana de explorador de objetos mostrar la lista de informacin acerca de la instancia de base de datos SQL Server 2005 Express Edition, incluyendo sus bases de datos, informacin de seguridad, opciones de administracin, entre otras. Necesitamos adjuntar la base de datos Northwind en la carpeta DataFields (o a cualquier lugar que tengas moverla) para la instancia de base de datos SQL Server 2005 Express Edition. Haga clic derecho en la carpeta de base de datos y seleccione la opcin adjuntar del men contextual. Esto abrir al cuadro de dialogo Adjuntar bases de datos. Presione el botn adjuntar, seleccione el archivo NORTHWND.MDF apropiado y haga clic en OK. Hasta este momento su pantalla deber lucir similar a la Figura 2.

Figura 2. Conectar a la instancia de base de datos apropiada Nota: Cuando conectamos la instancia SQL Server 2005 Express Edition por medio de Management Studio el cuadro de dialogo Adjuntar bases de datos no le permite profundizar dentro de los directorios de perfil de usuario, como en Mis Documentos. Por lo tanto asegurese de colocar los archivos NORTHWND.MDF y NORTHWND_log.LDF en un directorio que no pertenezca al perfil de usuario. Haga clic en el botn OK para adjuntar la base de datos. El cuadro de dialogo de adjuntar bases de datos se cerrar y el Explorador de objetos deber mostrar la base de datos recien adjuntada. Cambie el nombre de la base de datos Northwind que tendra un nombre como: 9FE54661B32FDD967F51D71D0D5145CC_LINE ARTICLES\ DATATUTORIALS\ VOLUME 3\ CSHARP\ 73\ ASPNET_DATA_TUTORIAL_75_CS\APP_DATA\NORTHWND.MDF. Renombre la base de datos a Northwind haciendo clic derecho sobre la base de datos y seleccionando Cambiar Nombre.

Figura 3. Renombrar la base de datos a Northwind Paso 2. Crear una nueva solucin y proyecto SQL Server en Visual Studio Para crear procedimientos almacenados administrados o UDFs en SQL Server 2005 escribiremos el procedimiento almacenado y la lgica UDF como cdigo Visual Basic en una clase. Una vez que el cdigo ha sido escrito necesitaremos compilar esta clase en un ensamblador (un archivo .dll), registrar el ensamblador con la base de datos SQL Server y luego crear un procedimiento almacenado u objeto UDF en la base de datos que apunte la mtodo correspondiente en el ensamblador. Estos pasos pueden ser realizados manualmente. Podemos crear el cdigo en cualquier editor de texto, compilar este desde la lnea de comandos usando el compilador de Visual Basic (vbc.exe) registralo con la base de datos usando el comando CREATE ASSEMBLY o desde Management Studio y agregar el procedimiento almacenado u objeto UDF por medio de medios similares. Afortunadamente, las versiones Professional y Team Systems de Visual Studio incluyen un tipo de proyecto SQL Server que automatiza estas tareas. En este tutorial iremos a travs del uso del tipo de proyecto SQL Server para crear un procedimiento almacenado administrado y un UDF. Nota: Si estas usando Visual Web Developer o la edicin Standard de Visual Studio, entonces tendremos que usar en su lugar el enfoque manualmente. El paso 13 proporciona instrucciones detalladas para realizar estos pasos manualmente. Le animo a leer los pasos del 2 al 12 antes de leer el paso 13 ya que estos pasos incluyen instrucciones de configuracin SQL Server importantes que deben ser aplicados indeendientemente de que versin de Visual Studio estamos usando.

Comience abriendo Visual Studio. Desde el men Archivo, seleccione Nuevo Proyecto para mostrar el cuadro de dialogo Nuevo Proyecto (Ver Figura 4). Profundice en el tipo de proyecto base de datos y luego desde las plantillas mostradas en la derecha, seleccione crear un nuevo proyecto SQL Server. Yo escogi nombrar este proyecto ManagedDatabaseConstructs y colocarlo dentro de una solucin llamada Tutorial75.

Figura 4. Crear un nuevo proyecto SQL Server Haga clic en el botn OK del cuadro de dialogo Nuevo Proyecto para crear la solucin y el proyecto SQL Server. Un proyecto SQL Server es unido a una base de datos en particular. Consecuentemente despus de crear el nuevo proyecto SQL Server inmediatamente nos pide especificar esta informacin. La figura 5 muestra el cuadro de dialogo de Referencia de Nueva Base de datos que ha sido llenada para apuntar a la base de datos Northwind que registramos en la instancia de base de datos SQL Server 2005 Express Edition en el paso 1.

Figura 5. Asociar el proyecto SQL Server con la base de datos Northwind Con el fin de depurar los procedimientos almacenados administrados y UDFs que crearemos dentro de este proyecto, necesitamos habilitar el soporte de depuracin SQL/CLR para la conexin. Cuando asociamos un proyecto SQL Server con una nueva base de datos (como hicimos en la figura 5), Visual Studio nos pregunta si deseamos habilitar SQL/CLR sobre la conexin (Ver Figura 6). Da clic en Si.

Figura 6. Habilitar depuracin SQL/CLR Hasta este momento el nuevo proyecto SQL Server ha sido agregado a la solucin. Este contiene una carpeta llamada Test Scripts con un archivo llamado Test.Sql, el cual es usado para depurar los objetos de base de datos administrados creados en el proyecto. Observaremos la depuracin en el paso 12.

Ahora podemos agregar nuevos procedimientos almacenados administrados y UDFs a este proyecto, pero antes incluiremos nuestra aplicacin web existente en la solucin. Desde el men Archivo seleccione la opcin Agregar y seleccione sitio web existente. Busca la carpeta del sitio web apropiado y de clic en OK. Como se muestra en la figura 7, esto actualizar la solucin para incluir dos proyectos: el sitio web y el proyecto SQL Server ManagedDataBaseConstructs.

Figura 7. El Explorador de Soluciones ahora incluye dos nuevos proyectos El valor NORTHWNDConnectionString en el actual Web.config referencia al archivo NORTHWND.MDF en la carpeta App_Data. Como removimos esta base de datos de App_Data y la registramos explcitamente en la instancia de base de datos de SQL Server 2005 Express Edition, necesitamos actualizar el valor NORTHWNDConnectionString. Abra el archivo Web.config en el sitio web y cambie el valor NORTHWNDConnectionString para que su cadena de conexin se lea Data Source=localhost\SQLExpress;Initial Catalog=Northwind;Integrated Security=True.

Despues de este cambio, su seccin <connectionStrings> en el web.config lucir similar a la siguiente:


<connectionStrings> <add name="NORTHWNDConnectionString" connectionString= "Data Source=localhost\SQLExpress;Initial Catalog=Northwind; Integrated Security=True;Pooling=false" providerName="System.Data.SqlClient" /> </connectionStrings>

Nota: Como discutimos en el tutorial anterior, cuando depuramos un objeto SQL Server desde una aplicacin cliente, como un sitio web ASP.Net, necesitamos deshabilitar la agrupacin de conexin. La cadena de conexin mostada anteriormente deshabilita la agruapcion de conexin (Pooling=false). Si no tiene planeado depurar procedimientos o UDFs administrados desde un sitio web, habilite la agrupacin de conexin. Paso 3. Crear un procedimiento almacenado administrado

Figura 29. Compilar GetProductsWithPriceGreaterThan.vb en un ensamblador

La bandera /t especifica que la clase Visual Basic debe ser compilada en un DLL (rather than un ejecutable). La bandera /out especifica el nombre del ensamblador resultante. Nota: En lugar de compilar el archivo de clase GetProductsWithPriceGreaterThan.vb desde la lnea de comando, usted puede usar alternativamente Visual Basic Express Edition o crear un proyecto separado de biblioteca de clases en Visual Studio Standard Edition. Con el cdigo compilado en el ensamblador, estamos listos para registrar el ensamblador dentro de la base de datos SQL Server 2005. Esto puede realizarse a travs de T-SQL, usando el comando CREATE ASSEMBLY o po medio de SQL Server Management Studio. Vamos a enfocarnos en usar Management Studio. Desde el Management Studio, expanda la carpeta Programmability en la base de datos Northwind. Una de sus subcarpetas es Assemblies. Para agregar manualmente un ensamblador a la base de datos, haga clic derecho en la carpeta Assemblies y seleccione en el men contextual Nuevo Ensamblador. Esto muestra el cuadro de dialogo Nuevo Ensmblador (Ver Figura 30). Haga clic en el botn buscar, seleccione el ensamblador ManuallyCreatedDBObjects.dll que recin compilamos, y luego haga clic en OK para gregar el ensamblador a la base de datos. Usted no deber ver el ensamblador ManuallyCreatedDBObjects.dll en el explorador de objetos.

Figura 30. Agregar el ensamblador ManuallyCreatedDBObjects.dll a la base de datos

Figura 31. El ManuallyCreatedDBObjects.dll es mostrado en el Explorador de Objetos Aunque agregamos el ensamblador a la base de datos Northwind, aun tenemos que asociar un procedimiento almacenado con el mtodo GetProductsWithPriceGreaterThan en el ensamblador. Para realizar esto, abra una ventana de nueva consultay ejecute el siguiente script:
CREATE PROCEDURE [dbo].[GetProductsWithPriceGreaterThan] ( @price money ) WITH EXECUTE AS CALLER AS EXTERNAL NAME [ManuallyCreatedDBObjects].[StoredProcedures].[GetProductsWithPriceGreaterThan] GO

Esto crea un nuevo procedimiento almacenado en la base de datos Northwind llamada GetProductsWithPriceGreaterThan y asocia este con el mtodo administrado GetProductsWithPriceGreaterThan (el cual esta en la clase ProcedimientosAlmacenados, que esta en el ensamblador ManuallyCreatedDBObjects). Despus de ejecutar el script anterior, actualice la carpeta de procedimientos almacenados en el explorador de objetos. Tu deberas ver un nuevo procedimiento almacenado ingresando - GetProductsWithPriceGreaterThan el cual tenia un icono de llave cerca a el. Para probar este procedimiento almacenado, ingrese y ejecute el siguiente script en la ventana de consultas:
exec GetProductsWithPriceGreaterThan 24.95

Como muestra la figura 32, el comando anterior muestra informacin para esos productos con un UnitPrice mayor que $24.95.

Figura 32. El ManuallyCreatedDBObjects.dll es mostrado en el explorador de Objetos Resumen Microsoft SQL Server 2005 proporciona integracin con el lenguaje Common Runtime (CLR), el cual permite crear objetos de base de datos usando cdigo administrado. Previamente estos objetos de base de datos solamente podan ser creados usando TSQL, pero ahora podemos crear estos objetos usando lenguajes de programacin .NET como Visual Basic En este tutorial creamos dos procedimientos almacenados y una funcin definida por el usuario administrada. El tipo de proyecto SQL Server de Visual Studio facilita crear, compilar y desarrollar objetos de base de datos. Sin embargo, los tipos de proyecto SQL Server solo son validos en las ediciones Professional y Team Systems de Visual Studio. Para los que usan Visual Web Developer o la edicin Standard de Visual Studio, los pasos de creacin, compilacin y desarrollo deben ser realizados manualmente, como vimos en el paso 13.

Anda mungkin juga menyukai