Mostrar datos binarios en los controles web de datos (C#)

por Scott Mitchell

Descargar PDF

En este tutorial, se verán las opciones para presentar datos binarios en una página web, incluida la presentación de un archivo de imagen y el aprovisionamiento de un vínculo "Descargar" para un archivo PDF.

Introducción

En el tutorial anterior se han explorado las dos técnicas para asociar datos binarios con un modelo de datos subyacente de una aplicación y se ha usado el control FileUpload para cargar archivos desde un explorador al sistema de archivos del servidor web. Todavía no se ha visto cómo asociar los datos binarios cargados con el modelo de datos. Es decir, después de cargar y guardar un archivo en el sistema de archivos, se debe almacenar una ruta de acceso al archivo en el registro de base de datos adecuado. Si los datos se almacenan directamente en la base de datos, los datos binarios cargados no se deben guardar en el sistema de archivos, sino que se deben insertar en la base de datos.

Antes de examinar la asociación de los datos con el modelo de datos, primero verá cómo proporcionar los datos binarios al usuario final. La presentación de datos de texto es sencilla pero, ¿cómo se deben presentar los datos binarios? Depende, por supuesto, del tipo de datos binarios. En el caso de las imágenes, es probable que quiere mostrar la imagen; para archivos PDF, documentos de Microsoft Word, archivos ZIP y otros tipos de datos binarios, proporcionar un vínculo de descarga probablemente sea más adecuado.

En este tutorial verá cómo presentar los datos binarios junto con sus datos de texto asociados mediante controles web de datos como GridView y DetailsView. El siguiente tutorial se centrará en la asociación de un archivo cargado con la base de datos.

Paso 1: Suministro de valores BrochurePath

La columna Picture de la tabla Categories ya contiene datos binarios para las distintas imágenes de categoría. En concreto, la columna Picture de cada registro contiene el contenido binario de una imagen de mapa de bits de 16 colores específica y de baja calidad. Cada imagen de categoría tiene 172 píxeles de ancho y 120 píxeles de alto, y consume aproximadamente 11 KB. Además, el contenido binario de la columna Picture incluye un encabezado OLE de 78 bytes que se debe quitar antes de mostrar la imagen. Esta información de encabezado está presente porque la base de datos Northwind tiene su origen en Microsoft Access. En Access, los datos binarios se almacenan mediante el tipo de datos OLE Object, que se agrupa en este encabezado. Por ahora, verá cómo quitar los encabezados de estas imágenes de baja calidad para mostrar la imagen. En un tutorial posterior, creará una interfaz para actualizar una columna Picture de categoría y reemplazará estas imágenes de mapa de bits que usan encabezados OLE por imágenes JPG equivalentes sin los encabezados OLE innecesarios.

En el tutorial anterior ha visto cómo usar el control FileUpload. Por tanto, puede continuar y agregar archivos de folleto al sistema de archivos del servidor web. Pero al hacerlo, no se actualiza la columna BrochurePath de la tabla Categories. Verá cómo hacerlo en el siguiente tutorial, pero por ahora es necesario proporcionar manualmente valores para esta columna.

En la descarga de este tutorial, encontrará siete archivos de folleto PDF en la carpeta ~/Brochures, uno para cada una de las categorías excepto Seafood. Se ha omitido intencionadamente cómo agregar un folleto Seafood para ilustrar cómo controlar escenarios en los que no todos los registros tienen datos binarios asociados. Para actualizar la tabla Categories con estos valores, haga clic con el botón derecho en el nodo Categories del Explorador de servidores y elija Mostrar datos de tabla. Después, escriba las rutas de acceso virtuales a los archivos de folleto de cada categoría que tenga un folleto, como se muestra en la figura 1. Como no hay ningún folleto para la categoría Seafood, deje su valor de columna BrochurePath como NULL.

Manually Enter the Values for the Categories Table s BrochurePath Column

Figura 1: Introducción manual de los valores para la columna BrochurePath de la tabla Categories (Haga clic para ver la imagen a tamaño completo)

Con los valores BrochurePath proporcionados para la tabla Categories, ya puede crear un control GridView que muestre cada categoría junto con un vínculo para descargar el folleto de la categoría. En el paso 4 ampliará este control GridView para mostrar también la imagen de la categoría.

Para empezar, arrastre un control GridView desde el Cuadro de herramientas al Diseñador de la página DisplayOrDownloadData.aspx en la carpeta BinaryData. Establezca ID de GridView en Categories y, desde la etiqueta inteligente de GridView, elija enlazarla a un nuevo origen de datos. En concreto, se debe enlazar a un objeto ObjectDataSource denominado CategoriesDataSource que recupera datos mediante el método s GetCategories() del objeto CategoriesBLL.

Create a New ObjectDataSource Named CategoriesDataSource

Figura 2: Creación de un objeto ObjectDataSource denominado CategoriesDataSource (Haga clic para ver la imagen a tamaño completo)

Configure the ObjectDataSource to Use the CategoriesBLL Class

Figura 3: Configuración de ObjectDataSource para usar la clase CategoriesBLL (Haga clic aquí para ver la imagen a tamaño completo)

Retrieve the List of Categories Using the GetCategories() Method

Figura 4: Recuperación de la lista de categorías mediante el método GetCategories() (Haga clic para ver la imagen a tamaño completo)

Después de completar el Asistente para configurar orígenes de datos, Visual Studio agregará automáticamente un elemento BoundField al control GridView Categories para CategoryID, CategoryName, Description, NumberOfProducts, BrochurePath y DataColumn. Continúe y quite la instancia NumberOfProducts de BoundField, ya que la consulta del método GetCategories() no recupera esta información. Quite también la instancia CategoryID de BoundField y cambie el nombre de las propiedades HeaderText de los controles BoundField CategoryName y BrochurePath a Category y Brochure, respectivamente. Después de realizar estos cambios, el marcado declarativo de GridView y ObjectDataSource debe ser similar al siguiente:

<asp:GridView ID="Categories" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="CategoryID"
    DataSourceID="CategoriesDataSource" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:BoundField DataField="BrochurePath" HeaderText="Brochure" 
            SortExpression="BrochurePath" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

Vea esta página en un explorador (vea la figura 5). Se muestra cada una de las ocho categorías. Las siete categorías con valores BrochurePath tienen el valor BrochurePath mostrado en el control BoundField correspondiente. Seafood, que tiene un valor NULL para BrochurePath, muestra una celda vacía.

Each Category s Name, Description, and BrochurePath Value is Listed

Figura 5: Se muestra el nombre, la descripción y el valor BrochurePath de cada categoría (Haga clic para ver la imagen a tamaño completo)

En lugar de mostrar el texto de la columna BrochurePath, quiere crear un vínculo al folleto. Para ello, quite la instancia BrochurePath de BoundField y reemplácela por un control HyperLinkField. Establezca la propiedad HeaderText del nuevo control HyperLinkField en Folleto, su propiedad Text en Ver folleto y su propiedad DataNavigateUrlFields en BrochurePath.

Add a HyperLinkField for BrochurePath

Figura 6: Adición de un control HyperLinkField para BrochurePath

Esto agregará una columna de vínculos al control GridView, como se muestra en la figura 7. Al hacer clic en un vínculo Ver folleto, se mostrará el PDF directamente en el explorador o se pedirá al usuario que descargue el archivo, en función de si se instala un lector de PDF y la configuración del explorador.

A Category s Brochure Can Be Viewed by Clicking the View Brochure Link

Figura 7: Se puede ver un folleto de categoría al hacer clic en el vínculo Ver folleto (Haga clic para ver la imagen a tamaño completo)

The Category s Brochure PDF is Displayed

Figura 8: Se muestra el PDF del folleto de la categoría (Haga clic para ver la imagen a tamaño completo)

Ocultación del texto Ver folleto para categorías sin un folleto

Como se muestra en la figura 7, le control HyperLinkField BrochurePath muestra su valor de propiedad Text (Ver folleto) para todos los registros, independientemente de si hay un valor distinto de NULL para BrochurePath. Por supuesto, si BrochurePath es NULL, el vínculo se muestra solo como texto, como sucede con la categoría Seafood (consulte la figura 7). En lugar de mostrar el texto Ver folleto, puede ser agradable que esas categorías sin un valor BrochurePath muestren un texto alternativo, como No hay un folleto disponible.

Para proporcionar este comportamiento, es necesario usar un control TemplateField cuyo contenido se genera mediante una llamada a un método de página que emite la salida adecuada en función del valor BrochurePath. Esta técnica de formato se ha explorado antes en el tutorial Uso de instancias de TemplateField en el control GridView.

Para convertir HyperLinkField en una instancia de TemplateField, seleccione el control HyperLinkField BrochurePath y, después, haga clic en el vínculo Convertir este campo en un campo de plantilla en el cuadro de diálogo Editar columnas.

Convert the HyperLinkField into a TemplateField

Figura 9: Conversión de HyperLinkField en TemplateField

Esto creará una instancia de TemplateField con un elemento ItemTemplate que contiene un control web HyperLink cuya propiedad NavigateUrl está enlazada al valor BrochurePath. Reemplace este marcado por una llamada al método GenerateBrochureLink, y pase el valor de BrochurePath:

<asp:TemplateField HeaderText="Brochure">
    <ItemTemplate>
        <%# GenerateBrochureLink(Eval("BrochurePath")) %>
    </ItemTemplate>
</asp:TemplateField>

A continuación, cree un método protected en la clase de código subyacente de la página ASP.NET denominada GenerateBrochureLink que devuelve string y acepta object como parámetro de entrada.

protected string GenerateBrochureLink(object BrochurePath)
{
    if (Convert.IsDBNull(BrochurePath))
        return "No Brochure Available";
    else
        return string.Format(@"<a href="{0}">View Brochure</a>", 
            ResolveUrl(BrochurePath.ToString()));
}

Este método determina si el valor object pasado es un valor NULL de base de datos y, en ese caso, devuelve un mensaje que indica que la categoría carece de folleto. De lo contrario, si hay un valor BrochurePath, se muestra en un hipervínculo. Tenga en cuenta que si el valor BrochurePath está presente, se pasa al método ResolveUrl(url). Este método resuelve el valor url que se pasa, y reemplaza el carácter ~ por la ruta de acceso virtual adecuada. Por ejemplo, si la raíz de la aplicación es /Tutorial55, ResolveUrl("~/Brochures/Meats.pdf") devolverá /Tutorial55/Brochures/Meat.pdf.

En la figura 10 se muestra la página después de aplicar estos cambios. Tenga en cuenta que en el campo BrochurePath de la categoría Seafood ahora muestra el texto No hay un folleto disponible.

The Text No Brochure Available is Displayed for Those Categories Without a Brochure

Figura 10: El texto No hay un folleto disponible se muestra para las categorías sin folleto (Haga clic para ver la imagen a tamaño completo)

Paso 3: Adición de una página web para mostrar una imagen de categoría

Cuando un usuario visita una página ASP.NET, recibe el código HTML de la página ASP.NET. El código HTML recibido es solo texto y no contiene datos binarios. Cualquier dato binario adicional, como imágenes, archivos de sonido, aplicaciones de Macromedia Flash, vídeos Reproductor de Windows Media insertados, etc., existen como recursos independientes en el servidor web. El código HTML contiene referencias a estos archivos, pero no incluye el contenido real de los archivos.

Por ejemplo, en HTML, el elemento <img> se usa para hacer referencia a una imagen, con el atributo src que apunta al archivo de imagen de la siguiente manera:

<img src="MyPicture.jpg" ... />

Cuando un explorador recibe este código HTML, realiza otra solicitud al servidor web para recuperar el contenido binario del archivo de imagen, que luego se muestra en el explorador. El mismo concepto se aplica a cualquier dato binario. En el paso 2, el folleto no se ha enviado al explorador como parte del marcado HTML de la página. En su lugar, en el código HTML representado se han proporcionado hipervínculos que, al hacer clic en ellos, han hecho que el explorador solicitara el documento PDF directamente.

Para mostrar o permitir que los usuarios descarguen datos binarios que residen en la base de datos, es necesario crear una página web independiente que devuelva los datos. Para esta aplicación, solo hay un campo de datos binarios almacenado directamente en la base de datos de la imagen de la categoría. Por tanto, se necesita una página que, al llamarla, devuelva los datos de imagen de una categoría determinada.

Agregue una nueva página ASP.NET a la carpeta BinaryData con el nombre DisplayCategoryPicture.aspx. Al hacerlo, deje la casilla Seleccionar página maestra desactivada. Esta página espera un valor CategoryID en la cadena de consulta y devuelve los datos binarios de la columna Picture de esa categoría. Como esta página devuelve datos binarios y nada más, no necesita ningún marcado en la sección HTML. Por tanto, haga clic en la pestaña Origen de la esquina inferior izquierda y quite todo el marcado de la página, excepto la directiva <%@ Page %>. Es decir, el marcado declarativo de DisplayCategoryPicture.aspx debe constar de una sola línea:

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="DisplayCategoryPicture.aspx.cs" 
    Inherits="BinaryData_DisplayCategoryPicture" %>

Si ve el atributo MasterPageFile en la directiva <%@ Page %>, quítelo.

En la clase de código subyacente de la página, agregue el código siguiente al controlador de eventos Page_Load:

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];
    // 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 int OleHeaderLength = 78;
    int strippedImageLength = category.Picture.Length - OleHeaderLength;
    byte[] strippedImageData = new byte[strippedImageLength];
    Array.Copy(category.Picture, OleHeaderLength, 
        strippedImageData, 0, strippedImageLength);
    
    Response.BinaryWrite(strippedImageData);
}

