Обновление и удаление существующих двоичных данных (VB)

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

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

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

Введение

В последних трех руководствах мы добавили довольно много возможностей для работы с двоичными данными. Мы начали с добавления столбца BrochurePath в таблицу Categories и соответствующим образом обновили архитектуру. Мы также добавили методы уровня доступа к данным и уровня бизнес-логики для работы с существующим Picture столбцом таблицы Категорий, который содержит двоичное содержимое файла изображения. Мы создали веб-страницы для представления двоичных данных в GridПросмотреть ссылку для скачивания брошюры с изображением категории, показанным <img> в элементе, и добавили DetailsView, чтобы пользователи могли добавить новую категорию и загрузить ее данные брошюры и рисунка.

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

Шаг 1. Обновление уровня доступа к данным

DAL имеет автоматически созданные Insertметоды , Updateи Delete , но эти методы были созданы на CategoriesTableAdapter основе запроса main , который не включает Picture столбец. Insert Поэтому методы и Update не включают параметры для указания двоичных данных для изображения категории. Как и в предыдущем руководстве, необходимо создать новый метод TableAdapter для обновления Categories таблицы при указании двоичных данных.

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

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

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

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

UPDATE [Categories] SET 
    [CategoryName] = @CategoryName, 
    [Description] = @Description, 
    [BrochurePath] = @BrochurePath ,
    [Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))

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

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

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

Шаг 2. Добавление методов уровня бизнес-логики

Помимо обновления DAL, необходимо обновить BLL, чтобы включить методы обновления и удаления категории. Это методы, которые будут вызываться из уровня представления.

Для удаления категории можно использовать CategoriesTableAdapter автоматически созданный Delete метод s. Добавьте в класс CategoriesBLL следующий метод:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Delete, True)> _
Public Function DeleteCategory(ByVal categoryID As Integer) As Boolean
    Dim rowsAffected As Integer = Adapter.Delete(categoryID)
    ' Return true if precisely one row was deleted, otherwise false
    Return rowsAffected = 1
End Function

В этом руководстве мы создадим два метода обновления категории: один из них ожидает данные двоичного рисунка и вызывает UpdateWithPicture метод, который мы только что добавили в CategoriesTableAdapter , а другой принимает только CategoryNameзначения , Descriptionи BrochurePath и использует CategoriesTableAdapter автоматически сгенерированную Update инструкцию класса. Обоснование использования двух методов заключается в том, что в некоторых случаях пользователю может потребоваться обновить изображение категории вместе с другими полями. В этом случае пользователю придется отправить новое изображение. Затем двоичные данные загруженного изображения можно использовать в инструкции UPDATE . В других случаях пользователь может быть заинтересован только в обновлении, например, имени и описания. Но если UPDATE инструкция также ожидает двоичные данные для столбца Picture , необходимо также предоставить эти сведения. Для этого потребуется дополнительная поездка в базу данных для возврата данных изображения для редактируемой записи. Поэтому нам нужны два UPDATE метода. Уровень бизнес-логики определяет, какой из них следует использовать, в зависимости от того, предоставляются ли данные изображения при обновлении категории.

Чтобы упростить эту задачу, добавьте в класс два метода CategoriesBLL с именем UpdateCategory. Первый должен принимать три String s, Byte массив и в Integer качестве входных параметров; второй, только три String s и Integer. Входные String параметры относятся к имени категории, описанию и пути к файлу буклета, Byte массив — для двоичного содержимого рисунка категории и Integer определяет CategoryID обновляемую запись. Обратите внимание, что первая перегрузка вызывает вторую, если переданный Byte массив имеет значение Nothing:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, False)> _
Public Function UpdateCategory(categoryName As String, description As String, _
    brochurePath As String, picture() As Byte, categoryID As Integer) As Boolean
    
    ' If no picture is specified, use other overload
    If picture Is Nothing Then
        Return UpdateCategory(categoryName, description, brochurePath, categoryID)
    End If
    ' Update picture, as well
    Dim rowsAffected As Integer = Adapter.UpdateWithPicture _
        (categoryName, description, brochurePath, picture, categoryID)
    ' Return true if precisely one row was updated, otherwise false
    Return rowsAffected = 1
