Actualizar y eliminar los datos binarios existentes (C#)
por Scott Mitchell
En tutoriales anteriores se ha visto cómo el control GridView facilita la edición y eliminación de datos de texto. En este tutorial, verá 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 se ha agregado bastante funcionalidad para trabajar con datos binarios. Para empezar, se ha agregado una columna BrochurePath
a la tabla Categories
y se ha actualizado la arquitectura en consecuencia. También se han 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. Se han 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 se ha 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 hará 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 han generado en función de la consulta principal de CategoriesTableAdapter
, que no incluye la columna Picture
. Por tanto, los métodos Insert
y Update
no incluyen parámetros para especificar los datos binarios para la imagen de la categoría. Como en el tutorial anterior, es necesario crear un 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 preguntando 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. Como se va a crear 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 (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))
En la pantalla final del asistente se pide asignar un nombre al nuevo método TableAdapter. Escriba UpdateWithPicture
y haga clic en Finalizar.
Figura 2: Asignación del nombre UpdateWithPicture
al método 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 a fin de 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, puede 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 bool DeleteCategory(int categoryID)
{
int rowsAffected = Adapter.Delete(categoryID);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
En este tutorial, se crearán dos métodos para actualizar una categoría: uno que espera los datos de imagen binarios e invoca el método UpdateWithPicture
que se acaba 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á edita. Por lo tanto, querrá 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 int
como parámetros de entrada; el segundo, solo tres objetos string
y uno int
. 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 int
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 null
:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateCategory(string categoryName, string description,
string brochurePath, byte[] picture, int categoryID)
{
// If no picture is specified, use other overload
if (picture == null)
return UpdateCategory(categoryName, description, brochurePath, categoryID);
// Update picture, as well
int rowsAffected = Adapter.UpdateWithPicture
(categoryName, description, brochurePath, picture, categoryID);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateCategory(string categoryName, string description,
string brochurePath, int categoryID)
{
int rowsAffected = Adapter.Update
(categoryName, description, brochurePath, categoryID);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Paso 3: Copia de la funcionalidad de inserción y visualización
En el tutorial anterior se ha creado 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 se ampliará el control GridView para que admita la edición y eliminación. En lugar de continuar trabajando desde UploadInDetailsView.aspx
, en su lugar los cambios de este tutorial se colocarán 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
de ObjectDataSource. Pero 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
estos métodos se marcan mediante DataObjectMethodAttribute
como el método predeterminado para actualizar y eliminar.
Por ahora, establezca la lista desplegable de la pestaña UPDATE en (None), pero deje la lista desplegable de la pestaña DELETE establecida en DeleteCategory
. Volverá 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í se sobrescribirán 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 tanto, borre esta propiedad por completo o restablézcala al valor predeterminado, {0}
. Si necesita repasar esta propiedad del control ObjectDataSource, vea 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 control GridView cuya propiedad ShowDeleteButton
está establecida en true
.
Figura 5: Habilitación de la compatibilidad con la eliminación en 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 se producirá 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 y proporcione un folleto y una imagen. La categoría de prueba, que 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 control 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 Eliminar 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, es necesario 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 quiere 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
string deletedCategorysPdfPath = null;
protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
// Determine the PDF path for the category being deleted...
int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (category.IsBrochurePathNull())
deletedCategorysPdfPath = null;
else
deletedCategorysPdfPath = category.BrochurePath;
}
protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
// Delete the brochure file if there were no problems deleting the record
if (e.Exception == null)
{
// Is there a file to delete?
if (deletedCategorysPdfPath != null)
{
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
}
}
}
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ía 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 que invoque 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. 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
Como se ha descrito 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 de ObjectDataSource y continúe con el segundo paso. Como 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 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 en 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 control TextBox a cuya propiedad Text
se le asigna el valor del DataImageUrlField
de 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
, que permita al usuario hacer una de las siguientes cosas:
- Dejar el folleto de la categoría como está,
- Actualizar el folleto de la categoría mediante la carga de un nuevo folleto, o bien
- Quitar el folleto de la categoría por completo (en caso de que la categoría ya no tenga un folleto asociado).
También es necesario actualizar la interfaz de edición del objeto ImageField Picture
, pero lo hará en el paso 7.
Desde 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 y establezca 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
establecidos en 1, 2 y 3, respectivamente:
- Usar folleto actual
- Quitar folleto actual
- Cargar nuevo folleto
Establezca la propiedad Selected
de la primera instancia de 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 void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e)
{
// Get a reference to the RadioButtonList and its Parent
RadioButtonList BrochureOptions = (RadioButtonList)sender;
Control parent = BrochureOptions.Parent;
// Now use FindControl("controlID") to get a reference of the
// FileUpload control
FileUpload BrochureUpload =
(FileUpload)parent.FindControl("BrochureUpload");
// Only show BrochureUpload if SelectedValue = "3"
BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3");
}
Como los controles RadioButtonList y FileUpload están dentro de una plantilla, hay que escribir 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 tenga 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 elemento 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 Editar. En la figura 15 se 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, querrá seguir usando la misma configuración deBrochurePath
. Por 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, querrá establecer el valor deBrochurePath
del registro enNULL
. Esto se puede lograr si se establece 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 querrá hacerlo si la actualización se completa sin generar una excepción. - Si
SelectedValue
es 3, querrá asegurarse de que el usuario ha cargado un archivo PDF y, después, 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 querrá hacerlo si la actualización se completa sin generar una excepción.
Los pasos que se deben 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 se ha agregado en el tutorial anterior. Por tanto, debe refactorizar esta funcionalidad en métodos independientes. En concreto, se ha 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
.
A continuación se muestra el código de estos dos métodos. Observe la similitud entre ProcessBrochureUpload
y el controlador de eventos ItemInserting
del control DetailsView del tutorial anterior. En este tutorial se han 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 string ProcessBrochureUpload
(FileUpload BrochureUpload, out bool CancelOperation)
{
CancelOperation = false; // by default, do not cancel operation
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName),
".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
CancelOperation = true;
return null;
}
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension,
"-", iteration, ".pdf");
iteration++;
}
// 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 null;
}
}
private void DeleteRememberedBrochurePath()
{
// Is there a file to delete?
if (deletedCategorysPdfPath != null)
{
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
}
}
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 void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
// Reference the RadioButtonList
RadioButtonList BrochureOptions =
(RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions");
// Get BrochurePath information about the record being updated
int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (BrochureOptions.SelectedValue == "1")
{
// Use current value for BrochurePath
if (category.IsBrochurePathNull())
e.NewValues["brochurePath"] = null;
else
e.NewValues["brochurePath"] = category.BrochurePath;
}
else if (BrochureOptions.SelectedValue == "2")
{
// Remove the current brochure (set it to NULL in the database)
e.NewValues["brochurePath"] = null;
}
else if (BrochureOptions.SelectedValue == "3")
{
// Reference the BrochurePath FileUpload control
FileUpload BrochureUpload =
(FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload");
// Process the BrochureUpload
bool cancelOperation = false;
e.NewValues["brochurePath"] =
ProcessBrochureUpload(BrochureUpload, out cancelOperation);
e.Cancel = cancelOperation;
}
else
{
// Unknown value!
throw new ApplicationException(
string.Format("Invalid BrochureOptions value, {0}",
BrochureOptions.SelectedValue));
}
if (BrochureOptions.SelectedValue == "2" ||
BrochureOptions.SelectedValue == "3")
{
// "Remember" that we need to delete the old PDF file
if (category.IsBrochurePathNull())
deletedCategorysPdfPath = null;
else
deletedCategorysPdfPath = category.BrochurePath;
}
}
protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
// If there were no problems and we updated the PDF file,
// then delete the existing one
if (e.Exception == null)
{
DeleteRememberedBrochurePath();
}
}
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. Pero en este tutorial 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 el 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. Pero 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 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 quiere proporcionar un cuadro de texto en la interfaz de edición. En su lugar, quiere ofrecer un control FileUpload que el usuario final pueda usar para cambiar la imagen de la categoría. A diferencia del valor BrochurePath
, para estos tutoriales se ha decidido requerir que cada categoría tenga una imagen. Por 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 elemento 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. 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>
Es necesario actualizar EditItemTemplate
para usar un control FileUpload. En la etiqueta inteligente del control GridView, haga clic en el vínculo Editar plantillas y seleccione 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
. Pero si esta matriz tiene un valor null
, se llama a la sobrecarga de UpdateCategory
a, que emite la instrucción SQL UPDATE
que no modifica la columna Picture
, y deja intacta la imagen actual de la categoría. Por 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 quiere especificar un valor para el parámetro picture
. Por otro lado, si se ha cargado un archivo en el control FileUpload PictureUpload
, querrá asegurarse de que es un archivo JPG. Si es así, puede enviar su contenido binario al control ObjectDataSource mediante el parámetro picture
.
Como 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 tanto, se ha refactorizado la funcionalidad común en un nuevo método, ValidPictureUpload
, y se ha actualizado el controlador de eventos ItemInserting
para usar este método.
Agregue el código siguiente al inicio del controlador de eventos RowUpdating
de GridView. Es importante que este código sea anterior al código que guarda el archivo de folleto, ya que no quiere 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
FileUpload PictureUpload =
(FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure the picture upload is valid
if (ValidPictureUpload(PictureUpload))
{
e.NewValues["picture"] = PictureUpload.FileBytes;
}
else
{
// Invalid file upload, cancel update and exit event handler
e.Cancel = true;
return;
}
}
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 null
. 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 bool ValidPictureUpload(FileUpload PictureUpload)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
return false;
}
else
{
return true;
}
}
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 ha 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. Para corregirlo, quite 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 void Page_Load(object sender, EventArgs e)
{
int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
// Get information about the specified category
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories = _
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRow category = 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);
}
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. Como 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. Estos retoques finales se dejan como un ejercicio para que practique.
Resumen
Con este tutorial finaliza el análisis del trabajo con datos binarios. En este tutorial y los tres anteriores ha 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. Pero 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 ha visto 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.
En el siguiente conjunto de tutoriales se exploran 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 y 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 a través de mitchell@4GuysFromRolla.com. o de su blog, que se puede encontrar en http://ScottOnWriting.NET.
Agradecimientos especiales a
Esta serie de tutoriales fue revisada por muchos revisores. El revisor principal de este tutorial ha sido Teresa Murphy. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.