Este código comienza leyendo el valor de cadena de consulta CategoryID en una variable denominada categoryID. A continuación, se recuperan os datos de imagen mediante una llamada al método GetCategoryWithBinaryDataByCategoryID(categoryID) de la clase CategoriesBLL. Estos datos se devuelven al cliente mediante el método Response.BinaryWrite(data), pero antes de llamarlo, se debe quitar el encabezado OLE del valor de columna Picture. Para ello, se crea una matriz byte denominada strippedImageData que contendrá exactamente 78 caracteres menos que lo que se encuentra en la columna Picture. El método Array.Copy se usa para copiar los datos de category.Picture a partir de la posición 78 hasta strippedImageData.

La propiedad Response.ContentType especifica el tipo MIME del contenido que se devuelve para que el explorador sepa cómo representarlo. Como la columna Picture de la tabla Categories es una imagen de mapa de bits, aquí se usa el tipo MIME de mapa de bits (image/bmp). Si omite el tipo MIME, la mayoría de los exploradores seguirán mostrando la imagen correctamente porque pueden deducir el tipo en función del contenido de los datos binarios del archivo de imagen. Pero es recomendable incluir el tipo MIME siempre que sea posible. Vea el sitio web de Internet Assigned Numbers Authority para obtener una lista completa de los tipos de medios MIME.