End Function
<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateCategory(categoryName As String, description As String, _
    brochurePath As String, categoryID As Integer) As Boolean
    Dim rowsAffected As Integer = Adapter.Update _
        (categoryName, description, brochurePath, categoryID)
    ' Return true if precisely one row was updated, otherwise false
    Return rowsAffected = 1
End Function

Шаг 3. Копирование функций вставки и просмотра

В предыдущем руководстве мы создали страницу UploadInDetailsView.aspx со списком всех категорий в GridView и предоставили DetailsView для добавления новых категорий в систему. В этом руководстве мы расширим GridView, включив в него поддержку редактирования и удаления. Вместо того, чтобы продолжать работать с UploadInDetailsView.aspx, позвольте вместо этого поместить изменения этого руководства на страницу UpdatingAndDeleting.aspx из той же папки , ~/BinaryData. Скопируйте и вставьте декларативную разметку и код из UploadInDetailsView.aspx в UpdatingAndDeleting.aspx.

Начните с открытия страницы UploadInDetailsView.aspx . Скопируйте весь декларативный синтаксис в элементе <asp:Content> , как показано на рис. 3. Затем откройте UpdatingAndDeleting.aspx и вставьте эту разметку в свой <asp:Content> элемент . Аналогичным образом скопируйте код из UploadInDetailsView.aspx класса кода программной части страницы в UpdatingAndDeleting.aspx.

Скопируйте декларативную разметку из UploadInDetailsView.aspx

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

После копирования декларативной разметки и кода посетите страницу UpdatingAndDeleting.aspx. Вы должны увидеть те же выходные данные и пользовательский интерфейс, что UploadInDetailsView.aspx и на странице из предыдущего руководства.

Шаг 4. Добавление поддержки удаления в ObjectDataSource и GridView

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

Чтобы устранить эту проблему, щелкните параметр Настройка источника данных в смарт-теге ObjectDataSource, чтобы запустить мастер. На первом экране показано, что ObjectDataSource настроен для работы с классом CategoriesBLL . Нажмите Далее. В настоящее время указываются только свойства ObjectDataSource InsertMethod и SelectMethod . Однако мастер автоматически заполнил раскрывающийся список на вкладках UPDATE и DELETE методами UpdateCategory и DeleteCategory соответственно. Это связано с тем, что в классе мы помечаем CategoriesBLLDataObjectMethodAttribute эти методы, используя в качестве методов по умолчанию для обновления и удаления.

Пока задайте для раскрывающегося списка update tab s значение (Нет), но оставьте для DeleteCategoryраскрывающегося списка delete значение . Мы вернемся к этому мастеру на шаге 6, чтобы добавить поддержку обновления.

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

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

Примечание

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

Объект ObjectDataSource теперь будет содержать значение для своего DeleteMethod свойства, а также DeleteParameterзначение . Помните, что при использовании мастера для указания методов Visual Studio задает свойству original_{0}ObjectDataSource OldValuesParameterFormatString значение , что вызывает проблемы с вызовами метода обновления и удаления. Поэтому либо полностью очистите это свойство, либо сбросьте его до значения по умолчанию , {0}. Если вам нужно обновить память для этого свойства ObjectDataSource, см. статью Общие сведения о вставке, обновлении и удалении данных .

После завершения работы мастера и исправления OldValuesParameterFormatStringдекларативная разметка ObjectDataSource должна выглядеть примерно так:

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture" 
    DeleteMethod="DeleteCategory">
    <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>
    <DeleteParameters>
        <asp:Parameter Name="categoryID" Type="Int32" />
    </DeleteParameters>
</asp:ObjectDataSource>

После настройки ObjectDataSource добавьте возможности удаления в GridView, установив флажок Включить удаление из смарт-тега GridView. Это добавит CommandField в GridView, свойство которого ShowDeleteButton имеет значение True.

Включение поддержки удаления в GridView

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

Проверьте функциональность удаления. Между таблицами и Categories таблицами CategoryIDCategoryID существует внешний ключProducts, поэтому при попытке удалить одну из первых восьми категорий возникнет исключение нарушения ограничения внешнего ключа. Чтобы проверить эту функциональность, добавьте новую категорию, предоставив брошюру и рисунок. Моя тестовая категория, показанная на рис. 6, включает файл тестового буклета с именем Test.pdf и изображение теста. На рисунке 7 показан элемент GridView после добавления категории теста.

