Поделиться через


Включение параметра отправки файла при добавлении новой записи (VB)

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

Загрузить PDF-файл

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

Введение

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

В этом руководстве мы создадим веб-страницу для добавления новой категории. В дополнение к TextBoxes для имени и описания категории эта страница должна содержать два элемента управления FileUpload, один для нового изображения категории и один для брошюры. Отправленный рисунок будет храниться непосредственно в столбце Picture новой записи, а брошюра будет сохранена в папке ~/Brochures с путем к файлу, сохраненном в столбце BrochurePath новой записи.

Перед созданием этой новой веб-страницы необходимо обновить архитектуру. Запрос CategoriesTableAdapter main не получает Picture столбец. Следовательно, автоматически созданный Insert метод имеет только входные данные для CategoryNameполей , Descriptionи BrochurePath . Поэтому необходимо создать дополнительный метод в TableAdapter, который запрашивает все четыре Categories поля. Класс CategoriesBLL уровня бизнес-логики также потребуется обновить.

Шаг 1. ДобавлениеInsertWithPictureметода вCategoriesTableAdapter

При создании обратного CategoriesTableAdapter объекта в учебнике Создание уровня доступа к данным мы настроили его для автоматического создания INSERTинструкций , UPDATEи DELETE на основе запроса main. Кроме того, мы поручили TableAdapter использовать подход DB Direct, который создал методы Insert, Updateи Delete. Эти методы выполняют автоматически созданные INSERTинструкции , UPDATEи DELETE и, следовательно, принимают входные параметры на основе столбцов, возвращаемых запросом main. В учебнике Отправка файлов мы дополнили CategoriesTableAdapter запрос main для использования столбца BrochurePath .

CategoriesTableAdapter Так как запрос main не ссылается на Picture столбец, мы не можем ни добавить новую запись, ни обновить существующую запись со значением столбцаPicture. Чтобы записать эти сведения, можно создать новый метод в TableAdapter, который используется специально для вставки записи с двоичными данными INSERT , или настроить автоматически созданный оператор. Проблема с настройкой автоматически созданной инструкции INSERT заключается в том, что мы рискуем перезаписывать настройки мастером. Например, представьте, что мы настроили инструкцию INSERT так, чтобы она включала использование столбца Picture . Это приведет к обновлению метода TableAdapter, Insert чтобы включить дополнительный входной параметр для двоичных данных изображения категории. Затем мы могли бы создать метод на уровне бизнес-логики, чтобы использовать этот метод DAL и вызвать этот метод BLL через уровень презентации, и все будет работать замечательно. То есть до следующей настройки TableAdapter с помощью мастера настройки TableAdapter. Как только мастер завершит работу, наши настройки INSERT инструкции будут перезаписаны, Insert метод отменить изменения в старую форму, и наш код больше не будет компилироваться!

Примечание

Эта проблема не возникает при использовании хранимых процедур вместо нерегламентированных инструкций SQL. В будущем учебнике будет рассмотрено использование хранимых процедур вместо нерегламентированных инструкций SQL на уровне доступа к данным.

Чтобы избежать этой потенциальной головной боли, вместо настройки автоматически создаваемых инструкций SQL давайте создадим новый метод для TableAdapter. Этот метод с именем InsertWithPictureпринимает значения для CategoryNameстолбцов , Description, BrochurePathи Picture и выполняет инструкцию INSERT , которая сохраняет все четыре значения в новой записи.

Откройте типизированный набор данных и в Designer щелкните правой кнопкой мыши заголовок CategoriesTableAdapter и выберите в контекстном меню пункт Добавить запрос. В этом случае запускается мастер настройки запросов TableAdapter, который начинается с запроса TableAdapter, который начинается с запроса TableAdapter, который должен получить доступ к базе данных. Выберите Использовать инструкции SQL и нажмите кнопку Далее. На следующем шаге запрашивается тип создаваемого запроса. Так как мы повторно создадим запрос для добавления новой записи в таблицу Categories , нажмите кнопку ВСТАВКА и нажмите кнопку Далее.

Выбор параметра INSERT

Рис. 1. Выбор параметра INSERT (щелкните для просмотра полноразмерного изображения)

Теперь необходимо указать инструкцию INSERT SQL. Мастер автоматически предлагает инструкцию, соответствующую INSERT запросу main TableAdapter. В этом случае это INSERT оператор, который вставляет CategoryNameзначения , Descriptionи BrochurePath . Обновите инструкцию таким образом, чтобы Picture столбец был включен вместе с параметром @Picture , например:

