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


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

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

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

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

Введение

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

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

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

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

Откройте typed DataSet и в 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 bool DeleteCategory(int categoryID)
{
    int rowsAffected = Adapter.Delete(categoryID);
    // Return true if precisely one row was deleted, otherwise false
    return rowsAffected == 1;
}

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

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

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateCategory(string categoryName, string description, 
    string brochurePath, byte[] picture, int categoryID)
{
    // If no picture is specified, use other overload
    if (picture == null)
        return UpdateCategory(categoryName, description, brochurePath, categoryID);
    // Update picture, as well
    int rowsAffected = Adapter.UpdateWithPicture
        (categoryName, description, brochurePath, picture, categoryID);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateCategory(string categoryName, string description, 
    string brochurePath, int categoryID)
{
    int rowsAffected = Adapter.Update
        (categoryName, description, brochurePath, categoryID);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

Шаг 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 соответственно. Это связано с тем, CategoriesBLL что в классе мы помечаем DataObjectMethodAttribute эти методы с использованием в качестве методов по умолчанию для обновления и удаления.

Пока задайте для раскрывающегося списка UPDATE tab s значение (Нет), а для раскрывающегося списка DELETE tab s оставьте значение DeleteCategory. Мы вернемся к этому мастеру на шаге 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 (щелкните для просмотра полноразмерного изображения)

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

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

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

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

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

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

Затем щелкните ссылку Удалить в строке Категория теста, что приведет к обратной отправке страницы и CategoriesBLL вызову метода класса DeleteCategory . Это вызовет метод DAL, 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 
string deletedCategorysPdfPath = null;
protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    // Determine the PDF path for the category being deleted...
    int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    if (category.IsBrochurePathNull())
        deletedCategorysPdfPath = null;
    else
        deletedCategorysPdfPath = category.BrochurePath;
}
protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    // Delete the brochure file if there were no problems deleting the record
    if (e.Exception == null)
    {
        // Is there a file to delete?
        if (deletedCategorysPdfPath != null)
        {
            System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
        }
    }
}

В обработчике 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 значения s. Как указано в шаге 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 s будет присвоено ShowEditButton значение , что приведет к trueдобавлению кнопки "Изменить" (и "Обновить" и "Отмена" для редактируемой строки).

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

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

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

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

Рис. 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.

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

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

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

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

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

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

protected void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e)
{
    // Get a reference to the RadioButtonList and its Parent
    RadioButtonList BrochureOptions = (RadioButtonList)sender;
    Control parent = BrochureOptions.Parent;
    // Now use FindControl("controlID") to get a reference of the 
    // FileUpload control
    FileUpload BrochureUpload = 
        (FileUpload)parent.FindControl("BrochureUpload");
    // Only show BrochureUpload if SelectedValue = "3"
    BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3");
}

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

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

Изначально выбран параметр Использовать текущую брошюру

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

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

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

Сохранение файла брошюры и обновление столбцаBrochurePath

При нажатии кнопки Update GridView срабатывает событие RowUpdating . Вызывается команда обновления ObjectDataSource, а затем запускается событие GridView RowUpdated . Как и в случае с рабочим процессом удаления, необходимо создать обработчики событий для обоих этих событий. В обработчике RowUpdating событий необходимо определить, какое действие следует предпринять на SelectedValue основе элемента BrochureOptions 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 string ProcessBrochureUpload
    (FileUpload BrochureUpload, out bool CancelOperation)
{
    CancelOperation = false;    // by default, do not cancel operation
    if (BrochureUpload.HasFile)
    {
        // Make sure that a PDF has been uploaded
        if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), 
            ".pdf", true) != 0)
        {
            UploadWarning.Text = 
                "Only PDF documents may be used for a category's brochure.";
            UploadWarning.Visible = true;
            CancelOperation = true;
            return null;
        }
        const string BrochureDirectory = "~/Brochures/";
        string brochurePath = BrochureDirectory + BrochureUpload.FileName;
        string fileNameWithoutExtension = 
            System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
        int iteration = 1;
        while (System.IO.File.Exists(Server.MapPath(brochurePath)))
        {
            brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension, 
                "-", iteration, ".pdf");
            iteration++;
        }
        // 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 null;
    }
}
private void DeleteRememberedBrochurePath()
{
    // Is there a file to delete?
    if (deletedCategorysPdfPath != null)
    {
        System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
    }
}

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

protected void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    // Reference the RadioButtonList
    RadioButtonList BrochureOptions = 
        (RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions");
    // Get BrochurePath information about the record being updated
    int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    if (BrochureOptions.SelectedValue == "1")
    {
        // Use current value for BrochurePath
        if (category.IsBrochurePathNull())
            e.NewValues["brochurePath"] = null;
        else
            e.NewValues["brochurePath"] = category.BrochurePath;
    }
    else if (BrochureOptions.SelectedValue == "2")
    {
        // Remove the current brochure (set it to NULL in the database)
        e.NewValues["brochurePath"] = null;
    }
    else if (BrochureOptions.SelectedValue == "3")
    {
        // Reference the BrochurePath FileUpload control
        FileUpload BrochureUpload = 
            (FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload");
        // Process the BrochureUpload
        bool cancelOperation = false;
        e.NewValues["brochurePath"] = 
            ProcessBrochureUpload(BrochureUpload, out cancelOperation);
        e.Cancel = cancelOperation;
    }
    else
    {
        // Unknown value!
        throw new ApplicationException(
            string.Format("Invalid BrochureOptions value, {0}", 
                BrochureOptions.SelectedValue));
    }
    if (BrochureOptions.SelectedValue == "2" || 
        BrochureOptions.SelectedValue == "3")
    {
        // "Remember" that we need to delete the old PDF file
        if (category.IsBrochurePathNull())
            deletedCategorysPdfPath = null;
        else
            deletedCategorysPdfPath = category.BrochurePath;
    }
}
protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    // If there were no problems and we updated the PDF file, 
    // then delete the existing one
    if (e.Exception == null)
    {
        DeleteRememberedBrochurePath();
    }
}

Обратите внимание, 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 . Однако если этот массив имеет null значение, вызывается альтернативная 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
FileUpload PictureUpload = 
    (FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
    // Make sure the picture upload is valid
    if (ValidPictureUpload(PictureUpload))
    {
        e.NewValues["picture"] = PictureUpload.FileBytes;
    }
    else
    {
        // Invalid file upload, cancel update and exit event handler
        e.Cancel = true;
        return;
    }
}

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

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

private bool ValidPictureUpload(FileUpload PictureUpload)
{
    // Make sure that a JPG has been uploaded
    if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), 
            ".jpg", true) != 0 &&
        string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), 
            ".jpeg", true) != 0)
    {
        UploadWarning.Text = 
            "Only JPG documents may be used for a category's picture.";
        UploadWarning.Visible = true;
        return false;
    }
    else
    {
        return true;
    }
}

Шаг 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 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];
    // 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);
}

Примечание

Интерфейсы 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.