Después de crear esta página, se puede ver una imagen de categoría determinada si se visita DisplayCategoryPicture.aspx?CategoryID=categoryID. En la figura 11 se muestra la imagen de la categoría Beverages, que se puede ver en DisplayCategoryPicture.aspx?CategoryID=1.

The Beverages Category s Picture is Displayed

Figura 11: Se muestra la imagen de la categoría Beverages (Haga clic para ver la imagen a tamaño completo)

Si al visitar DisplayCategoryPicture.aspx?CategoryID=categoryID se inicia una excepción que indica No se puede convertir el objeto de tipo "System.DBNull" al tipo "System.Byte[]", se puede deber a dos motivos. En primer lugar, la columna Picture de la tabla Categories permite valores NULL. Pero en la página DisplayCategoryPicture.aspx se supone que hay un valor distinto de NULL presente. No se puede acceder directamente a la propiedad Picture de CategoriesDataTable si tiene un valor NULL. Si quiere permitir valores NULL para la columna Picture, tendrá que incluir la siguiente condición:

if (category.IsPictureNull())
{
    // Display some "No Image Available" picture
    Response.Redirect("~/Images/NoPictureAvailable.gif");
}
else
{
    // Send back the binary contents of the Picture column
    // ... Set ContentType property and write out ...
    // ... data via Response.BinaryWrite ...
}