Добавление тестовой категории с брошюрой и изображением

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

После вставки категории теста она отображается в GridView.

Рис. 7. После вставки категории теста она отображается в GridView (Щелкните для просмотра полноразмерного изображения)

В Visual Studio обновите Обозреватель решений. Теперь в папке ~/BrochuresTest.pdf должен появиться новый файл (см. рис. 8).

Затем щелкните ссылку Удалить в строке Категория теста, что приведет к обратной отправке страницы и CategoriesBLL вызову метода класса DeleteCategory . Это приведет к вызову метода DAL s Delete , что приведет к отправке соответствующей DELETE инструкции в базу данных. Затем данные отскокируются в GridView, а разметка отправляется обратно клиенту, при этом категория "Тест" больше не отображается.

Хотя рабочий процесс удаления успешно удалил запись категории теста из Categories таблицы, файл брошюры не был удален из файловой системы веб-сервера. Обновите Обозреватель решений, и вы увидите, что Test.pdf он по-прежнему находится в папке~/Brochures.

Файл Test.pdf не был удален из файловой системы веб-сервера

Рис. 8. Файл Test.pdf не был удален из файловой системы веб-сервера

Шаг 5. Удаление файла брошюры удаленных категорий

Одним из недостатков хранения двоичных данных за пределами базы данных является то, что при удалении связанной записи базы данных необходимо предпринять дополнительные действия для очистки этих файлов. GridView и ObjectDataSource предоставляют события, которые запускают как до, так и после выполнения команды удаления. На самом деле необходимо создать обработчики событий как для событий до, так и для событий после действия. Перед удалением Categories записи необходимо определить путь к PDF-файлу, но мы не хотим удалять PDF-файл перед удалением категории в случае, если есть исключение, а категория не удаляется.

Событие GridView RowDeleting срабатывает до вызова команды удаления ObjectDataSource, а его RowDeleted событие срабатывает после. Создайте обработчики событий для этих двух событий, используя следующий код:

' A page variable to "remember" the deleted category's BrochurePath value
Private deletedCategorysPdfPath As String = Nothing
Protected Sub Categories_RowDeleting(sender As Object, e As GridViewDeleteEventArgs) _
    Handles Categories.RowDeleting
    
    ' Determine the PDF path for the category being deleted...
    Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID"))
    Dim categoryAPI As New CategoriesBLL()
    Dim categoriesData As Northwind.CategoriesDataTable = _
        categoryAPI.GetCategoryByCategoryID(categoryID)
    Dim category As Northwind.CategoriesRow = categoriesData(0)
    If category.IsBrochurePathNull() Then
        deletedCategorysPdfPath = Nothing
    Else
        deletedCategorysPdfPath = category.BrochurePath
    End If
End Sub
Protected Sub Categories_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
    Handles Categories.RowDeleted
    
    ' Delete the brochure file if there were no problems deleting the record
    If e.Exception Is Nothing Then
        DeleteRememberedBrochurePath()
    End If
End Sub

В обработчике RowDeletingCategoryID событий удаляемая строка извлекается из коллекции GridView DataKeys , доступ к которой в этом обработчике событий можно получить через коллекцию e.Keys . Затем вызывается класс s GetCategoryByCategoryID(categoryID) для CategoriesBLL возврата сведений об удаляемой записи. Если возвращаемый CategoriesDataRow объект имеет значение, отличноеNULL``BrochurePath от , он сохраняется в переменной deletedCategorysPdfPath страницы, чтобы файл можно было удалить в обработчике RowDeleted событий.

Примечание

Вместо того чтобы получать BrochurePath сведения об удаляемой Categories записи в RowDeleting обработчике событий, можно было бы добавить BrochurePath в свойство GridView DataKeyNames и получить доступ к значению записи через коллекцию e.Keys . Это немного увеличит размер состояния представления GridView, но сократит объем необходимого кода и сэкономит поездку в базу данных.