INSERT INTO [Categories] 
    ([CategoryName], [Description], [BrochurePath], [Picture]) 
VALUES 
    (@CategoryName, @Description, @BrochurePath, @Picture)

На последнем экране мастера появится запрос на присвоение имени новому методу TableAdapter. Введите InsertWithPicture и нажмите кнопку Готово.

Присвойте новому методу TableAdapter имя InsertWithPicture

Рис. 2. Присвойтите имя методу InsertWithPicture New TableAdapter (Щелкните для просмотра полноразмерного изображения)

Шаг 2. Обновление уровня бизнес-логики

Так как уровень представления должен только интерфейсировать со слоем бизнес-логики, а не обходить его для перехода непосредственно на уровень доступа к данным, необходимо создать метод BLL, который вызывает только что созданный метод DAL (InsertWithPicture). В этом руководстве создайте метод в классе с именем InsertWithPicture , который принимает в CategoriesBLL качестве входных данных три String и Byte массив. Входные String параметры относятся к имени категории, описанию и пути к файлу брошюры, а Byte массив — для двоичного содержимого рисунка категории. Как показано в следующем коде, этот метод BLL вызывает соответствующий метод DAL:

<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

Примечание

Перед добавлением InsertWithPicture метода в BLL убедитесь, что типизированный набор данных сохранен. CategoriesTableAdapter Так как код класса создается автоматически на основе typed DataSet, если вы не сохраните изменения в typed DataSet, Adapter свойство не будет знать о методе InsertWithPicture .

Шаг 3. Перечисление существующих категорий и их двоичных данных

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

Начните с открытия страницы DisplayOrDownload.aspx из BinaryData папки. Перейдите в представление Источник и скопируйте декларативный синтаксис GridView и ObjectDataSource, вставив его в <asp:Content> элемент в UploadInDetailsView.aspx. Кроме того, не забудьте скопировать GenerateBrochureLink метод из класса кода программной DisplayOrDownload.aspx части в UploadInDetailsView.aspx.

Скопируйте и вставьте декларативный синтаксис из DisplayOrDownload.aspx в UploadInDetailsView.aspx

Рис. 3. Копирование и вставка декларативного синтаксиса из DisplayOrDownload.aspx в UploadInDetailsView.aspx (щелкните для просмотра полноразмерного изображения)

После копирования декларативного синтаксиса и GenerateBrochureLink метода на страницу UploadInDetailsView.aspx просмотрите страницу в браузере, чтобы убедиться, что все было скопировано правильно. Вы увидите GridView со списком восьми категорий, который содержит ссылку для скачивания брошюры, а также изображение категории.

Теперь вы должны увидеть каждую категорию вместе с ее двоичными данными

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

Шаг 4. Настройка для поддержкиCategoriesDataSourceвставки

Объект CategoriesDataSource ObjectDataSource, используемый Categories GridView, в настоящее время не предоставляет возможности вставки данных. Чтобы обеспечить поддержку вставки через этот элемент управления источником данных, необходимо сопоставить его Insert метод с методом в базовом объекте . CategoriesBLL В частности, мы хотим сопоставить его с методомCategoriesBLL, добавленным на шаге 2. InsertWithPicture

Для начала щелкните ссылку Настройка источника данных из смарт-тега ObjectDataSource. На первом экране показан объект, для работы с которым настроен источник данных, CategoriesBLL. Оставьте этот параметр как есть и нажмите кнопку Далее, чтобы перейти к экрану Определение методов данных. Перейдите на вкладку INSERT и выберите InsertWithPicture метод из раскрывающегося списка. Чтобы завершить работу мастера, нажмите кнопку Готово.

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

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

Примечание

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

После завершения работы мастера ObjectDataSource теперь будет включать значение для своего InsertMethod свойства, а также InsertParameters для четырех столбцов категорий, как показано в следующей декларативной разметке:

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

Шаг 5. Создание интерфейса вставки

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

Начните с перетаскивания DetailsView с панели элементов на Designer над GridView, задав его свойству значение NewCategory и очистив Height значения свойств и Width .ID Из смарт-тега DetailsView привяжите его к существующемуCategoriesDataSource, а затем проверка флажок Включить вставку.

Снимок экрана: DetailsView со свойством CategoryID, равным NewCategory, пустыми значениями свойств Height и Width и установлен флажок Включить вставку.