En el código anterior se supone que hay un archivo de imagen denominado NoPictureAvailable.gif en la carpeta Images que quiere mostrar para las categorías sin una imagen.

Esta excepción también podría deberse a que la instrucción SELECT del método GetCategoryWithBinaryDataByCategoryID de CategoriesTableAdapter se ha revertido a la lista de columnas de la consulta principal, lo que puede ocurrir si usa instrucciones SQL ad hoc y ha vuelto a ejecutar el asistente para la consulta principal de TableAdapter. Compruebe que la instrucción SELECT del método GetCategoryWithBinaryDataByCategoryID todavía incluye la columna Picture.

Nota:

Cada vez que se visita DisplayCategoryPicture.aspx, se accede a la base de datos y se devuelven los datos de imagen de la categoría especificada. Pero si la imagen de la categoría no ha cambiado desde que el usuario la ha visto por última vez, se ha desperdiciado el esfuerzo. Afortunadamente, HTTP permite operaciones GET condicionales. Con una operación GET condicional, el cliente que realiza la solicitud HTTP envía un encabezado If-Modified-Since HTTP que proporciona la fecha y hora a la que el cliente ha recuperado por última vez este recurso del servidor web. Si el contenido no ha cambiado desde esta fecha especificada, el servidor web puede responder con un código de estado No modificado (304) y omitir la devolución del contenido del recurso solicitado. En resumen, esta técnica evita que el servidor web tenga que devolver contenido para un recurso si no se ha modificado desde que el cliente ha accedido por última vez.