После вызова базовой команды удаления ObjectDataSource срабатывает обработчик событий GridView RowDeleted . Если при удалении данных не было исключений и имеется значение deletedCategorysPdfPath, pdf-файл удаляется из файловой системы. Обратите внимание, что этот дополнительный код не требуется для очистки двоичных данных категории, связанных с ее изображением. Это связано с тем, что данные рисунка хранятся непосредственно в базе данных, поэтому при удалении Categories строки также удаляются данные изображения этой категории.

После добавления двух обработчиков событий снова запустите этот тестовый случай. При удалении категории также удаляется связанный с ней PDF-файл.

Обновление существующих двоичных данных, связанных с записями, создает некоторые интересные проблемы. В оставшейся части этого руководства рассматривается добавление возможностей обновления в брошюру и рисунок. На шаге 6 рассматриваются методы обновления сведений о брошюре, а на шаге 7 рассматривается обновление рисунка.

Шаг 6. Обновление брошюры категории

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

Щелкните ссылку Настройка источника данных в мастере ObjectDataSource и перейдите ко второму шагу. Из-за используемого DataObjectMethodAttribute в CategoriesBLLраскрывающийся список UPDATE должен автоматически заполняться UpdateCategory перегрузкой, которая принимает четыре входных параметра (для всех столбцов, кроме Picture). Измените это значение таким образом, чтобы он использовал перегрузку с пятью параметрами.

Настройка ObjectDataSource для использования метода UpdateCategory, включающего параметр для рисунка

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

ObjectDataSource теперь будет включать значение для своего UpdateMethod свойства, а также соответствующие UpdateParameter значения . Как указано в шаге 4, Visual Studio задает свойству ObjectDataSource OldValuesParameterFormatString значение original_{0} при использовании мастера настройки источника данных. Это приведет к проблемам с вызовами метода обновления и удаления. Поэтому либо полностью очистите это свойство, либо сбросьте его до значения по умолчанию , {0}.

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

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture" 
    DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory">
    <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>
    <DeleteParameters>
        <asp:Parameter Name="categoryID" Type="Int32" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="categoryName" Type="String" />
        <asp:Parameter Name="description" Type="String" />
        <asp:Parameter Name="brochurePath" Type="String" />
        <asp:Parameter Name="picture" Type="Object" />
        <asp:Parameter Name="categoryID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

Чтобы включить встроенные функции редактирования GridView, проверка параметр Включить редактирование из смарт-тега GridView. При этом свойству CommandField будет присвоено ShowEditButton значение True, что приведет к добавлению кнопки Изменить (и кнопок Обновить и Отмена для редактируемой строки).

Настройка GridView для поддержки редактирования

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

Перейдите на страницу в браузере и нажмите одну из кнопок "Изменить". Поля CategoryName и Description BoundFields отображаются в виде текстовых полей. В BrochurePath TemplateField отсутствует EditItemTemplate, поэтому он продолжает отображать ссылку ItemTemplate на брошюру. ImageField Picture отрисовывается в виде элемента TextBox, свойству которого Text присваивается значение imageField DataImageUrlField , в данном случае CategoryID.

GridView не имеет интерфейса редактирования для BrochurePath

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

НастройкаBrochurePathинтерфейса редактирования

Нам нужно создать интерфейс редактирования для BrochurePath TemplateField, который позволяет пользователю выполнять следующие действия:

  • Оставьте брошюру категории "как есть",
  • Обновите брошюру категории, загрузив новый буклет, или
  • Полностью удалите брошюру категории (в случае, если в категории больше нет связанного буклета).

Нам также нужно обновить Picture интерфейс редактирования ImageField, но мы перейдем к этому на шаге 7.

В смарт-теге GridView щелкните ссылку Изменить шаблоны и выберите BrochurePath TemplateField в раскрывающемся списке EditItemTemplate . Добавьте веб-элемент управления RadioButtonList в этот шаблон, задав для его ID свойства значение BrochureOptions , а для свойства AutoPostBack — значение True. В окно свойств щелкните многоточие в свойстве Items , чтобы открыть ListItem Редактор Коллекция. Добавьте следующие три параметра с Value s 1, 2 и 3 соответственно:

  • Использование текущего буклета
  • Удаление текущего буклета
  • Загрузка нового буклета

