Отображение двоичных данных в веб-элементах управления данными (C#)

Скотт Митчелл

Скачать в формате PDF

В этом руководстве мы рассмотрим параметры представления двоичных данных на веб-странице, включая отображение файла изображения и подготовку ссылки "Скачать" для PDF-файла.

Введение

В предыдущем руководстве мы изучили два метода связывания двоичных данных с базовой моделью данных приложения и использовали элемент управления FileUpload для отправки файлов из браузера в файловую систему веб-сервера. Мы еще не узнали, как связать отправленные двоичные данные с моделью данных. То есть после отправки и сохранения файла в файловой системе путь к файлу должен храниться в соответствующей записи базы данных. Если данные хранятся непосредственно в базе данных, то переданные двоичные данные не должны быть сохранены в файловой системе, но их необходимо внедрить в базу данных.

Прежде чем рассматривать связывание данных с моделью данных, давайте сначала рассмотрим, как предоставить двоичные данные конечному пользователю. Отображение текстовых данных достаточно просто, но как должны быть представлены двоичные данные? Это зависит, конечно, от типа двоичных данных. Для изображений, скорее всего, мы хотим отобразить изображение; для PDF-файлов, документов Microsoft Word, ZIP-файлов и других типов двоичных данных, предоставляя ссылку для скачивания, вероятно, более подходит.

В этом руководстве мы рассмотрим, как представить двоичные данные вместе с связанными текстовыми данными с помощью веб-элементов управления данными, таких как GridView и DetailsView. В следующем руководстве мы переключим внимание на связывание отправленного файла с базой данных.

Шаг 1. ПредоставлениеBrochurePathзначений

Столбец Picture в Categories таблице уже содержит двоичные данные для различных образов категорий. В частности, Picture столбец для каждой записи содержит двоичное содержимое зернистого, низкого качества, 16-цветового растрового изображения. Каждое изображение категории имеет ширину 172 пикселей и 120 пикселей высотой и потребляет примерно 11 КБ. Кроме того, двоичное содержимое в Picture столбце содержит 78-байтовый заголовок OLE , который необходимо удалить перед отображением изображения. Эти сведения о заголовке присутствуют, так как база данных Northwind имеет свои корни в Microsoft Access. В Access двоичные данные хранятся с помощью типа данных OLE Object, который добавляет этот заголовок. Теперь мы посмотрим, как удалить заголовки с этих низкокачественных изображений, чтобы показать изображение. В следующем руководстве мы создадим интерфейс для обновления столбца Picture категории и заменим эти растровые изображения, использующие заголовки OLE с эквивалентными изображениями JPG без ненужных заголовков OLE.

В предыдущем руководстве мы узнали, как использовать элемент управления FileUpload. Таким образом, вы можете идти вперед и добавить файлы брошюры в файловую систему веб-сервера. Однако это не обновляет BrochurePath столбец в Categories таблице. В следующем руководстве мы посмотрим, как это сделать, но теперь нам нужно вручную указать значения для этого столбца.

В этом руководстве вы найдете семь PDF-файлов брошюр в папке ~/Brochures , по одному для каждой категории, кроме морепродуктов. Я намеренно опущен добавление брошюры "Морепродукты", чтобы иллюстрировать, как обрабатывать сценарии, где не все записи имеют связанные двоичные данные. Чтобы обновить Categories таблицу с этими значениями, щелкните правой кнопкой мыши Categories узел из обозревателя серверов и выберите "Показать данные таблицы". Затем введите виртуальные пути к файлам брошюр для каждой категории, которая содержит брошюру, как показано на рисунке 1. Так как нет брошюры для категории "Морепродукты", оставьте значение столбца BrochurePath как NULL.

Вручную введите значения для столбца BrochurePath таблицы категорий

Рис. 1. Введите значения столбца CategoriesBrochurePath таблицы вручную (щелкните, чтобы просмотреть изображение полного размера)

BrochurePath При использовании значений, предоставленных для Categories таблицы, мы готовы создать GridView, который перечисляет каждую категорию вместе со ссылкой для скачивания брошюры категории. На шаге 4 мы расширим этот GridView, чтобы также отобразить изображение категории.

Начните с перетаскивания GridView из Панели инструментов в конструктор страницы DisplayOrDownloadData.aspx, которая находится в папке BinaryData. Назначьте ID для GridView как Categories, а затем с помощью смарт-тега GridView выберите привязку к новому источнику данных. В частности, привязать его к объекту ObjectDataSource с именем CategoriesDataSource , который извлекает данные с помощью CategoriesBLL метода объекта GetCategories() .

Создание объекта ObjectDataSource с именем CategoriesDataSource

Рис. 2: Создайте новый ObjectDataSource с именем CategoriesDataSource (Щелкните, чтобы увидеть изображение в полном размере)

Настройка ObjectDataSource для использования класса CategoriesBLL

Рис. 3. Настройка ObjectDataSource для использования CategoriesBLL класса (щелкните, чтобы просмотреть изображение полного размера)

Получение списка категорий с помощью метода GetCategories()

Рис. 4. Получение списка категорий с помощью GetCategories() метода (щелкните, чтобы просмотреть изображение полного размера)

После завершения работы мастера настройки источника данных Visual Studio автоматически добавит BoundField в Categories GridView для CategoryID, CategoryName, Description, NumberOfProducts, BrochurePath и DataColumn. Продолжайте и удалите NumberOfProducts BoundField, так как запрос метода GetCategories() не извлекает эту информацию. Также удалите CategoryID BoundField и переименуйте BoundFields CategoryName и BrochurePath свойства HeaderText в Категория и Брошюра соответственно. После внесения этих изменений декларативная разметка GridView и ObjectDataSource должны выглядеть следующим образом:

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

Просмотрите эту страницу через браузер (см. рисунок 5). В списке перечислены все восемь категорий. Семь категорий со BrochurePath значениями имеют BrochurePath значение, отображаемое в соответствующем BoundField. Морепродукты, имеющие значение для своего BrochurePath, отображают пустую NULL ячейку.

Для каждой категории перечислены Имя, Описание и значение BrochurePath

Рис. 5. Имя каждой категории, описание и BrochurePath значение перечислены (щелкните, чтобы просмотреть изображение полного размера)

Вместо отображения текста столбца BrochurePath мы хотим создать ссылку на брошюру. Для этого удалите BrochurePath BoundField и замените его гиперлинкфилдом. Установите для нового свойства HyperLinkField значение HeaderText - "Брошюра", для свойства Text - "Просмотреть брошюру", и для свойства DataNavigateUrlFields значение BrochurePath.

Добавление HyperLinkField для BrochurePath

Рис. 6. Добавление HyperLinkField для BrochurePath

Это добавит столбец ссылок на GridView, как показано на рисунке 7. Щелкнув ссылку "Просмотреть брошюру", вы увидите PDF-файл непосредственно в браузере или предложите пользователю скачать файл в зависимости от того, установлен ли модуль чтения PDF и параметры браузера.

Брошюра категории можно просмотреть, щелкнув ссылку

Рис. 7. Брошюра категории можно просмотреть, щелкнув ссылку "Просмотреть брошюру" (щелкните, чтобы просмотреть изображение полного размера)

Отображается PDF-файл брошюры категории

Рис. 8. Отображается PDF-файл брошюры категории (щелкните, чтобы просмотреть изображение полного размера)

Скрытие текста "Просмотреть брошюру" для категорий без брошюры

Как показано на рисунке 7, HyperLinkField отображает значение своего свойства BrochurePath (Просмотреть брошюру) для всех записей, независимо от того, существует ли значение, отличное от значения BrochurePath. Конечно, если BrochurePath это NULLтак, ссылка отображается только в виде текста, как и в случае с категорией "Морепродукты" (см. рис. 7). Вместо отображения текста "Посмотреть брошюру", было бы неплохо, чтобы для категорий без значения BrochurePath отображался альтернативный текст, например "Брошюра недоступна".

Чтобы обеспечить это поведение, необходимо использовать TemplateField, содержимое которого создается вызовом метода страницы, который выдает соответствующие выходные данные на основе значения BrochurePath. Сначала мы изучили этот метод форматирования в руководстве по использованию TemplateFields в элементе управления GridView.

Преобразуйте HyperLinkField в TemplateField, выбрав BrochurePath HyperLinkField и нажав на ссылку "Преобразовать это поле в TemplateField" в диалоговом окне "Изменение столбцов".

Преобразование HyperLinkField в TemplateField

Рис. 9. Преобразование HyperLinkField в templateField

Это создаст элемент TemplateField с ItemTemplate, который содержит элемент управления HyperLink Web, свойство NavigateUrl которого связано со значением BrochurePath. Замените эту разметку вызовом метода GenerateBrochureLink, передавая значение BrochurePath:

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

Затем создайте protected метод в классе кодовой части страницы ASP.NET, который GenerateBrochureLink возвращает string и принимает object входной параметр.

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()));
}

