Actualizar y eliminar los datos binarios existentes (VB)
Por Scott Mitchell
En tutoriales anteriores vimos cómo el control GridView facilita la edición y eliminación de datos de texto. En este tutorial, veremos cómo el control GridView también permite editar y eliminar datos binarios, tanto si esos datos binarios se guardan en la base de datos como si se almacenan en el sistema de archivos.
Introducción
En los últimos tres tutoriales hemos agregado bastante funcionalidad para trabajar con datos binarios. Para empezar, hemos agregado una columna BrochurePath
a la tabla Categories
y hemos actualizado la arquitectura en consecuencia. También hemos agregado métodos de capa de acceso a datos y de capa de lógica de negocios para trabajar con la columna Picture
existente de la tabla Categories, que contiene los datos binarios de un archivo de imagen. Hemos creado páginas web para presentar los datos binarios en un control GridView con un vínculo de descarga del folleto, con la imagen de la categoría mostrada en un elemento <img>
, y hemos agregado un control DetailsView para permitir a los usuarios agregar una nueva categoría y cargar sus datos de folleto e imagen.
Lo único que queda por implementar es la capacidad de editar y eliminar categorías existentes, y lo haremos en este tutorial mediante las características integradas de edición y eliminación de GridView. Al editar una categoría, el usuario podrá cargar opcionalmente una imagen nueva o hacer que la categoría siga usando la existente. Para el folleto, puede optar por usar el folleto existente, cargar un nuevo folleto o indicar que la categoría ya no tiene un folleto asociado. ¡Comencemos!
Paso 1: Actualización de la capa de acceso a datos
La capa de acceso a datos (DAL) tiene métodos Insert
, Update
y Delete
generados automáticamente, pero estos métodos se generaron en función de la consulta principal de CategoriesTableAdapter
, que no incluye la columna Picture
. Por lo tanto, los métodos Insert
y Update
no incluyen parámetros para especificar los datos binarios para la imagen de la categoría. Igual que hicimos en el tutorial anterior, es necesario crear un nuevo método TableAdapter para actualizar la tabla Categories
al especificar datos binarios.
Abra el conjunto de datos con tipo y, en el diseñador, haga clic con el botón derecho en el encabezado de CategoriesTableAdapter
y elija Agregar consulta en el menú contextual para iniciar el Asistente para la configuración de consultas de TableAdapter. Este asistente comienza preguntándonos cómo debe acceder la consulta TableAdapter a la base de datos. Elija Usar instrucciones SQL y haga clic en Siguiente. En el paso siguiente se solicita el tipo de consulta que se va a generar. Puesto que estamos creando una consulta para agregar un nuevo registro a la tabla Categories
, elija UPDATE y haga clic en Siguiente.
Figura 1: Selección de la opción UPDATE (haga clic para ver la imagen a tamaño completo).
Ahora es necesario especificar la instrucción SQL UPDATE
. El asistente sugiere automáticamente una instrucción UPDATE
correspondiente a la consulta principal de TableAdapter (una que actualiza los valores de CategoryName
, Description
y BrochurePath
). Cambie la instrucción para que la columna Picture
se incluya junto con un parámetro @Picture
, de la siguiente manera:
UPDATE [Categories] SET
[CategoryName] = @CategoryName,
[Description] = @Description,
[BrochurePath] = @BrochurePath ,
[Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))
La pantalla final del asistente nos pide que asignemos un nombre al nuevo método TableAdapter. Escriba UpdateWithPicture
y haga clic en Finalizar.
Figura 2: Asignación de un nombre al método UpdateWithPicture
de TableAdapter (haga clic para ver la imagen a tamaño completo)
Paso 2: Adición de los métodos de la capa de lógica de negocios
Además de actualizar la capa de acceso a datos, es necesario actualizar la capa de lógica de negocios para incluir métodos para actualizar y eliminar una categoría. Estos métodos se invocarán desde la capa de presentación.
Para eliminar una categoría, podemos usar el método Delete
de CategoriesTableAdapter
generado automáticamente. Agregue el siguiente método 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
En este tutorial, vamos a crear dos métodos para actualizar una categoría: uno que espera los datos de imagen binarios e invoca el método UpdateWithPicture
que acabamos de agregar a CategoriesTableAdapter
, y otro que acepta solo los valores de CategoryName
, Description
y BrochurePath
, y usa la instrucción Update
generada automáticamente de la clase CategoriesTableAdapter
. La lógica detrás del uso de dos métodos es que, en algunas circunstancias, es posible que un usuario quiera actualizar la imagen de la categoría junto con sus otros campos, en cuyo caso el usuario tendrá que cargar la nueva imagen. Los datos binarios de la imagen cargada se pueden usar en la instrucción UPDATE
. En otros casos, es posible que el usuario solo esté interesado en actualizar el nombre y la descripción, por ejemplo. Pero si la instrucción UPDATE
espera también los datos binarios de la columna Picture
, también es necesario proporcionar esa información. Esto requeriría un viaje adicional a la base de datos para devolver los datos de imagen del registro que se está editando. Por lo tanto, queremos usar dos métodos UPDATE
. La capa de lógica de negocios determinará cuál se usará en función de si se proporcionan datos de imagen al actualizar la categoría.
Para facilitar esto, agregue dos métodos a la clase CategoriesBLL
, ambos denominados UpdateCategory
. El primero debe aceptar tres objetos String
, una matriz de Byte
y un objeto Integer
como parámetros de entrada; el segundo, solo tres objetos String
y uno Integer
. Los parámetros String
de entrada son para el nombre, la descripción y la ruta del archivo de folleto de la categoría, mientras que la matriz Byte
es para el contenido binario de la imagen de la categoría, y el objeto Integer
identifica la propiedad CategoryID
del registro que se va a actualizar. Observe que la primera sobrecarga invoca la segunda si la matriz Byte
pasada 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: Copia de la funcionalidad de inserción y visualización
En el tutorial anterior creamos una página denominada UploadInDetailsView.aspx
que enumeraba todas las categorías de un control GridView y proporcionaba un control DetailsView para agregar nuevas categorías al sistema. En este tutorial ampliaremos el control GridView para que admita la edición y eliminación. En lugar de continuar trabajando desde UploadInDetailsView.aspx
, coloquemos en su lugar los cambios de este tutorial en la página UpdatingAndDeleting.aspx
de la misma carpeta ~/BinaryData
. Copie y pegue el marcado declarativo y el código de UploadInDetailsView.aspx
a UpdatingAndDeleting.aspx
.
Para empezar, abra la página UploadInDetailsView.aspx
. Copie toda la sintaxis declarativa dentro del elemento <asp:Content>
, como se muestra en la figura 3. Después, abra UpdatingAndDeleting.aspx
y pegue este marcado dentro de su elemento <asp:Content>
. Del mismo modo, copie el código de la clase de código subyacente de la página UploadInDetailsView.aspx
en UpdatingAndDeleting.aspx
.
Figura 3: Copia del marcado declarativo de UploadInDetailsView.aspx
(haga clic para ver la imagen a tamaño completo)
Después de copiar el código y el marcado declarativo, visite UpdatingAndDeleting.aspx
. Debería ver la misma salida y tener la misma experiencia de usuario que con la página UploadInDetailsView.aspx
del tutorial anterior.
Paso 4: Adición de compatibilidad con la eliminación a los controles ObjectDataSource y GridView
Como se ha explicado en el tutorial Información general sobre la inserción, actualización y eliminación de datos, el control GridView proporciona funcionalidades de eliminación integradas y estas funcionalidades se pueden habilitar con solo activar una casilla si el origen de datos subyacente de la cuadrícula admite la eliminación. Actualmente, el control ObjectDataSource al que está enlazado el control GridView (CategoriesDataSource
) no admite la eliminación.
Para solucionar este problema, haga clic en la opción Configurar origen de datos de la etiqueta inteligente del ObjectDataSource para iniciar el asistente. La primera pantalla muestra que el control ObjectDataSource está configurado para trabajar con la clase CategoriesBLL
. Presione Siguiente. Actualmente, solo se especifican las propiedades InsertMethod
y SelectMethod
del ObjectDataSource. Sin embargo, el asistente rellena automáticamente las listas desplegables en las pestañas UPDATE y DELETE con los métodos UpdateCategory
y DeleteCategory
, respectivamente. Esto se debe a que en la clase CategoriesBLL
marcamos estos métodos usando DataObjectMethodAttribute
como el método predeterminado para actualizar y eliminar.
Por ahora, establezca la lista desplegable de la pestaña UPDATE en (Ninguno), pero deje la lista desplegable de la pestaña DELETE establecida en DeleteCategory
. Volveremos a este asistente en el paso 6 para agregar compatibilidad con la actualización.
Figura 4: Configuración de ObjectDataSource para usar el método DeleteCategory
(haga clic para ver la imagen a tamaño completo)
Nota:
Al completar el asistente, Visual Studio puede preguntar si quiere actualizar campos y claves, lo que regenerará los campos de controles web de datos. Elija No, ya que si elige Sí sobrescribirá las personalizaciones de campo que haya realizado.
El control ObjectDataSource ahora incluirá un valor para su propiedad DeleteMethod
, así como un objeto DeleteParameter
. Recuerde que, cuando se usa el asistente para especificar los métodos, Visual Studio establece la propiedad OldValuesParameterFormatString
del ObjectDataSource en original_{0}
, lo que provoca problemas con las invocaciones de los métodos de actualización y eliminación. Por lo tanto, borre esta propiedad por completo o restablézcala al valor predeterminado, {0}
. Si necesita repasar esta propiedad del control ObjectDataSource, consulte el tutorial Información general sobre la inserción, actualización y eliminación de datos.
Después de completar el asistente y corregir la propiedad OldValuesParameterFormatString
, el marcado declarativo del control ObjectDataSource debería ser 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>
Después de configurar el control ObjectDataSource, agregue funcionalidades de eliminación al control GridView activando la casilla Habilitar eliminación en la etiqueta inteligente del GridView. Esto agregará un elemento CommandField al GridView cuya propiedad ShowDeleteButton
está establecida en True
.
Figura 5: Habilitación de la compatibilidad con la eliminación en el GridView (haga clic para ver la imagen a tamaño completo)
Dedique un momento a probar la funcionalidad de eliminación. Hay una clave externa entre la propiedad CategoryID
de la tabla Products
y la propiedad CategoryID
de la tabla Categories
, por lo que obtendrá una excepción de infracción de la restricción de clave externa si intenta eliminar cualquiera de las ocho primeras categorías. Para probar esta funcionalidad, agregue una nueva categoría proporcionándole un folleto y una imagen. Mi categoría de prueba, la cual se muestra en la figura 6, incluye un archivo de folleto de prueba denominado Test.pdf
y una imagen de prueba. En la figura 7 se muestra el control GridView después de agregar la categoría de prueba.
Figura 6: Adición de una categoría de prueba con un folleto y una imagen (haga clic para ver la imagen a tamaño completo)
Figura 7: Después de insertar la categoría de prueba, se muestra en el GridView (haga clic para ver la imagen a tamaño completo)
En Visual Studio, actualice el Explorador de soluciones. Ahora debería ver un nuevo archivo en la carpeta ~/Brochures
, Test.pdf
(vea la figura 8).
Después, haga clic en el vínculo Delete en la fila de la categoría de prueba, lo que hace que la página aplique un postback y se active el método DeleteCategory
de la clase CategoriesBLL
. Esto invocará el método Delete
de la capa de acceso a datos, lo que hará que la instrucción DELETE
adecuada se envíe a la base de datos. Los datos se vuelven a enlazar al control GridView y el marcado se devuelve al cliente sin la categoría de prueba.
Aunque el flujo de trabajo de eliminación ha quitado correctamente el registro de la categoría de prueba de la tabla Categories
, no ha quitado su archivo de folleto del sistema de archivos del servidor web. Actualice el Explorador de soluciones y verá que Test.pdf
todavía se encuentra en la carpeta ~/Brochures
.
Figura 8: El archivo Test.pdf
no se ha eliminado del sistema de archivos del servidor web
Paso 5: Eliminación del archivo de folleto de la categoría eliminada
Una de las desventajas de almacenar datos binarios externos a la base de datos es que se deben realizar pasos adicionales para limpiar estos archivos cuando se elimina el registro de la base de datos asociado. Los controles GridView y ObjectDataSource proporcionan eventos que se activan antes y después de que se haya ejecutado el comando de eliminación. Por tanto, necesitamos crear controladores de eventos para los eventos previos y posteriores a la acción. Antes de eliminar el registro de Categories
, es necesario determinar la ruta de acceso de su archivo PDF, pero no queremos eliminar el PDF antes de que se elimine la categoría para que no se produzca una excepción y la categoría no se elimine.
El evento RowDeleting
del control GridView se desencadena antes de invocar el comando de eliminación del control ObjectDataSource, mientras que el evento RowDeleted
se desencadena después. Cree controladores de eventos para estos dos eventos mediante el código siguiente:
' 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 eventos RowDeleting
, la propiedad CategoryID
de la fila que se va a eliminar se toma de la colección DataKeys
del GridView, a la que se puede acceder en este controlador de eventos por medio de la colección e.Keys
. Después, se invoca el método GetCategoryByCategoryID(categoryID)
de la clase CategoriesBLL
para devolver información sobre el registro que se va a eliminar. Si el objeto CategoriesDataRow
devuelto tiene un valor distinto de NULL``BrochurePath
, se almacena en la variable deletedCategorysPdfPath
de página para que el archivo se pueda eliminar mediante el controlador de eventos RowDeleted
.
Nota:
En lugar de recuperar los detalles de BrochurePath
del registro de Categories
que se va a eliminar en el controlador de eventos RowDeleting
, podríamos agregar alternativamente el objeto BrochurePath
a la propiedad DataKeyNames
del control GridView y acceder al valor del registro por medio de la colección e.Keys
. Esto aumentaría ligeramente el tamaño del estado de visualización del control GridView, pero reduciría la cantidad de código necesario y ahorraría un viaje a la base de datos.
Una vez invocado el comando de eliminación subyacente del ObjectDataSource, se activa el controlador de eventos RowDeleted
del GridView. Si no hay excepciones en la eliminación de los datos y hay un valor para deletedCategorysPdfPath
, el PDF se elimina del sistema de archivos. Tenga en cuenta que este código adicional no es necesario para limpiar los datos binarios de la categoría asociados a su imagen. Esto se debe a que los datos de imagen se almacenan directamente en la base de datos, por lo que la eliminación de la fila de Categories
también elimina los datos de imagen de esa categoría.
Después de agregar los dos controladores de eventos, vuelva a ejecutar este caso de prueba. Al eliminar la categoría, también se elimina su PDF asociado.
La actualización de los datos binarios asociados de un registro existente proporciona algunos desafíos interesantes. El resto de este tutorial profundiza en la adición de funcionalidades de actualización al folleto y la imagen. En el paso 6 se exploran las técnicas para actualizar la información del folleto, mientras que en el paso 7 se trata la actualización de la imagen.
Paso 6: Actualización del folleto de una categoría
Tal y como se describe en el tutorial Información general sobre la inserción, actualización y eliminación de datos, el control GridView ofrece compatibilidad de edición integrada a nivel de fila que se puede implementar mediante la activación de una casilla si su origen de datos subyacente está configurado correctamente. Actualmente, el control ObjectDataSource CategoriesDataSource
aún no está configurado para incluir compatibilidad con la actualización, por lo que vamos a agregarla.
Haga clic en el vínculo Configurar origen de datos desde el asistente del ObjectDataSource y continúe con el segundo paso. Dado que DataObjectMethodAttribute
se usa en CategoriesBLL
, la lista desplegable UPDATE debería rellenarse automáticamente con la sobrecarga de UpdateCategory
que acepta cuatro parámetros de entrada (para todas las columnas, excepto Picture
). Cambie esto para que use la sobrecarga con cinco parámetros.
Figura 9: Configuración de ObjectDataSource para usar el método UpdateCategory
que incluye un parámetro para Picture
(haga clic para ver la imagen a tamaño completo)
El control ObjectDataSource ahora incluirá un valor para su propiedad UpdateMethod
, así como los objetos UpdateParameter
correspondientes. Como se indica en el paso 4, Visual Studio establece la propiedad OldValuesParameterFormatString
del control ObjectDataSource en original_{0}
cuando se usa el asistente para configurar orígenes de datos. Esto provocará problemas con las invocaciones de los métodos de actualización y eliminación. Por lo tanto, borre esta propiedad por completo o restablézcala al valor predeterminado, {0}
.
Después de completar el asistente y corregir la propiedad OldValuesParameterFormatString
, el marcado declarativo del control ObjectDataSource debería tener un aspecto similar al 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 activar las características de edición integradas del control GridView, active la opción Habilitar edición desde la etiqueta inteligente del GridView. Esto establecerá la propiedad ShowEditButton
de CommandField en True
, lo que agregará un botón Edit (así como botones Update y Cancel para la fila que se está editando).
Figura 10: Configuración del control GridView para admitir la edición (haga clic para ver la imagen a tamaño completo)
Visite la página desde un explorador y haga clic en el botón Edit de una de las filas. Los objetos BoundField CategoryName
y Description
se representan como cuadros de texto. El objeto TemplateField BrochurePath
carece de un objeto EditItemTemplate
, por lo que continúa mostrando un vínculo al folleto en ItemTemplate
. El objeto ImageField Picture
se representa como un TextBox a cuya propiedad Text
se le asigna el valor del DataImageUrlField
del ImageField, en este caso CategoryID
.
Figura 11: El control GridView carece de una interfaz de edición para BrochurePath
(haga clic para ver la imagen a tamaño completo)
Personalización de la interfaz de edición de BrochurePath
Es necesario crear una interfaz de edición para el objeto TemplateField BrochurePath
, una que permita al usuario hacer una de las siguientes cosas:
- Dejar el folleto de la categoría tal como está.
- Actualizar el folleto de la categoría mediante la carga de un nuevo folleto.
- Quitar el folleto de la categoría por completo (en caso de que la categoría ya no tenga un folleto asociado).
También necesitamos actualizar la interfaz de edición del objeto ImageField Picture
, pero llegaremos a esto en el paso 7.
En la etiqueta inteligente del control GridView, haga clic en el vínculo Editar plantillas y seleccione la plantilla EditItemTemplate
del objeto TemplateField BrochurePath
en la lista desplegable. Agregue un control web RadioButtonList a esta plantilla, estableciendo su propiedad ID
en BrochureOptions
y su propiedad AutoPostBack
en True
. En la ventana Propiedades, haga clic en los puntos suspensivos de la propiedad Items
, lo que abrirá el editor de la colección ListItem
. Agregue las tres opciones siguientes con Value
establecido en 1, 2 y 3, respectivamente:
- Use current brochure
- Remove current brochure
- Upload new brochure
Establezca la propiedad Selected
del primer ListItem
en True
.
Figura 12: Adición de tres elementos ListItem
al control RadioButtonList
Debajo del control RadioButtonList, agregue un control FileUpload denominado BrochureUpload
. Establezca su propiedad Visible
en False
.
Figura 13: Adición de controles RadioButtonList y FileUpload a EditItemTemplate
(haga clic para ver la imagen a tamaño completo)
El control RadioButtonList proporciona las tres opciones para el usuario. La idea es que el control FileUpload solo se muestre si se selecciona la última opción, Upload new brochure (Cargar un nuevo folleto). Para ello, cree un controlador de eventos para el evento SelectedIndexChanged
del control RadioButtonList y agregue el código siguiente:
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
Dado que los controles RadioButtonList y FileUpload están dentro de una plantilla, tenemos que escribir un poco de código para acceder mediante programación a estos controles. Se pasa una referencia del control RadioButtonList al controlador de eventos SelectedIndexChanged
en el parámetro de entrada sender
. Para obtener el control FileUpload, es necesario obtener el control primario RadioButtonList y usar el método FindControl("controlID")
desde allí. Una vez que tengamos una referencia a los controles RadioButtonList y FileUpload, la propiedadVisible
del control FileUpload se establece en True
solo si el valor SelectedValue
del control RadioButtonList es igual a 3, que es el Value
del ListItem
Upload new brochure.
Con este código aplicado, dedique un momento a probar la interfaz de edición. Haga clic en el botón Edit de una fila. Inicialmente, se debe seleccionar la opción Use current brochure (Usar el folleto actual). Al cambiar el índice seleccionado, se produce un postback. Si se selecciona la tercera opción, se muestra el control FileUpload; de lo contrario, se oculta. En la figura 14 se muestra la interfaz de edición cuando se hace clic por primera vez en el botón Edit. La figura 15 muestra la interfaz después de seleccionar la opción Upload new brochure (Cargar un nuevo folleto).
Figura 14: Inicialmente, está seleccionada la opción Use current brochure (haga clic para ver la imagen a tamaño completo)
Figura 15: La selección de la opción Upload new brochure muestra el control FileUpload (haga clic para ver la imagen a tamaño completo)
Guardado del archivo de folleto y actualización de la columna BrochurePath
Cuando se hace clic en el botón Update del control GridView, se desencadena su evento RowUpdating
. Se invoca el comando de actualización del control ObjectDataSource y, después, se desencadena el evento RowUpdated
del control GridView. Igual que con el flujo de trabajo de eliminación, es necesario crear controladores de eventos para ambos eventos. En el controlador de eventos RowUpdating
, es necesario determinar qué acción se realizará en función del valor SelectedValue
del control RadioButtonList BrochureOptions
:
- Si
SelectedValue
es 1, queremos seguir usando la misma configuración deBrochurePath
. Por lo tanto, es necesario establecer el parámetrobrochurePath
del control ObjectDataSource en el valorBrochurePath
existente del registro que se está actualizando. El parámetrobrochurePath
del control ObjectDataSource se puede establecer mediantee.NewValues["brochurePath"] = value
. - Si
SelectedValue
es 2, queremos establecer el valor deBrochurePath
del registro enNULL
. Esto se puede lograr estableciendo el parámetrobrochurePath
del control ObjectDataSource enNothing
, lo que da como resultado que se use una base de datosNULL
en la instrucciónUPDATE
. Si hay un archivo de folleto existente que se va a quitar, es necesario eliminar el archivo existente. Pero solo queremos hacerlo si la actualización se completa sin generar una excepción. - Si
SelectedValue
es 3, queremos asegurarnos de que el usuario ha cargado un archivo PDF y, después, queremos guardarlo en el sistema de archivos y actualizar el valor de la columnaBrochurePath
del registro. Además, si hay un archivo de folleto existente que se va a reemplazar, es necesario eliminar el archivo anterior. Pero solo queremos hacerlo si la actualización se completa sin generar una excepción.
Los pasos que es necesario completar cuando el valor SelectedValue
del control RadioButtonList es 3 son prácticamente idénticos a los que usa el controlador de eventos ItemInserting
del control DetailsView. Este controlador de eventos se ejecuta cuando se agrega un nuevo registro de categoría desde el control DetailsView que hemos agregado en el tutorial anterior. Por lo tanto, nos corresponde refactorizar esta funcionalidad en métodos independientes. En concreto, he dividido la funcionalidad común en dos métodos:
ProcessBrochureUpload(FileUpload, out bool)
acepta como entrada una instancia del control FileUpload y un valor booleano de salida que especifica si la operación de eliminación o edición debe continuar o si se debe cancelar debido a algún error de validación. Este método devuelve la ruta de acceso al archivo guardado onull
si no se ha guardado ningún archivo.DeleteRememberedBrochurePath
elimina el archivo especificado por la ruta de acceso de la variable de páginadeletedCategorysPdfPath
sideletedCategorysPdfPath
no esnull
.
El código de estos dos métodos se muestra más abajo. Observe la similitud entre ProcessBrochureUpload
y el controlador de eventos ItemInserting
del control DetailsView del tutorial anterior. En este tutorial he actualizado los controladores de eventos del control DetailsView para usar estos nuevos métodos. Descargue el código asociado a este tutorial para ver las modificaciones en los controladores de eventos del control 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
del control GridView usan los métodos ProcessBrochureUpload
y DeleteRememberedBrochurePath
, como se muestra en el código siguiente:
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
Observe cómo el controlador de eventos RowUpdating
usa una serie de instrucciones condicionales para realizar la acción adecuada en función del valor de la propiedad SelectedValue
del control RadioButtonList BrochureOptions
.
Con este código aplicado, puede editar una categoría y hacer que use su folleto actual, que no use ningún folleto o que cargue uno nuevo. Ahora, pruébelo. Establezca puntos de interrupción en los controladores de eventos RowUpdating
y RowUpdated
para hacerse una idea del flujo de trabajo.
Paso 7: Carga de una nueva imagen
La interfaz de edición del objeto ImageField Picture
se representa como un cuadro de texto rellenado con el valor de su propiedad DataImageUrlField
. Durante el flujo de trabajo de edición, GridView pasa un parámetro al control ObjectDataSource con el nombre del parámetro, el valor de la propiedad DataImageUrlField
de ImageField y el valor especificado en el cuadro de texto de la interfaz de edición. Este comportamiento es adecuado cuando la imagen se guarda como un archivo en el sistema de archivos y la propiedad DataImageUrlField
contiene la dirección URL completa de la imagen. En tales circunstancias, la interfaz de edición muestra la dirección URL de la imagen en el cuadro de texto, que el usuario puede cambiar y volver a guardar en la base de datos. Esta interfaz predeterminada no permite al usuario cargar una nueva imagen, pero sí permite cambiar la dirección URL de la imagen de su valor actual a otro. En este tutorial, sin embargo, la interfaz de edición predeterminada de ImageField no es suficiente porque los datos binarios de Picture
se almacenan directamente en la base de datos y la propiedad DataImageUrlField
solo contiene el valor CategoryID
.
Para comprender mejor lo que sucede en nuestro tutorial cuando un usuario edita una fila con un objeto ImageField, considere el ejemplo siguiente: un usuario edita una fila con CategoryID
igual a 10, lo que hace que el objeto ImageField Picture
se represente como un cuadro de texto con el valor 10. Imagine que el usuario cambia el valor de este cuadro de texto a 50 y hace clic en el botón Update. Se produce un postback y el control GridView crea inicialmente un parámetro denominado CategoryID
con el valor 50. Sin embargo, antes de que el GridView envíe este parámetro (y los parámetros CategoryName
y Description
), agrega los valores de la colección DataKeys
. Por lo tanto, sobrescribe el parámetro CategoryID
con el valor de CategoryID
subyacente de la fila actual, que es 10. En resumen, la interfaz de edición de ImageField no afecta al flujo de trabajo de edición de este tutorial porque los nombres de la propiedad DataImageUrlField
de ImageField y el valor DataKey
de la cuadrícula son iguales.
Aunque ImageField facilita la visualización de una imagen basada en datos de una base de datos, no queremos proporcionar un cuadro de texto en la interfaz de edición. En su lugar, queremos ofrecer un control FileUpload que el usuario final puede usar para cambiar la imagen de la categoría. A diferencia del valor BrochurePath
, para estos tutoriales hemos decidido requerir que cada categoría tenga una imagen. Por lo tanto, no es necesario permitir al usuario indicar que no hay ninguna imagen asociada, el usuario puede cargar una imagen nueva o dejar la imagen actual tal como está.
Para personalizar la interfaz de edición del objeto ImageField, es necesario convertirlo en un objeto TemplateField. En la etiqueta inteligente del control GridView, haga clic en el vínculo Editar columnas, seleccione el objeto ImageField y haga clic en el vínculo Convertir este campo en un TemplateField.
Figura 16: Conversión del campo ImageField en TemplateField
La conversión del objeto ImageField en un objeto TemplateField de esta manera genera un TemplateField con dos plantillas. Como se muestra en la siguiente sintaxis declarativa, el objeto ItemTemplate
contiene un control web de imagen cuya propiedad ImageUrl
se asigna mediante una sintaxis de enlace de datos basada en las propiedades DataImageUrlField
y DataImageUrlFormatString
del objeto ImageField. La plantilla EditItemTemplate
contiene un elemento TextBox cuya propiedad Text
está 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 la plantilla EditItemTemplate
para usar un control FileUpload. En la etiqueta inteligente del control GridView, haga clic en el vínculo Editar plantillas y seleccione la plantilla EditItemTemplate
del objeto TemplateField Picture
en la lista desplegable. En la plantilla debería ver un cuadro de texto, quítelo. Después, arrastre un control FileUpload desde el cuadro de herramientas a la plantilla y establezca su propiedad ID
en PictureUpload
. Agregue también a la plantilla el texto "To change the category's picture, specify a new picture. To keep the category s picture the same, leave the field empty" (Para cambiar la imagen de la categoría, proporcione una nueva imagen. Para mantener la imagen actual de la categoría, deje el campo vacío).
Figura 17: Adición de un control FileUpload a EditItemTemplate
(haga clic para ver la imagen a tamaño completo)
Después de personalizar la interfaz de edición, vea el progreso en un explorador. Al ver una fila en modo de solo lectura, la imagen de la categoría se muestra como estaba antes, pero al hacer clic en el botón Edit se representa la columna de imagen como texto con un control FileUpload.
Figura 18: La interfaz de edición incluye un control FileUpload (haga clic para ver la imagen a tamaño completo)
Recuerde que el control ObjectDataSource está configurado para llamar al método UpdateCategory
de la clase CategoriesBLL
que acepta como entrada los datos binarios de la imagen como una matriz Byte
. Sin embargo, si esta matriz es Nothing
, se llama a la sobrecarga alternativa UpdateCategory
, que emite la instrucción SQL UPDATE
que no modifica la columna Picture
, dejando intacta la imagen actual de la categoría. Por lo tanto, en el controlador de eventos RowUpdating
del control GridView, es necesario hacer referencia mediante programación al control FileUpload PictureUpload
y determinar si se ha cargado un archivo. Si no se ha cargado ninguno, no queremos especificar un valor para el parámetro picture
. Por otro lado, si se ha cargado un archivo en el control FileUpload PictureUpload
, queremos asegurarnos de que es un archivo JPG. Si es así, podemos enviar su contenido binario al control ObjectDataSource mediante el parámetro picture
.
Igual que sucede con el código usado en el paso 6, gran parte del código necesario aquí ya existe en el controlador de eventos ItemInserting
del control DetailsView. Por lo tanto, he refactorizado la funcionalidad común en un nuevo método, ValidPictureUpload
, y he actualizado el controlador de eventos ItemInserting
para usar este método.
Agregue el código siguiente al inicio del controlador de eventos RowUpdating
del GridView. Es importante que este código venga antes del código que guarda el archivo de folleto, ya que no queremos guardar el folleto en el sistema de archivos del servidor web si se carga un archivo de imagen no válido.
' 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 método ValidPictureUpload(FileUpload)
toma un control FileUpload como único parámetro de entrada y comprueba la extensión del archivo cargado para asegurarse de que el archivo cargado es un archivo JPG; solo se llama a este método si se carga un archivo de imagen. Si no se carga ningún archivo, no se establece el parámetro picture y, por tanto, usa su valor predeterminado Nothing
. Si se carga una imagen y ValidPictureUpload
devuelve True
, al parámetro picture
se le asignan los datos binarios de la imagen cargada. Si el método devuelve False
, se cancela el flujo de trabajo de actualización y se cierra el controlador de eventos.
El código del método ValidPictureUpload(FileUpload)
, que se ha refactorizado desde el controlador de eventos ItemInserting
del control DetailsView, 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: Reemplazo de las imágenes originales de las categorías por archivos JPG
Recuerde que las ocho imágenes originales de las categorías son archivos de mapa de bits encapsulados en un encabezado OLE. Ahora que hemos agregado la capacidad de editar la imagen de un registro existente, dedique un momento a reemplazar estos mapas de bits por archivos JPG. Si quiere seguir usando las imágenes de categoría actuales, puede convertirlas a archivos JPG realizando los pasos siguientes:
- Guarde las imágenes de mapa de bits en el disco duro. Visite la página
UpdatingAndDeleting.aspx
en el explorador y, para cada una de las ocho primeras categorías, haga clic con el botón derecho en la imagen y elija guardar la imagen. - Abra la imagen en el editor de imágenes que prefiera. Puede usar Microsoft Paint, por ejemplo.
- Guarde el mapa de bits como una imagen JPG.
- Actualice la imagen de la categoría con el archivo JPG desde la interfaz de edición.
Después de editar una categoría y cargar la imagen JPG, la imagen no se representará en el explorador porque la página DisplayCategoryPicture.aspx
quita los primeros 78 bytes de las imágenes de las ocho primeras categorías. Corrija esto quitando el código que realiza la eliminación del encabezado OLE. Después de hacerlo, el controlador de eventos DisplayCategoryPicture.aspx``Page_Load
debería tener solo el código siguiente:
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 inserción y edición de la página UpdatingAndDeleting.aspx
podrían necesitar un poco más de trabajo. Los campos BoundField CategoryName
y Description
de los controles DetailsView y GridView deben convertirse en objetos TemplateField. Dado que CategoryName
no admite valores NULL
, se debe agregar un elemento RequiredFieldValidator. Y probablemente se debería convertir el cuadro de texto Description
en un objeto TextBox de varias líneas. Dejo estos toques finales como un ejercicio para que practique.
Resumen
Con este tutorial finaliza nuestro análisis del trabajo con datos binarios. En este tutorial y los tres anteriores hemos visto cómo se pueden almacenar datos binarios en el sistema de archivos o directamente en la base de datos. Un usuario proporciona datos binarios al sistema seleccionando un archivo de su disco duro y cargándolo en el servidor web, desde donde se puede almacenar en el sistema de archivos o insertar en la base de datos. ASP.NET 2.0 incluye un control FileUpload que permite proporcionar una interfaz tan fácil de usar como arrastrar y colocar. Sin embargo, como se indica en el tutorial Carga de archivos, el control FileUpload solo es adecuado para cargas de archivos relativamente pequeñas, idealmente que no superen un megabyte. También hemos explorado cómo asociar los datos cargados con el modelo de datos subyacente, además de cómo editar y eliminar los datos binarios de los registros existentes.
Nuestro siguiente conjunto de tutoriales explora varias técnicas de almacenamiento en caché. El almacenamiento en caché proporciona un medio para mejorar el rendimiento general de una aplicación tomando los resultados de operaciones costosas y almacenándolos en una ubicación a la que se puede acceder más rápidamente.
¡Feliz programación!
Acerca del autor
Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, trabaja con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él vía mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.
Agradecimientos especiales a
Muchos revisores han evaluado esta serie de tutoriales. El revisor principal de este tutorial ha sido Teresa Murphy. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.