Присвойте первому ListItem свойству s Selected значение True.

Добавление трех элементов ListItems в RadioButtonList

Рис. 12. Добавление трех ListItem в RadioButtonList

Под Элементом RadioButtonList добавьте элемент управления FileUpload с именем BrochureUpload. Для его свойства Visible задайте значение False.

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

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

Этот элемент RadioButtonList предоставляет три варианта для пользователя. Идея заключается в том, что элемент управления FileUpload будет отображаться только в том случае, если выбран последний параметр Отправить новый буклет. Для этого создайте обработчик событий для события RadioButtonList SelectedIndexChanged и добавьте следующий код:

Protected Sub BrochureOptions_SelectedIndexChanged _
    (sender As Object, e As EventArgs)
    
    ' Get a reference to the RadioButtonList and its Parent
    Dim BrochureOptions As RadioButtonList = _
        CType(sender, RadioButtonList)
    Dim parent As Control = BrochureOptions.Parent
    ' Now use FindControl("controlID") to get a reference of the 
    ' FileUpload control
    Dim BrochureUpload As FileUpload = _
        CType(parent.FindControl("BrochureUpload"), FileUpload)
    ' Only show BrochureUpload if SelectedValue = "3"
    BrochureUpload.Visible = (BrochureOptions.SelectedValue = "3")
End Sub

Так как элементы управления RadioButtonList и FileUpload находятся в шаблоне, нам нужно написать немного кода для программного доступа к этим элементам управления. Обработчику SelectedIndexChanged событий передается ссылка на RadioButtonList во входном параметре sender . Чтобы получить элемент управления FileUpload, необходимо получить родительский элемент управления RadioButtonList и использовать FindControl("controlID") метод . После получения ссылки на элементы управления RadioButtonList и FileUpload свойству элемента управления Visible FileUpload присваивается значение True только в том случае, если значение RadioButtonList SelectedValue равно 3, которое является свойством Value для отправки нового буклета ListItem.

Создав этот код, протестируйте интерфейс редактирования. Нажмите кнопку Изменить для строки. Изначально следует выбрать параметр Использовать текущий буклет. Изменение выбранного индекса приводит к обратной отправке. Если выбран третий параметр, отображается элемент управления FileUpload, в противном случае он скрыт. На рисунке 14 показан интерфейс редактирования при первом нажатии кнопки Изменить. На рисунке 15 показан интерфейс после выбора параметра Отправить новый буклет.

Изначально выбран параметр

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

Выбор параметра Отправить новый буклет отображает элемент управления FileUpload

Рис. 15. Выбор параметра Отправить новый буклет отображает элемент управления FileUpload (Щелкните для просмотра полноразмерного изображения)

Сохранение файла буклета и обновление столбцаBrochurePath

При нажатии кнопки Update gridView срабатывает событие RowUpdating . Вызывается команда objectDataSource update, после чего срабатывает событие GridView RowUpdated . Как и в случае с рабочим процессом удаления, необходимо создать обработчики событий для обоих этих событий. В обработчике RowUpdating событий необходимо определить, какое действие следует предпринять на SelectedValueBrochureOptions основе элемента RadioButtonList:

  • SelectedValue Если значение равно 1, мы хотим использовать тот же BrochurePath параметр. Поэтому необходимо задать для параметра ObjectDataSource brochurePath существующее BrochurePath значение обновляемой записи. Параметр ObjectDataSource brochurePath можно задать с помощью e.NewValues["brochurePath"] = value.
  • SelectedValue Если имеет значение 2, необходимо задать для записи BrochurePath значение NULL. Это можно сделать, задав параметру ObjectDataSource brochurePath значение Nothing, что приводит к использованию базы данных NULL в инструкции UPDATE . Если существует файл брошюры, который удаляется, необходимо удалить существующий файл. Однако мы хотим сделать это только в том случае, если обновление завершается без возникновения исключения.
  • SelectedValue Если имеет значение 3, мы хотим убедиться, что пользователь загрузил PDF-файл, а затем сохранить его в файловой системе и обновить значение столбца BrochurePath записи. Кроме того, если существует файл брошюры, который заменяется, необходимо удалить предыдущий файл. Однако мы хотим сделать это только в том случае, если обновление завершается без возникновения исключения.