Этот метод определяет, является ли переданное object значение базой данных NULL и, если да, возвращает сообщение, указывающее, что категория не имеет брошюры. В противном случае, если имеется BrochurePath значение, оно отображается в гиперссылке. Обратите внимание, что если BrochurePath значение присутствует, оно передается в ResolveUrl(url) метод. Этот метод обрабатывает переданный URL-адрес, заменяя символ ~ на соответствующий виртуальный путь. Например, если приложение расположено в корне /Tutorial55, то ResolveUrl("~/Brochures/Meats.pdf") вернёт /Tutorial55/Brochures/Meat.pdf.

На рисунке 10 показана страница после применения этих изменений. Обратите внимание, что поле категории "Морепродукты" BrochurePath теперь отображает текст "Нет брошюры доступны".

Текст

Рис. 10: Отображается текст "Нет доступной брошюры" для категорий без брошюры (щелкните, чтобы просмотреть изображение полного размера)

Шаг 3. Добавление веб-страницы для отображения рисунка категории

Когда пользователь посещает страницу ASP.NET, он получает HTML-код страницы ASP.NET. Полученный HTML-код является просто текстом и не содержит двоичных данных. Любые дополнительные двоичные данные, такие как изображения, звуковые файлы, приложения Macromedia Flash, внедренные Медиаплеер Windows видео и т. д., существуют в виде отдельных ресурсов на веб-сервере. HTML содержит ссылки на эти файлы, но не включает фактическое содержимое файлов.

