Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tutorial se muestra cómo crear una interfaz web que permita al usuario escribir datos de texto y cargar archivos binarios. Para ilustrar las opciones disponibles para almacenar datos binarios, se guardará un archivo en la base de datos mientras el otro se almacena en el sistema de archivos.
Introducción
En los dos tutoriales anteriores hemos explorado técnicas para almacenar datos binarios asociados al modelo de datos de la aplicación, se ha examinado cómo usar el control FileUpload para enviar archivos desde el cliente al servidor web y se ha visto cómo presentar estos datos binarios en un control web de datos. Sin embargo, todavía tenemos que hablar sobre cómo asociar datos cargados con el modelo de datos.
En este tutorial crearemos una página web para agregar una nueva categoría. Además de textBoxes para el nombre y la descripción de la categoría, esta página deberá incluir dos controles FileUpload uno para la nueva imagen de categoría y otro para el folleto. La imagen cargada se almacenará directamente en la nueva columna del Picture
registro, mientras que el folleto se guardará en la ~/Brochures
carpeta con la ruta de acceso al archivo guardado en la nueva columna del registro.BrochurePath
Antes de crear esta nueva página web, es necesario actualizar la arquitectura. La CategoriesTableAdapter
consulta principal no recupera la Picture
columna. Por lo tanto, el método auto-generado Insert
solo tiene entradas para los campos CategoryName
, Description
, y BrochurePath
. Por lo tanto, es necesario crear un método adicional en TableAdapter que solicite los cuatro Categories
campos. La CategoriesBLL
clase de la capa de lógica de negocios también tendrá que actualizarse.
Paso 1: Agregar unInsertWithPicture
método alCategoriesTableAdapter
Cuando creamos el CategoriesTableAdapter
en el tutorial Creación de una capa de acceso a datos, lo configuramos para generar automáticamente INSERT
, UPDATE
y DELETE
basadas en la consulta principal. Además, hemos indicado a TableAdapter que emplee el enfoque directo de base de datos, que creó los métodos Insert
, Update
y Delete
. Estos métodos ejecutan las instrucciones generadas automáticamente INSERT
, UPDATE
y DELETE
, y en consecuencia, aceptan parámetros de entrada basados en las columnas devueltas por la consulta principal. En el tutorial Carga de archivos mejoramos la CategoriesTableAdapter
consulta principal para usar la BrochurePath
columna.
Puesto que la CategoriesTableAdapter
consulta principal no hace referencia a la Picture
columna, no podemos agregar un nuevo registro ni actualizar un registro existente con un valor para la Picture
columna. Para capturar esta información, podemos crear un nuevo método en tableAdapter que se usa específicamente para insertar un registro con datos binarios o podemos personalizar la instrucción generada automáticamente INSERT
. El problema con la personalización de la instrucción generada automáticamente INSERT
es que nos arriesgamos a que el asistente sobrescriba nuestras personalizaciones. Por ejemplo, imagine que hemos personalizado la INSERT
instrucción para incluir el uso de la Picture
columna. Esto actualizaría el método Insert
de TableAdapter para incluir un parámetro de entrada adicional para la imagen de la categoría en formato binario. A continuación, podríamos crear un método en la capa lógica de negocios para usar este método DAL e invocar este método BLL a través de la capa de presentación, y todo funcionaría maravillosamente. Es decir, hasta la próxima vez que configuremos TableAdapter a través del Asistente para configuración de TableAdapter. Tan pronto como el asistente haya completado, nuestras personalizaciones en la instrucción INSERT
se sobrescribirán, el método Insert
volverá a su forma antigua y nuestro código ya no se compilará.
Nota:
Esta molestia deja de ser un problema al usar procedimientos almacenados en lugar de instrucciones SQL ad hoc. Un tutorial futuro explorará el uso de procedimientos almacenados en lugar de instrucciones SQL ad hoc en la capa de acceso a datos.
Para evitar este posible dolor de cabeza, en lugar de personalizar las instrucciones SQL generadas automáticamente, vamos a crear un nuevo método para TableAdapter. Este método, denominado InsertWithPicture
, aceptará valores para las CategoryName
columnas , Description
, BrochurePath
y Picture
y ejecutará una INSERT
instrucción que almacena los cuatro valores de un nuevo registro.
Abra el Conjunto de datos con tipo y, en el Diseñador, haga clic con el botón derecho en el encabezado CategoriesTableAdapter
y seleccione Agregar consulta en el menú contextual. Esto inicia el Asistente para configuración de consultas de TableAdapter, que comienza preguntándonos cómo debe tener acceso 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 Categories
tabla, elija INSERTAR y haga clic en Siguiente.
Figura 1: Seleccionar la opción INSERT (haga clic para ver la imagen de tamaño completo)
Ahora es necesario especificar la INSERT
instrucción SQL. El asistente sugiere automáticamente una INSERT
instrucción correspondiente a la consulta principal de TableAdapter. En este caso, es una INSERT
instrucción que inserta los valores CategoryName
, Description
y BrochurePath
. Actualice la instrucción para que la Picture
columna se incluya junto con un @Picture
parámetro, de la siguiente manera:
INSERT INTO [Categories]
([CategoryName], [Description], [BrochurePath], [Picture])
VALUES
(@CategoryName, @Description, @BrochurePath, @Picture)
La pantalla final del asistente nos pide que asignemos un nombre al nuevo método TableAdapter. Escriba InsertWithPicture
y haga clic en Finalizar.
Figura 2: Asignar un nombre al nuevo método InsertWithPicture
TableAdapter (haga clic para ver la imagen de tamaño completo)
Paso 2: Actualización de la capa de lógica de negocios
Dado que la capa de presentación solo debe interactuar con la capa lógica de negocios y evitar ir directamente a la capa de acceso a datos, es necesario crear un método BLL que invoque al método DAL que acabamos de crear (InsertWithPicture
). En este tutorial, cree un método en la CategoriesBLL
clase denominada InsertWithPicture
que acepta como entrada tres String
s y una Byte
matriz. Los String
parámetros de entrada son para el nombre, la descripción y la ruta del archivo de folleto de la categoría, mientras que la Byte
matriz es para el contenido binario de la imagen de la categoría. Como se muestra en el código siguiente, este método BLL invoca el método DAL correspondiente:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Insert, False)> _
Public Sub InsertWithPicture(categoryName As String, description As String, _
brochurePath As String, picture() As Byte)
Adapter.InsertWithPicture(categoryName, description, brochurePath, picture)
End Sub
Nota:
Asegúrese de haber guardado el Conjunto de datos con tipo antes de agregar el InsertWithPicture
método a BLL. Dado que el CategoriesTableAdapter
código de clase se genera automáticamente en función del Typed DataSet, si no guarda primero los cambios en el Typed DataSet, la Adapter
propiedad no tendrá conocimiento del InsertWithPicture
método.
Paso 3: Enumerar las categorías existentes y sus datos binarios
En este tutorial crearemos una página que permita a un usuario final agregar una nueva categoría al sistema, proporcionando una imagen y un folleto para la nueva categoría. En el tutorial anterior, utilizamos un GridView con un TemplateField e ImageField para mostrar el nombre, la descripción, la imagen y el vínculo de cada categoría para descargar su folleto. Vamos a replicar esa funcionalidad para este tutorial, creando una página que enumera todas las categorías existentes y permite crear nuevas.
Para empezar, abra la DisplayOrDownload.aspx
página desde la BinaryData
carpeta . Vaya a la vista Origen y copie la sintaxis declarativa de GridView y ObjectDataSource, pegandola dentro del <asp:Content>
elemento en UploadInDetailsView.aspx
. Además, no olvide copiar el método GenerateBrochureLink
de la clase del código detrás de DisplayOrDownload.aspx
a UploadInDetailsView.aspx
.
Figura 3: Copiar y pegar la sintaxis declarativa de DisplayOrDownload.aspx
a UploadInDetailsView.aspx
(haga clic para ver la imagen de tamaño completo)
Después de copiar la sintaxis declarativa y el método GenerateBrochureLink
a la página UploadInDetailsView.aspx
, visualice la página a través de un explorador para asegurarse de que todo se copió correctamente. Debería ver un GridView que enumera las ocho categorías que incluye un vínculo para descargar el folleto, así como la imagen de la categoría.
Figura 4: Ahora debería ver cada categoría junto con sus datos binarios (haga clic para ver la imagen de tamaño completo)
Paso 4: Configurar el CategoriesDataSource
para admitir la inserción
ObjectDataSource CategoriesDataSource
usado actualmente por Categories
GridView no proporciona la capacidad de insertar datos. Para admitir la inserción a través de este control de origen de datos, es necesario asignar su Insert
método a un método en su objeto subyacente, CategoriesBLL
. En concreto, queremos asignarlo al CategoriesBLL
método que agregamos de nuevo en el paso 2, InsertWithPicture
.
Para empezar, haga clic en el vínculo Configurar origen de datos desde la etiqueta inteligente ObjectDataSource. En la primera pantalla se muestra el objeto con el que está configurado el origen de datos para que funcione, CategoriesBLL
. Deje esta configuración as-is y haga clic en Siguiente para avanzar a la pantalla Definir métodos de datos. Vaya a la pestaña INSERT y elija el InsertWithPicture
método de la lista desplegable. Haga clic en Finalizar para completar el asistente.
Figura 5: Configurar objectDataSource para usar el método (InsertWithPicture
imagen de tamaño completo)
Nota:
Al completar el asistente, Visual Studio puede preguntar si desea 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.
Después de completar el asistente, ObjectDataSource incluirá ahora un valor para su propiedad InsertMethod
, así como InsertParameters
para las cuatro columnas de categoría, como ilustra el siguiente marcado declarativo:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture">
<InsertParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
</InsertParameters>
</asp:ObjectDataSource>
Paso 5: Crear la interfaz de inserción
Como se cubrió por primera vez en An Overview of Inserting, Updating, and Deleting Data, el control DetailsView proporciona una interfaz de inserción integrada que se puede usar al trabajar con un control de origen de datos que admite la inserción. Vamos a agregar un control DetailsView a esta página encima de GridView que representará permanentemente su interfaz de inserción, lo que permite al usuario agregar rápidamente una nueva categoría. Al agregar una nueva categoría en DetailsView, GridView se actualizará automáticamente y mostrará la nueva categoría.
Para empezar, arrastre un control DetailsView desde el Cuadro de herramientas al Diseñador situado encima del GridView, establezca la propiedad ID
en NewCategory
y borre los valores de las propiedades Height
y Width
. En la etiqueta inteligente DetailsView, vincule a la existente CategoriesDataSource
y active la casilla Habilitar inserción.
Figura 6: Enlazar detailsView a CategoriesDataSource
y Habilitar inserción (haga clic para ver la imagen de tamaño completo)
Para representar permanentemente DetailsView en su interfaz de inserción de registros, establezca su propiedad DefaultMode
a Insert
.
Tenga en cuenta que DetailsView tiene cinco BoundFields CategoryID
, CategoryName
, Description
, NumberOfProducts
y BrochurePath
, aunque el BoundField CategoryID
no se representa en la interfaz de inserción porque su propiedad InsertVisible
está establecida en False
. Estos BoundFields existen porque son las columnas devueltas por el GetCategories()
método , que es lo que el ObjectDataSource invoca para recuperar sus datos. Sin embargo, para insertar, no queremos permitir que el usuario especifique un valor para NumberOfProducts
. Además, es necesario permitirles cargar una imagen para la nueva categoría, así como cargar un PDF para el folleto.
Quite el NumberOfProducts
BoundField de DetailsView por completo y, a continuación, actualice las HeaderText
propiedades de y CategoryName
BrochurePath
BoundFields a Category y Brochure, respectivamente. A continuación, convierta BoundField BrochurePath
en un TemplateField y agregue un nuevo TemplateField para la imagen, lo que proporciona a este nuevo TemplateField un HeaderText
valor de Picture. Mueva templateField Picture
para que esté entre BrochurePath
TemplateField y CommandField.
Figura 7: Enlazar detailsView a CategoriesDataSource
y habilitar la inserción
Si ha convertido el BrochurePath
BoundField en un TemplateField a través del cuadro de diálogo Editar campos, el TemplateField incluye un ItemTemplate
, un EditItemTemplate
y un InsertItemTemplate
. Solo se necesita InsertItemTemplate
, así que no dude en quitar las otras dos plantillas. En este momento, la sintaxis declarativa de DetailsView debe ser similar a la siguiente:
<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False"
DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource"
DefaultMode="Insert">
<Fields>
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
InsertVisible="False" ReadOnly="True"
SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
<InsertItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
</InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture"></asp:TemplateField>
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
Agregar controles FileUpload para los campos folleto y imagen
Actualmente, templateField BrochurePath
s InsertItemTemplate
contiene un TextBox, mientras que Picture
TemplateField no contiene ninguna plantilla. Es necesario actualizar estos dos templateField s InsertItemTemplate
para usar controles FileUpload.
En la etiqueta inteligente DetailsView, elija la opción Editar plantillas y, a continuación, seleccione TemplateField BrochurePath
s InsertItemTemplate
en la lista desplegable. Quite el Cuadro de texto y, a continuación, arrastre un control FileUpload desde el Cuadro de herramientas a la plantilla. Establezca el control FileUpload en ID
BrochureUpload
. Del mismo modo, agregue un control FileUpload a TemplateField Picture
s InsertItemTemplate
. Establezca este control FileUpload en ID
a PictureUpload
.
Figura 8: Agregar un control FileUpload a InsertItemTemplate
(Haga clic para ver la imagen de tamaño completo)
Después de realizar estas adiciones, la sintaxis declarativa de TemplateField será:
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
<InsertItemTemplate>
<asp:FileUpload ID="BrochureUpload" runat="server" />
</InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
<InsertItemTemplate>
<asp:FileUpload ID="PictureUpload" runat="server" />
</InsertItemTemplate>
</asp:TemplateField>
Cuando un usuario agrega una nueva categoría, queremos asegurarnos de que el folleto y la imagen sean del tipo de archivo correcto. Para el folleto, el usuario debe proporcionar un PDF. Para la imagen, necesitamos que el usuario cargue un archivo de imagen, pero ¿se permite cualquier archivo de imagen o solo archivos de imagen de un tipo determinado, como GIFs o JPG? Para permitir distintos tipos de archivo, es necesario extender el Categories
esquema para incluir una columna que capture el tipo de archivo para que este tipo se pueda enviar al cliente a través Response.ContentType
de en DisplayCategoryPicture.aspx
. Puesto que no tenemos este tipo de columna, sería prudente restringir a los usuarios a proporcionar solo un tipo de archivo de imagen específico. Las Categories
imágenes existentes de la tabla son mapas de bits, pero JPG son un formato de archivo más adecuado para las imágenes que se sirven a través de la web.
Si un usuario carga un tipo de archivo incorrecto, es necesario cancelar la inserción y mostrar un mensaje que indique el problema. Agregue un control Web Label debajo de DetailsView. Establezca su propiedad ID
en UploadWarning
, limpie su propiedad Text
, establezca la propiedad CssClass
en Advertencia y las propiedades Visible
y EnableViewState
en False
. La Warning
clase CSS se define en Styles.css
y representa el texto en una fuente grande, roja, cursiva y negrita.
Nota:
Idealmente, CategoryName
y Description
BoundFields se convertirían en TemplateFields y sus interfaces de inserción se personalizarían. Por Description
ejemplo, la interfaz de inserción se adaptaría mejor utilizando un cuadro de texto de varias líneas. Y dado que la CategoryName
columna no acepta NULL
valores, se debe agregar un RequiredFieldValidator para asegurarse de que el usuario proporciona un valor para el nombre de la nueva categoría. Estos pasos se dejan como ejercicio para el lector. Recurra de nuevo a Personalización de la interfaz de modificación de datos para una mirada profunda a la mejora de las interfaces de modificación de datos.
Paso 6: Guardar el folleto cargado en el sistema de archivos del servidor web
Cuando el usuario ingresa los valores para una nueva categoría y hace clic en el botón Insertar, se ejecuta una devolución al servidor y se despliega el flujo de trabajo de inserción. En primer lugar, se desencadena el evento DetailsViewItemInserting
. A continuación, se invoca el método ObjectDataSource, Insert()
que da como resultado que se agregue un nuevo registro a la Categories
tabla. Después, se desencadena el evento DetailsViewItemInserted
.
Antes de invocar el método ObjectDataSource Insert()
, primero debemos asegurarnos de que el usuario cargó los tipos de archivo adecuados y, a continuación, guarde el pdf del folleto en el sistema de archivos del servidor web. Cree un controlador de eventos para el evento ItemInserting
DetailsView y agregue el código siguiente.
' Reference the FileUpload controls
Dim BrochureUpload As FileUpload = _
CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
' Make sure that a PDF has been uploaded
If String.Compare(System.IO.Path.GetExtension _
(BrochureUpload.FileName), ".pdf", True) <> 0 Then
UploadWarning.Text = _
"Only PDF documents may be used for a category's brochure."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
End If
El controlador de eventos se inicia haciendo referencia al BrochureUpload
control FileUpload desde las plantillas de DetailsView. A continuación, si se ha cargado un folleto, se examina la extensión del archivo cargado. Si la extensión no es .PDF, se muestra una advertencia, se cancela la inserción y finaliza la ejecución del controlador de eventos.
Nota:
Confiar en la extensión del archivo cargado no es una técnica segura para asegurarse de que el archivo cargado es un documento PDF. El usuario podría tener un documento PDF válido con la extensión .Brochure
, o podría haber tomado un documento que no es PDF y proporcionarle una .pdf
extensión. El contenido binario del archivo tendría que examinarse mediante programación para comprobar de forma más convincente el tipo de archivo. Sin embargo, estos enfoques exhaustivos suelen ser excesivos; comprobar la extensión es suficiente para la mayoría de los escenarios.
Como se describe en el tutorial Carga de archivos , se debe tener cuidado al guardar archivos en el sistema de archivos para que una carga de un usuario no sobrescriba otras s. En este tutorial intentaremos usar el mismo nombre que el archivo cargado. Si ya existe un archivo en el ~/Brochures
directorio con ese mismo nombre de archivo, sin embargo, anexaremos un número al final hasta que se encuentre un nombre único. Por ejemplo, si el usuario carga un archivo de folleto denominado Meats.pdf
, pero ya hay un archivo denominado Meats.pdf
en la ~/Brochures
carpeta, cambiaremos el nombre de archivo guardado a Meats-1.pdf
. Si existe, probaremos Meats-2.pdf
, etc., hasta que se encuentre un nombre de archivo único.
El código siguiente usa el File.Exists(path)
método para determinar si ya existe un archivo con el nombre de archivo especificado. Si es así, continúa probando nuevos nombres de archivo para el folleto hasta que no se encuentre ningún conflicto.
Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
brochurePath = String.Concat(BrochureDirectory, _
fileNameWithoutExtension, "-", iteration, ".pdf")
iteration += 1
End While
Una vez encontrado un nombre de archivo válido, el archivo debe guardarse en el sistema de archivos y el valor de ObjectDataSource debe brochurePath``InsertParameter
actualizarse para que este nombre de archivo se escriba en la base de datos. Como vimos en el tutorial Cargar archivos , el archivo se puede guardar mediante el método s SaveAs(path)
del control FileUpload. Para actualizar el parámetro de ObjectDataSource, use la colección brochurePath
.
' Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath))
e.Values("brochurePath") = brochurePath
Paso 7: Guardar la imagen cargada en la base de datos
Para almacenar la imagen cargada en el nuevo Categories
registro, es necesario asignar el contenido binario cargado al parámetro picture
de ObjectDataSource en el evento ItemInserting
de DetailsView. Sin embargo, antes de realizar esta asignación, es necesario asegurarnos primero de que la imagen cargada sea un JPG y no otro tipo de imagen. Como en el paso 6, vamos a usar la extensión de archivo de la imagen cargada para determinar su tipo.
Aunque la Categories
tabla permite NULL
valores para la Picture
columna, todas las categorías tienen actualmente una imagen. Vamos a forzar al usuario a proporcionar una imagen al agregar una nueva categoría a través de esta página. El código siguiente comprueba si se ha cargado una imagen y que tiene una extensión adecuada.
' Reference the FileUpload controls
Dim PictureUpload As FileUpload = _
CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
' Make sure that a JPG has been uploaded
If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpg", True) <> 0 AndAlso _
String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpeg", True) <> 0 Then
UploadWarning.Text = _
"Only JPG documents may be used for a category's picture."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Else
' No picture uploaded!
UploadWarning.Text = _
"You must provide a picture for the new category."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Este código debe colocarse antes del código del paso 6 para que, si hay un problema con la carga de imágenes, el controlador de eventos finalizará antes de que el archivo del folleto se guarde en el sistema de archivos.
Suponiendo que se ha cargado un archivo adecuado, asigne el contenido binario cargado al valor del parámetro picture con la siguiente línea de código:
' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes
Controlador de eventos completoItemInserting
Para completar, aquí está el controlador de eventos ItemInserting
completo.
Protected Sub NewCategory_ItemInserting _
(sender As Object, e As DetailsViewInsertEventArgs) _
Handles NewCategory.ItemInserting
' Reference the FileUpload controls
Dim PictureUpload As FileUpload = _
CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
' Make sure that a JPG has been uploaded
If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpg", True) <> 0 AndAlso _
String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpeg", True) <> 0 Then
UploadWarning.Text = _
"Only JPG documents may be used for a category's picture."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Else
' No picture uploaded!
UploadWarning.Text = _
"You must provide a picture for the new category."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes
' Reference the FileUpload controls
Dim BrochureUpload As FileUpload = _
CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
' Make sure that a PDF has been uploaded
If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _
".pdf", True) <> 0 Then
UploadWarning.Text = _
"Only PDF documents may be used for a category's brochure."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
brochurePath = String.Concat(BrochureDirectory, _
fileNameWithoutExtension, "-", iteration, ".pdf")
iteration += 1
End While
' Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath))
e.Values("brochurePath") = brochurePath
End If
End Sub
Paso 8: Corregir laDisplayCategoryPicture.aspx
página
Dediquemos un momento a probar la interfaz de inserción y ItemInserting
el controlador de eventos que se creó en los últimos pasos. Visite la UploadInDetailsView.aspx
página a través de un explorador e intente agregar una categoría, pero omita la imagen, o especifique una imagen no JPG o un folleto que no sea PDF. En cualquiera de estos casos, se mostrará un mensaje de error y se cancelará el flujo de trabajo de inserción.
Figura 9: Se muestra un mensaje de advertencia si se carga un tipo de archivo no válido (haga clic para ver la imagen de tamaño completo)
Una vez que haya comprobado que la página requiere que se cargue una imagen y no acepte archivos no PDF o no JPG, agregue una nueva categoría con una imagen JPG válida, dejando el campo Folleto vacío. Después de hacer clic en el botón Insertar, la página se refrescará y se agregará un nuevo registro a la tabla Categories
con la imagen cargada en formato binario almacenada directamente en la base de datos. GridView se actualiza y muestra una fila para la categoría recién agregada, pero, como se muestra en la figura 10, la imagen de la nueva categoría no se representa correctamente.
Figura 10: La imagen de la nueva categoría no se muestra (haga clic para ver la imagen de tamaño completo)
La razón por la que no se muestra la nueva imagen es porque la DisplayCategoryPicture.aspx
página que devuelve una imagen de categoría especificada está configurada para procesar mapas de bits que tienen un encabezado OLE. Este encabezado de 78 bytes se elimina de los contenidos binarios de la columna Picture
antes de que sean enviados de vuelta al cliente. Pero el archivo JPG que acabamos de cargar para la nueva categoría no tiene este encabezado OLE; por lo tanto, se quitan los bytes necesarios y válidos de los datos binarios de la imagen.
Dado que ahora hay mapas de bits con encabezados OLE y JPG en la Categories
tabla, es necesario actualizar DisplayCategoryPicture.aspx
para que haga la eliminación de encabezado OLE para las ocho categorías originales y omita esta eliminación para los registros de categoría más recientes. En nuestro siguiente tutorial examinaremos cómo actualizar una imagen de registro existente y actualizaremos todas las imágenes de categoría antiguas para que sean JPG. Por ahora, sin embargo, use el código siguiente en DisplayCategoryPicture.aspx
para quitar los encabezados OLE solo para esas ocho categorías originales:
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim categoryID As Integer = Convert.ToInt32(Request.QueryString("CategoryID"))
' Get information about the specified category
Dim categoryAPI As New CategoriesBLL()
Dim categories As Northwind.CategoriesDataTable = _
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID)
Dim category As Northwind.CategoriesRow = categories(0)
If categoryID <= 8 Then
' Output HTTP headers providing information about the binary data
Response.ContentType = "image/bmp"
' Output the binary data
' But first we need to strip out the OLE header
Const OleHeaderLength As Integer = 78
Dim strippedImageLength As Integer = _
category.Picture.Length - OleHeaderLength
Dim strippedImageData(strippedImageLength) As Byte
Array.Copy(category.Picture, OleHeaderLength, _
strippedImageData, 0, strippedImageLength)
Response.BinaryWrite(strippedImageData)
Else
' For new categories, images are JPGs...
' Output HTTP headers providing information about the binary data
Response.ContentType = "image/jpeg"
' Output the binary data
Response.BinaryWrite(category.Picture)
End If
End Sub
Con este cambio, la imagen JPG ahora se representa correctamente en GridView.
Figura 11: Las imágenes JPG para nuevas categorías se representan correctamente (haga clic para ver la imagen de tamaño completo)
Paso 9: Eliminar el folleto ante una excepción
Uno de los desafíos de almacenar datos binarios en el sistema de archivos del servidor web es que introduce una desconexión entre el modelo de datos y sus datos binarios. Por lo tanto, cada vez que se elimina un registro, también se deben quitar los datos binarios correspondientes en el sistema de archivos. Esto también puede entrar en juego al insertarlo. Considere el siguiente escenario: un usuario agrega una nueva categoría, especificando una imagen y un folleto válidos. Al hacer clic en el botón Insertar, se realiza un postback y se desencadena el evento de DetailsView ItemInserting
, guardando el folleto en el sistema de archivos del servidor web. A continuación, se invoca el método Insert()
de ObjectDataSource, que llama al método CategoriesBLL
de la clase InsertWithPicture
, que llama al método CategoriesTableAdapter
de InsertWithPicture
.
Ahora, ¿qué ocurre si la base de datos está sin conexión o si hay un error en la INSERT
instrucción SQL? Claramente, se producirá un error en insert, por lo que no se agregará ninguna nueva fila de categoría a la base de datos. Pero todavía tenemos almacenado el archivo del folleto en el sistema de archivos del servidor web. Este archivo debe eliminarse ante una excepción durante el flujo de trabajo de inserción.
Como se explicó anteriormente en el tutorial Control de excepciones de BLL y DAL-Level en una página de ASP.NET , cuando se produce una excepción desde las profundidades de la arquitectura, se propaga a través de las distintas capas. En la capa de presentación, podemos determinar si se ha producido una excepción desde el evento DetailsView s ItemInserted
. Este controlador de eventos también proporciona los valores de ObjectDataSource s InsertParameters
. Por lo tanto, podemos crear un controlador de eventos para el ItemInserted
evento que comprueba si se ha producido una excepción y, si es así, elimina el archivo especificado por el parámetro ObjectDataSource:brochurePath
Protected Sub NewCategory_ItemInserted _
(sender As Object, e As DetailsViewInsertedEventArgs) _
Handles NewCategory.ItemInserted
If e.Exception IsNot Nothing Then
' Need to delete brochure file, if it exists
If e.Values("brochurePath") IsNot Nothing Then
System.IO.File.Delete(Server.MapPath _
(e.Values("brochurePath").ToString()))
End If
End If
End Sub
Resumen
Hay una serie de pasos que se deben realizar para proporcionar una interfaz basada en web para agregar registros que incluyan datos binarios. Si los datos binarios se almacenan directamente en la base de datos, es probable que tenga que actualizar la arquitectura, agregando métodos específicos para controlar el caso en el que se insertan datos binarios. Una vez actualizada la arquitectura, el siguiente paso consiste en crear la interfaz de inserción, que se puede realizar mediante un DetailsView que se ha personalizado para incluir un control FileUpload para cada campo de datos binario. Los datos cargados se pueden guardar en el sistema de archivos del servidor web o asignarse a un parámetro de origen de datos en el controlador de eventos DetailsView.ItemInserting
Guardar datos binarios en el sistema de archivos requiere más planeación que guardar datos directamente en la base de datos. Se debe elegir un esquema de nomenclatura para evitar que una carga de un usuario sobrescriba la carga de otro usuario. Además, se deben realizar pasos adicionales para eliminar el archivo cargado si se produce un error en la inserción de la base de datos.
Ahora tenemos la capacidad de agregar nuevas categorías al sistema con un folleto y una imagen, pero aún hemos visto cómo actualizar los datos binarios de una categoría existente o cómo quitar correctamente los datos binarios de una categoría eliminada. Exploraremos estos dos temas en el siguiente tutorial.
¡Feliz programación!
Acerca del autor
Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando 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 en 24 horas. Se le puede contactar en mitchell@4GuysFromRolla.com.
Agradecimientos especiales a
Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. Los revisores principales de este tutorial fueron Dave Bernard, Teresa Murphy y Bernadette Leigh. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbeme a mitchell@4GuysFromRolla.com.