Действия, необходимые для выполнения, когда значение RadioButtonList SelectedValue равно 3, практически идентичны действиям, используемым обработчиком событий DetailsView ItemInserting . Этот обработчик событий выполняется при добавлении новой записи категории из элемента управления DetailsView, добавленного в предыдущем руководстве. Поэтому мы должны выполнить рефакторинг этой функции в отдельные методы. В частности, я переместил общую функциональность на два метода:

  • ProcessBrochureUpload(FileUpload, out bool) принимает в качестве входных данных экземпляр элемента управления FileUpload и выходное логическое значение, указывающее, должна ли операция удаления или изменения продолжаться или ее следует отменить из-за ошибки проверки. Этот метод возвращает путь к сохраненного файла или null значение , если файл не был сохранен.
  • DeleteRememberedBrochurePath Удаляет файл, указанный путем в переменной deletedCategorysPdfPath страницы, если deletedCategorysPdfPath не nullимеет значение .

Ниже приведен код для этих двух методов. Обратите внимание на сходство обработчика ProcessBrochureUploadItemInserting событий DetailsView и из предыдущего руководства. В этом руководстве я обновил обработчики событий DetailsView, чтобы использовать эти новые методы. Скачайте код, связанный с этим руководством, чтобы просмотреть изменения в обработчиках событий DetailsView.

Private Function ProcessBrochureUpload _
    (BrochureUpload As FileUpload, CancelOperation As Boolean) As String
    
    CancelOperation = False    ' by default, do not cancel operation
    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
            CancelOperation = True
            Return Nothing
        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))
        Return brochurePath
    Else
        ' No file uploaded
        Return Nothing
    End If
End Function
Private Sub DeleteRememberedBrochurePath()
    ' Is there a file to delete?
    If deletedCategorysPdfPath IsNot Nothing Then
        System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath))
    End If
End Sub

Обработчики RowUpdating событий и RowUpdated GridView используют ProcessBrochureUpload методы и DeleteRememberedBrochurePath , как показано в следующем коде:

Protected Sub Categories_RowUpdating _
    (sender As Object, e As GridViewUpdateEventArgs) _
    Handles Categories.RowUpdating
    
    ' Reference the RadioButtonList
    Dim BrochureOptions As RadioButtonList = _
        CType(Categories.Rows(e.RowIndex).FindControl("BrochureOptions"), _
            RadioButtonList)
    ' Get BrochurePath information about the record being updated
    Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID"))
    Dim categoryAPI As New CategoriesBLL()
    Dim categoriesData As Northwind.CategoriesDataTable = _
        categoryAPI.GetCategoryByCategoryID(categoryID)
    Dim category As Northwind.CategoriesRow = categoriesData(0)
    If BrochureOptions.SelectedValue = "1" Then
        ' Use current value for BrochurePath
        If category.IsBrochurePathNull() Then
            e.NewValues("brochurePath") = Nothing
        Else
            e.NewValues("brochurePath") = category.BrochurePath
        End If
    ElseIf BrochureOptions.SelectedValue = "2" Then
        ' Remove the current brochure (set it to NULL in the database)
        e.NewValues("brochurePath") = Nothing
    ElseIf BrochureOptions.SelectedValue = "3" Then
        ' Reference the BrochurePath FileUpload control
        Dim BrochureUpload As FileUpload = _
            CType(categories.Rows(e.RowIndex).FindControl("BrochureUpload"), _
                FileUpload)
        ' Process the BrochureUpload
        Dim cancelOperation As Boolean = False
        e.NewValues("brochurePath") = _
            ProcessBrochureUpload(BrochureUpload, cancelOperation)
        e.Cancel = cancelOperation
    Else
        ' Unknown value!
        Throw New ApplicationException( _
            String.Format("Invalid BrochureOptions value, {0}", _
                BrochureOptions.SelectedValue))
    End If
    If BrochureOptions.SelectedValue = "2" OrElse _
        BrochureOptions.SelectedValue = "3" Then
        
        ' "Remember" that we need to delete the old PDF file
        If (category.IsBrochurePathNull()) Then
            deletedCategorysPdfPath = Nothing
        Else
            deletedCategorysPdfPath = category.BrochurePath
        End If
    End If