Pero para implementar este comportamiento, es necesario agregar una columna PictureLastModified a la tabla Categories para capturar cuándo se ha actualizado por última vez la columna Picture, así como código para comprobar el encabezado If-Modified-Since. Para más información sobre el encabezado If-Modified-Since y el flujo de trabajo GET condicional, vea GET condicional de HTTP para hackers de RSS y Un examen profundo a las solicitudes HTTP en una página ASP.NET.

Paso 4: Representación de las imágenes de categoría en un control GridView

Ahora que tiene una página web para mostrar una imagen de categoría determinada, la puede mostrar mediante el control web Image un elemento <img> HTML que apunte a DisplayCategoryPicture.aspx?CategoryID=categoryID. Las imágenes cuya dirección URL viene determinada por los datos de la base de datos se pueden mostrar en controles GridView o DetailsView mediante ImageField. ImageField contiene propiedades DataImageUrlField y DataImageUrlFormatString que funcionan como las propiedades DataNavigateUrlFields y DataNavigateUrlFormatString de HyperLinkField.

Ahora se ampliará el control GridView Categories en DisplayOrDownloadData.aspx mediante la adición de una instancia de ImageField para mostrar cada imagen de categoría. Simplemente agregue ImageField y establezca sus propiedades DataImageUrlField y DataImageUrlFormatString en CategoryID y DisplayCategoryPicture.aspx?CategoryID={0}, respectivamente. Esto creará una columna GridView que representa un elemento <img> cuyo atributo src hace referencia a DisplayCategoryPicture.aspx?CategoryID={0}, donde {0} se reemplaza por el valor CategoryID de la fila de GridView.

Add an ImageField to the GridView

Figura 12: Adición de ImageField a GridView

Después de agregar ImageField, la sintaxis declarativa del control GridView debe ser similar a la siguiente:

<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:TemplateField HeaderText="Brochure">
            <ItemTemplate>
                <%# GenerateBrochureLink(Eval("BrochurePath")) %>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:ImageField DataImageUrlField="CategoryID" 
            DataImageUrlFormatString="DisplayCategoryPicture.aspx?CategoryID={0}">
        </asp:ImageField>
    </Columns>
</asp:GridView>

Dedique un momento a ver esta página en un explorador. Observe cómo cada registro ahora incluye una imagen para la categoría.

The Category s Picture is Displayed for Each Row

Figura 13: Se muestra la imagen de la categoría para cada fila (Haga clic para ver la imagen a tamaño completo)

Resumen

En este tutorial se ha examinado cómo presentar datos binarios. La forma de presentar los datos depende del tipo de datos. Para los archivos de folleto PDF, se ha ofrecido al usuario un vínculo Ver folleto que, al hacer clic en él, ha llevado al usuario directamente al archivo PDF. Para la imagen de la categoría, primero se ha creado una página para recuperar y devolver los datos binarios de la base de datos y, después, se ha usado esa página para mostrar cada imagen de categoría en un control GridView.

Ahora que ha visto cómo mostrar datos binarios, ya puede examinar cómo realizar la inserción, las actualizaciones y las eliminaciones en la base de datos con los datos binarios. En el siguiente tutorial verá cómo asociar un archivo cargado con su registro de base de datos correspondiente. En el tutorial siguiente, verá cómo actualizar los datos binarios existentes y cómo eliminar los datos binarios cuando se quita su registro asociado.

¡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, instructor y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él en mitchell@4GuysFromRolla.com. o a través de su blog, http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. Los revisores principales de este tutorial han sido Teresa Murphy y Randy Schmidt. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.