Например, в HTML <img> элемент используется для ссылки на рисунок с src атрибутом, указывающим на файл изображения следующим образом:

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

Когда браузер получает этот HTML-код, он отправляет другой запрос на веб-сервер, чтобы получить двоичное содержимое файла изображения, который затем отображается в браузере. Та же концепция применяется к любым двоичным данным. На втором шаге брошюра не была отправлена в браузер в составе HTML-разметки страницы. Скорее, отрисованный HTML-код предоставил гиперссылки, которые при щелчке привели к тому, что браузер запрашивает PDF-документ напрямую.

Чтобы отобразить или разрешить пользователям загружать двоичные данные, находящиеся в базе данных, необходимо создать отдельную веб-страницу, которая возвращает данные. Для нашего приложения существует только одно двоичное поле данных, хранящееся непосредственно в базе данных, изображение категории. Поэтому нам нужна страница, которая при вызове возвращает данные изображения для определенной категории.

Добавьте новую страницу ASP.NET в папку BinaryData с именем DisplayCategoryPicture.aspx. При этом снимите флажок "Выбор главной страницы". Эта страница ожидает значение CategoryID в строке запроса и возвращает двоичные данные столбца Picture этой категории. Так как эта страница возвращает двоичные данные и ничего другого, она не нуждается в разметке в разделе HTML. Поэтому щелкните вкладку "Источник" в левом нижнем углу и удалите все разметки страницы, кроме директивы <%@ Page %> . То есть декларативная DisplayCategoryPicture.aspx разметка должна состоять из одной строки.

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

Если вы видите атрибут MasterPageFile в директиве <%@ Page %>, удалите его.

В классе обратной стороны страницы добавьте следующий код в обработчик событий 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);
}

Этот код начинается с чтения значения CategoryID строки запроса в переменную с именем categoryID. Затем данные рисунка извлекаются посредством вызова метода GetCategoryWithBinaryDataByCategoryID(categoryID) класса CategoriesBLL. Эти данные возвращаются клиенту с помощью метода Response.BinaryWrite(data), но перед вызовом этого метода необходимо удалить заголовок OLE из значения столбца Picture. Это достигается путем создания массива byte с именем strippedImageData , который будет содержать ровно 78 символов меньше, чем то, что находится в столбце Picture . Метод Array.Copy используется для копирования данных с позиции 78 category.Picture в strippedImageData.

Свойство Response.ContentType указывает тип MIME возвращаемого содержимого, чтобы браузер знал, как его отрисовать. Categories Так как столбец таблицы Picture представляет собой растровое изображение, то здесь используется тип MIME растрового изображения (image/bmp). Если опустить тип MIME, большинство браузеров по-прежнему будут отображать изображение правильно, так как они могут выводить тип на основе содержимого двоичных данных файла изображения. Однако рекомендуется включить тип MIME, если это возможно. Полный список типов носителей MIME см. на веб-сайте Центра назначения номеров Интернета.

При помощи этой страницы можно просмотреть изображение конкретной категории, перейдя по ссылке DisplayCategoryPicture.aspx?CategoryID=categoryID. На рисунке 11 показано изображение категории напитков, которое можно просмотреть с DisplayCategoryPicture.aspx?CategoryID=1.

Отображается изображение категории напитков

Рис. 11. Изображение категории напитков отображается (щелкните, чтобы просмотреть изображение полного размера)

Если при посещении DisplayCategoryPicture.aspx?CategoryID=categoryID вы получаете исключение с сообщением "Не удается привести объект типа 'System.DBNull' к типу 'System.Byte[]'", это может быть вызвано двумя причинами. Во-первых, таблицы Picture столбец допускает NULL значения. На странице DisplayCategoryPicture.aspx предполагается наличие значения, отличного от NULL. Свойство Picture объекта CategoriesDataTable невозможно получить напрямую, если оно имеет значение NULL. Если вы хотите разрешить NULL значения для столбца Picture, вы должны включить следующее условие:

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