End Sub
Protected Sub Categories_RowUpdated _
    (sender As Object, e As GridViewUpdatedEventArgs) _
    Handles Categories.RowUpdated
    
    ' If there were no problems and we updated the PDF file, 
    ' then delete the existing one
    If e.Exception Is Nothing Then
        DeleteRememberedBrochurePath()
    End If
End Sub

Обратите внимание, RowUpdating что обработчик событий использует ряд условных инструкций для выполнения соответствующего действия на BrochureOptions основе значения свойства RadioButtonList SelectedValue .

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

Шаг 7. Отправка нового рисунка

Интерфейс Picture редактирования ImageField отображается в виде текстового поля, заполненного значением из его DataImageUrlField свойства . Во время рабочего процесса редактирования GridView передает параметр ObjectDataSource с именем параметра, значением свойства ImageField s DataImageUrlField и значением параметра, введенным в текстовое поле в интерфейсе редактирования. Такое поведение подходит, если образ сохраняется в файловой системе, а DataImageUrlField содержит полный URL-адрес изображения. В таких случаях интерфейс редактирования отображает URL-адрес изображения в текстовом поле, который пользователь может изменить и сохранить обратно в базу данных. Конечно, этот интерфейс по умолчанию не позволяет пользователю отправлять новое изображение, но позволяет ему изменять URL-адрес изображения с текущего значения на другое. Однако в этом руководстве недостаточно интерфейса редактирования ImageField по умолчанию, так как двоичные Picture данные хранятся непосредственно в базе данных, а DataImageUrlField свойство содержит только CategoryID.

Чтобы лучше понять, что происходит в нашем руководстве, когда пользователь редактирует строку с помощью ImageField, рассмотрим следующий пример: пользователь редактирует строку с CategoryID 10, в результате чего Picture ImageField будет отображаться в виде текстового поля со значением 10. Представьте, что пользователь изменяет значение в этом текстовом поле на 50 и нажимает кнопку Обновить. Происходит обратная передача, и GridView изначально создает параметр CategoryID со значением 50. Однако перед отправкой этого параметра (и CategoryName параметров и Description ) GridView добавляет значения из DataKeys коллекции. Поэтому параметр перезаписывается CategoryID базовым CategoryID значением текущей строки 10. Короче говоря, интерфейс редактирования ImageField не влияет на рабочий процесс редактирования в этом руководстве, так как имена свойства ImageField и DataImageUrlField значения сетки DataKey совпадают.

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

Чтобы настроить интерфейс редактирования ImageField, необходимо преобразовать его в TemplateField. В смарт-теге GridView щелкните ссылку Изменить столбцы, выберите ImageField и щелкните ссылку Преобразовать это поле в TemplateField.

Преобразование ImageField в templateField

Рис. 16. Преобразование ImageField в templateField

Преобразование ImageField в TemplateField таким образом создает TemplateField с двумя шаблонами. Как показано в следующем декларативном синтаксисе ItemTemplate , содержит элемент управления Image Web, свойство которого ImageUrl назначается с помощью синтаксиса привязки данных на основе свойств ImageField DataImageUrlField и DataImageUrlFormatString . Содержит EditItemTemplate объект TextBox, свойство которого Text привязано к значению, заданному свойством DataImageUrlField .

<asp:TemplateField>
    <EditItemTemplate>
        <asp:TextBox ID="TextBox1" runat="server" 
            Text='<%# Eval("CategoryID") %>'></asp:TextBox>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Image ID="Image1" runat="server" 
            ImageUrl='<%# Eval("CategoryID", 
                "DisplayCategoryPicture.aspx?CategoryID={0}") %>' />
    </ItemTemplate>
</asp:TemplateField>