Рис. 6. Привязка DetailsView к CategoriesDataSource и включение вставки (щелкните для просмотра полноразмерного изображения)

Чтобы окончательно отобразить DetailsView в интерфейсе вставки, присвойте свойству DefaultMode значение Insert.

Обратите внимание, что DetailsView имеет пять BoundFields CategoryID, CategoryName, NumberOfProductsDescriptionи BrochurePath , хотя CategoryID BoundField не отображается в интерфейсе вставки, так как его InsertVisible свойству Falseприсвоено значение . Эти Поля BoundFields существуют, так как они являются столбцами, возвращаемыми методом GetCategories() , который вызывает ObjectDataSource для получения своих данных. Однако для вставки не нужно разрешать пользователю указывать значение для NumberOfProducts. Кроме того, мы должны позволить им загрузить фотографию для новой категории, а также загрузить PDF-файл для брошюры.

NumberOfProducts Удалите BoundField из DetailsView, а затем обновите HeaderText свойства CategoryName и BrochurePath BoundFields на Category и Буклет соответственно. Затем преобразуйте BrochurePath BoundField в TemplateField и добавьте новое поле TemplateField для рисунка, присвоив этому новому templateField HeaderText значение Picture. Переместите Picture templateField так, чтобы оно было между BrochurePath TemplateField и CommandField.

Снимок экрана: окно полей с выделенными полями TemplateField, Picture и HeaderText.

Рис. 7. Привязка DetailsView к элементу CategoriesDataSource и включение вставки

Если вы преобразовали BrochurePath BoundField в TemplateField с помощью диалогового окна Изменение полей, templateField включает ItemTemplate, EditItemTemplateи InsertItemTemplate. InsertItemTemplate Однако требуется только , поэтому вы можете удалить два других шаблона. На этом этапе декларативный синтаксис DetailsView должен выглядеть следующим образом:

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

Добавление элементов управления FileUpload для полей буклета и рисунка

В настоящее BrochurePath время templateField InsertItemTemplate содержит Элемент TextBox, а Picture TemplateField не содержит никаких шаблонов. Нам нужно обновить эти два шаблона для InsertItemTemplate использования элементов управления FileUpload.

В смарт-теге DetailsView выберите параметр Изменить шаблоны, а затем выберите BrochurePath TemplateField из InsertItemTemplate раскрывающегося списка. Удалите textBox и перетащите элемент управления FileUpload с панели элементов в шаблон. Задайте для элемента управления FileUpload значение IDBrochureUpload. Аналогичным образом добавьте элемент управления FileUpload в Picture templateField .InsertItemTemplate Задайте для этого элемента управления FileUpload значение IDPictureUpload.

Добавление элемента управления FileUpload в InsertItemTemplate

Рис. 8. Добавление элемента управления FileUpload в InsertItemTemplate (щелкните для просмотра полноразмерного изображения)

После внесения этих дополнений декларативный синтаксис TemplateField будет следующим:

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

Когда пользователь добавляет новую категорию, мы хотим убедиться, что брошюра и изображение имеют правильный тип файла. Для брошюры пользователь должен предоставить PDF-файл. Для изображения требуется, чтобы пользователь отправлял файл изображения, но разрешаем ли мы какие-либо файлы изображений или только файлы изображений определенного типа, например GIF или JPG? Чтобы разрешить различные типы файлов, необходимо расширить схему Categories , включив столбец, который фиксирует тип файла, чтобы этот тип можно было отправить клиенту через Response.ContentType в DisplayCategoryPicture.aspx. Так как у нас нет такого столбца, было бы целесообразно ограничить пользователей только предоставлением определенного типа файла изображения. Существующие Categories изображения таблицы являются растровыми изображениями, но файлы JPG являются более подходящим форматом файлов для изображений, передаваемых через Интернет.

Если пользователь отправляет файл неправильного типа, необходимо отменить вставку и отобразить сообщение о проблеме. Добавьте элемент управления Label Web под DetailsView. Присвойте свойству значение , очистите его Text свойство, задайте CssClass для свойства значение Warning, а Visible свойства и EnableViewState — значение False.UploadWarningID Класс Warning CSS определяется в Styles.css и отображает текст крупным красным курсивом, полужирным шрифтом.

Примечание