В приведенном выше коде предполагается, что в папке Images есть какой-то файл изображения с именем NoPictureAvailable.gif, который будет отображаться для этих категорий без картинки.

Это исключение также может быть вызвано, если оператор метода CategoriesTableAdapter вернулся к списку столбцов основного запроса GetCategoryWithBinaryDataByCategoryID, что может произойти, если вы пользуетесь произвольными SQL инструкциями и заново запустите мастер для основного запроса TableAdapter SELECT. Убедитесь, что оператор GetCategoryWithBinaryDataByCategoryID метода SELECT по-прежнему содержит столбец Picture.

Примечание.

DisplayCategoryPicture.aspx Каждый раз, когда производится посещение, осуществляется доступ к базе данных, и предоставляются данные изображения указанной категории. Если изображение категории не изменилось с тех пор, как пользователь последний раз его просматривал, это напрасная трата усилий. К счастью, HTTP позволяет условные GET-запросы. При использовании условного GET клиент, выполняющий HTTP-запрос, отправляет If-Modified-Since HTTP-заголовок, содержащий дату и время последнего получения этого ресурса клиентом с веб-сервера. Если содержимое не изменилось с указанной даты, веб-сервер может ответить с кодом состояния "Не изменено" (304) и отказаться от отправки содержимого запрошенного ресурса. Короче говоря, этот метод позволяет веб-серверу от необходимости отправлять обратное содержимое для ресурса, если он не был изменен с момента последнего доступа к нему клиента.

Однако чтобы реализовать это поведение, необходимо добавить столбец PictureLastModified в таблицу Categories, чтобы зафиксировать, когда столбец Picture в последний раз обновлялся, а также добавить код для проверки заголовка If-Modified-Since. Дополнительные сведения о заголовке и условном запросе GET см. HTTP Conditional GET для хакеров RSS и Подробнее о выполнении HTTP-запросов на странице ASP.NET.

Шаг 4. Отображение изображений категорий в GridView

Теперь, когда у нас есть веб-страница для отображения изображения определенной категории, мы можем отобразить его с помощью элемента управления "Веб-изображение" или элемента HTML <img> , указывающего на DisplayCategoryPicture.aspx?CategoryID=categoryIDнего. Изображения, URL-адрес которых определяется данными базы данных, можно отобразить в GridView или DetailsView с помощью ImageField. ImageField содержит свойства DataImageUrlField и DataImageUrlFormatString, которые работают так же, как и свойства HyperLinkField DataNavigateUrlFields и DataNavigateUrlFormatString.

Давайте расширим Categories GridView в DisplayOrDownloadData.aspx, добавив элемент ImageField для отображения изображения каждой категории. Просто добавьте ImageField и задайте его DataImageUrlField и DataImageUrlFormatString свойства на CategoryID и DisplayCategoryPicture.aspx?CategoryID={0}, соответственно. При этом будет создан столбец GridView, который отображает элемент <img>, атрибут src которого ссылается на DisplayCategoryPicture.aspx?CategoryID={0}, где {0} заменяется значением строки GridView CategoryID.

Добавление ImageField в GridView

Рис. 12. Добавление ImageField в GridView

После добавления ImageField декларативный синтаксис вашего GridView должен выглядеть следующим образом:

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

Просмотрите эту страницу через браузер. Обратите внимание, что каждая запись теперь содержит рисунок для категории.

Изображение категории отображается для каждой строки

Рис. 13. Рисунок категории отображается для каждой строки (щелкните, чтобы просмотреть изображение полного размера)

Итоги

В этом руководстве мы рассмотрели, как представить двоичные данные. Способ представления данных зависит от типа данных. Для PDF-файлов брошюр мы предложили пользователю ссылку "Просмотреть брошюру", которая при щелчке взяла пользователя непосредственно в PDF-файл. Для рисунка категории сначала мы создали страницу для получения и возврата двоичных данных из базы данных, а затем использовали эту страницу для отображения каждого рисунка категории в GridView.

Теперь, когда мы рассмотрели, как отобразить двоичные данные, мы готовы изучить, как выполнять вставку, обновление и удаление базы данных с двоичными данными. В следующем руководстве мы рассмотрим, как связать загруженный файл с соответствующей записью базы данных. В следующем руководстве мы рассмотрим, как обновить имеющиеся двоичные данные, а также как их удалить при удалении связанной записи.

Счастливое программирование!

Об авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams обучает ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.

Особое спасибо кому

Эта серия учебников была проверена многими полезными рецензентами. Ведущими рецензентами этого руководства были Тереза Мерфи и Дэйв Гарднер. Хотите просмотреть мои предстоящие статьи MSDN? Если да, напишите мне на mitchell@4GuysFromRolla.com.