Необходимо обновить , EditItemTemplate чтобы использовать элемент управления FileUpload. В смарт-теге GridView щелкните ссылку Изменить шаблоны и выберите Picture TemplateField в раскрывающемся списке EditItemTemplate . В шаблоне должно появиться свойство TextBox удалить это. Затем перетащите элемент управления FileUpload из панели элементов в шаблон, задав для этого ID элемента значение PictureUpload. Кроме того, добавьте текст Чтобы изменить рисунок категории, укажите новый рисунок. Чтобы оставить изображение категории прежним, оставьте поле пустым для шаблона.

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

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

После настройки интерфейса редактирования просмотрите ход выполнения в браузере. При просмотре строки в режиме только для чтения изображение категории отображается как раньше, но при нажатии кнопки Изменить столбец рисунка отображается в виде текста с элементом управления FileUpload.

Интерфейс редактирования включает элемент управления FileUpload

Рис. 18. Интерфейс редактирования включает элемент управления FileUpload (Щелкните для просмотра полноразмерного изображения)

Помните, что ObjectDataSource настроен для вызова CategoriesBLL метода класса s UpdateCategory , который принимает в качестве входных данных двоичные данные для рисунка в виде массива Byte . Однако если этот массив имеет значение Nothing, вызывается альтернативная UpdateCategory перегрузка, которая выдает инструкцию UPDATE SQL, которая не изменяет Picture столбец, тем самым оставляя текущую картину категории без изменений. Поэтому в обработчике событий GridView RowUpdating необходимо программно ссылаться на PictureUpload элемент управления FileUpload и определить, был ли отправлен файл. Если один из них не был отправлен, мы не хотим указывать значение параметра picture . С другой стороны, если файл был отправлен в PictureUpload элементе управления FileUpload, мы хотим убедиться, что он является JPG-файлом. Если это так, то мы можем отправить его двоичное содержимое в ObjectDataSource с помощью picture параметра .

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

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

' Reference the PictureUpload FileUpload
Dim PictureUpload As FileUpload = _
    CType(categories.Rows(e.RowIndex).FindControl("PictureUpload"), _
        FileUpload)
If PictureUpload.HasFile Then
    ' Make sure the picture upload is valid
    If ValidPictureUpload(PictureUpload) Then
        e.NewValues("picture") = PictureUpload.FileBytes
    Else
        ' Invalid file upload, cancel update and exit event handler
        e.Cancel = True
        Exit Sub
    End If
End If

Метод ValidPictureUpload(FileUpload) принимает элемент управления FileUpload в качестве единственного входного параметра и проверяет расширение отправленного файла, чтобы убедиться, что отправленный файл является JPG; он вызывается только при отправке файла рисунка. Если файл не отправлен, параметр picture не задан и поэтому использует значение Nothingпо умолчанию . Если изображение было отправлено и ValidPictureUpload возвращается True, picture параметр назначается двоичным данным отправленного изображения; если метод возвращает False, рабочий процесс обновления отменяется и обработчик событий завершается.

Код ValidPictureUpload(FileUpload) метода, который был рефакторингован из обработчика ItemInserting событий DetailsView, выглядит следующим образом:

Private Function ValidPictureUpload(ByVal PictureUpload As FileUpload) As Boolean
    ' 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
        Return False
    Else
        Return True
    End If
End Function

Шаг 8. Замена исходных изображений категорий на JPG

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

  1. Сохраните растровые изображения на жестком диске. Перейдите на страницу UpdatingAndDeleting.aspx браузера и для каждой из первых восьми категорий щелкните изображение правой кнопкой мыши и выберите сохранить изображение.
  2. Откройте изображение в выбранном редакторе изображений. Например, можно использовать Microsoft Paint.
  3. Сохраните растровое изображение в формате JPG.
  4. Обновите изображение категории с помощью интерфейса редактирования с помощью JPG-файла.

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

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)
    ' 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 Sub

Примечание

Интерфейсы UpdatingAndDeleting.aspx вставки и редактирования страниц могут использовать немного больше работы. Поля CategoryName BoundField и Description в DetailsView и GridView должны быть преобразованы в TemplateFields. Так как CategoryName не допускает NULL значения, необходимо добавить RequiredFieldValidator. Кроме того, Description элемент TextBox, вероятно, следует преобразовать в многострочный элемент TextBox. Я оставляю эти последние штрихи как упражнение для вас.

Сводка

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

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

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

Об авторе

Скотт Митчелл (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.