В идеале CategoryName и Description BoundFields следует преобразовать в TemplateFields и настроить их интерфейсы вставки. Например Description , интерфейс вставки, скорее всего, лучше подходит для многострочного текстового поля. Так как CategoryName столбец не принимает NULL значения, необходимо добавить RequiredFieldValidator, чтобы пользователь предоставлял значение для имени новой категории. Эти действия остаются в качестве упражнения для читателя. Дополнительные сведения о дополнении интерфейсов изменения данных см. в статье Настройка интерфейса изменения данных.

Шаг 6. Сохранение отправленного буклета в файловой системе веб-сервера

Когда пользователь вводит значения для новой категории и нажимает кнопку Вставка, происходит обратная связь и разворачивается рабочий процесс вставки. Во-первых, срабатывает событие DetailsViewItemInserting. Затем вызывается метод ObjectDataSource, Insert() что приводит к добавлению новой записи в таблицу Categories . После этого срабатывает событие DetailsViewItemInserted.

Перед вызовом метода ObjectDataSource Insert() необходимо сначала убедиться, что пользователь загрузил соответствующие типы файлов, а затем сохранить PDF-файл брошюры в файловой системе веб-сервера. Создайте обработчик событий для события DetailsView ItemInserting и добавьте следующий код:

' 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

Обработчик событий начинается с ссылки на BrochureUpload элемент управления FileUpload из шаблонов DetailsView. Затем, если брошюра была загружена, проверяется расширение отправленного файла. Если расширение не .PDF, отображается предупреждение, вставка отменяется и выполнение обработчика событий завершается.

Примечание

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

Как описано в руководстве по отправке файлов , при сохранении файлов в файловой системе необходимо соблюдать осторожность, чтобы один пользователь не перезаписал другие файлы. В этом руководстве мы попытаемся использовать то же имя, что и отправленный файл. Однако если в ~/Brochures каталоге уже существует файл с таким же именем, мы добавим число в конце, пока не будет найдено уникальное имя. Например, если пользователь отправляет файл брошюры с именем Meats.pdf, но в ~/Brochures папке уже есть файл с именем Meats.pdf , мы изменим имя сохраненного файла на Meats-1.pdf. Если это существует, мы будем пытаться Meats-2.pdf, и т. д., пока не будет найдено уникальное имя файла.

В следующем кодеFile.Exists(path) метод используется для определения того, существует ли файл с указанным именем. Если это так, он продолжает пробовать новые имена файлов для брошюры, пока конфликт не будет найден.

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

После обнаружения допустимого имени файла файл необходимо сохранить в файловой системе и обновить значение ObjectDataSource brochurePath``InsertParameter , чтобы это имя файла было записано в базу данных. Как мы видели в руководстве По отправке файлов , файл можно сохранить с помощью метода элемента управления FileUpload s SaveAs(path) . Чтобы обновить параметр ObjectDataSource, brochurePath используйте коллекцию e.Values .

' Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath))
e.Values("brochurePath") = brochurePath

Шаг 7. Сохранение отправленного изображения в базе данных

Чтобы сохранить отправленное изображение в новой Categories записи, необходимо назначить отправленное двоичное содержимое параметру ObjectDataSource s picture в событии ItemInserting DetailsView. Однако перед выполнением этого назначения необходимо сначала убедиться, что загруженное изображение является JPG, а не каким-либо другим типом изображения. Как и на шаге 6, давайте воспользуемся расширением файла загруженного изображения, чтобы определить его тип.

Хотя таблица Categories допускает NULL значения для столбца Picture , все категории в настоящее время имеют рисунок. При добавлении новой категории на этой странице пользователь будет предоставлять изображение. Следующий код проверяет, что изображение отправлено и имеет соответствующее расширение.

' 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

Этот код следует поместить перед кодом из шага 6, чтобы при возникновении проблем с отправкой рисунка обработчик событий завершит работу перед сохранением файла брошюры в файловой системе.

Предположим, что был отправлен соответствующий файл, назначьте переданное двоичное содержимое значению параметра picture со следующей строкой кода:

' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes

Обработчик событий CompleteItemInserting

Для полноты ItemInserting ниже приведен обработчик событий в полном объеме:

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

Шаг 8. ИсправлениеDisplayCategoryPicture.aspxстраницы

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

Если отправлен недопустимый тип файла, отображается предупреждающее сообщение

Рис. 9. При отправке недопустимого типа файла отображается предупреждающее сообщение (щелкните для просмотра полноразмерного изображения)

