Compartir a través de


Incluir una opción de carga de archivos al agregar un nuevo registro (VB)

de Scott Mitchell

Descargar PDF

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 unInsertWithPicturemé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, Updatey 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 CategoryNamecolumnas , Description, BrochurePathy 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.

Selección de la opción INSERT

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.

Asigne un nombre al nuevo método TableAdapter InsertWithPicture.

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.

Copiar y pegar la sintaxis declarativa de DisplayOrDownload.aspx en 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.

Ahora debería ver cada categoría junto con sus datos binarios

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.

Configurar ObjectDataSource para usar el método InsertWithPicture

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.

Captura de pantalla de DetailsView con la propiedad CategoryID establecida en NewCategory, los valores de la propiedad Height y Width están vacíos y la casilla Habilitar inserción activada.

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 CategoryNameBrochurePath 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.

Captura de pantalla que muestra la ventana campos con TemplateField, Picture y HeaderText resaltados.

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 IDBrochureUpload. Del mismo modo, agregue un control FileUpload a TemplateField Picture s InsertItemTemplate. Establezca este control FileUpload en ID a PictureUpload.

Agregar un control FileUpload a InsertItemTemplate

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.aspxpá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.

Se muestra un mensaje de advertencia si se carga un tipo de archivo no válido

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.

No se muestra la imagen de la nueva categoría

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.

Las imágenes JPG para nuevas categorías se representan correctamente

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.