Убедившись, что страница требует отправки изображения и не будет принимать файлы, отличные от PDF или JPG, добавьте новую категорию с допустимым изображением JPG, оставив поле Брошюра пустым. После нажатия кнопки Вставка страница будет отправлена обратно, и в таблицу Categories будет добавлена новая запись с двоичным содержимым отправленного изображения, хранящимся непосредственно в базе данных. GridView обновляется и отображает строку для только что добавленной категории, но, как показано на рисунке 10, изображение новой категории отображается неправильно.

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

Рис. 10. Изображение новой категории не отображается (щелкните для просмотра полноразмерного изображения)

Причина, по которой новый рисунок не отображается, заключается в DisplayCategoryPicture.aspx том, что страница, возвращающая рисунок указанной категории, настроена для обработки растровых изображений с заголовком OLE. Этот заголовок 78 байт удаляется из Picture двоичного содержимого столбца перед его отправкой обратно клиенту. Но JPG-файл, который мы только что отправили для новой категории, не имеет этого ole-заголовка; Таким образом, допустимые необходимые байты удаляются из двоичных данных образа.

Так как в Categories таблице теперь есть как растровые изображения с заголовками OLE, так и JPG, необходимо обновить, чтобы она обрезала DisplayCategoryPicture.aspx заголовки OLE для исходных восьми категорий и обходила эту обрезку для новых записей категорий. В следующем руководстве мы рассмотрим, как обновить существующий образ записи, и обновим все старые изображения категорий, чтобы они были JPG. Пока же используйте следующий код в DisplayCategoryPicture.aspx , чтобы удалить заголовки OLE только для этих исходных восьми категорий:

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

Благодаря этому изменению изображение JPG теперь правильно отображается в GridView.

Изображения JPG для новых категорий отображаются правильно

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

Шаг 9. Удаление брошюры перед лицом исключения

Одна из проблем хранения двоичных данных в файловой системе веб-сервера заключается в том, что это вызывает разрыв между моделью данных и ее двоичными данными. Поэтому при удалении записи необходимо также удалять соответствующие двоичные данные в файловой системе. Это также может вступить в игру при вставке. Рассмотрим следующий сценарий: пользователь добавляет новую категорию, указывая допустимое изображение и брошюру. При нажатии кнопки Вставка происходит обратная связь и возникает событие DetailsView ItemInserting , при этом брошюра сохраняется в файловой системе веб-сервера. Затем вызывается метод ObjectDataSource, Insert() который вызывает CategoriesBLL метод класса s InsertWithPicture , который вызывает CategoriesTableAdapter метод s InsertWithPicture .

Что произойдет, если база данных находится в автономном режиме или в инструкции INSERT SQL возникает ошибка? Очевидно, что инструкция INSERT завершится ошибкой, поэтому новая строка категории не будет добавлена в базу данных. Но у нас по-прежнему есть загруженный файл брошюры, расположенный в файловой системе веб-сервера! Этот файл необходимо удалить перед лицом исключения во время рабочего процесса вставки.

Как обсуждалось ранее в руководстве По обработке исключений BLL- и DAL-Level в ASP.NET Page , при возникновении исключения из глубин архитектуры оно перемещается по различным уровням. На уровне представления можно определить, произошло ли исключение из события DetailsView ItemInserted . Этот обработчик событий также предоставляет значения объектов ObjectDataSource InsertParameters. Таким образом, можно создать обработчик событий для ItemInserted события, который проверяет наличие исключения и, если да, удаляет файл, указанный параметром 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

Сводка

Существует ряд шагов, которые необходимо выполнить, чтобы предоставить веб-интерфейс для добавления записей, включающих двоичные данные. Если двоичные данные хранятся непосредственно в базе данных, скорее всего, вам потребуется обновить архитектуру, добавив определенные методы для обработки ситуации, когда вставляются двоичные данные. После обновления архитектуры следующим шагом является создание интерфейса вставки, который можно выполнить с помощью DetailsView, который был настроен для включения элемента управления FileUpload для каждого поля двоичных данных. Отправленные данные затем можно сохранить в файловой системе веб-сервера или назначить параметру источника данных в обработчике ItemInserting событий DetailsView.

Сохранение двоичных данных в файловой системе требует больше планирования, чем сохранение данных непосредственно в базе данных. Необходимо выбрать схему именования, чтобы избежать перезаписи одного пользователя. Кроме того, необходимо выполнить дополнительные действия для удаления отправленного файла, если не удается выполнить вставку базы данных.

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

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

Об авторе

Скотт Митчелл (Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с Веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часа. Его можно связать по адресу mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.

Отдельная благодарность

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