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


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

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

Скачивание PDF

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

Введение

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

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

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

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

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

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

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

Теперь необходимо указать инструкцию UPDATE SQL. Мастер автоматически предлагает оператор UPDATE, который соответствует основному запросу 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, чтобы включить методы обновления и удаления категории. Это методы, которые будут вызываться из слоя презентации.

Для удаления категории можно использовать автоматически созданный метод CategoriesTableAdapterDelete. Добавьте приведенный ниже метод в класс 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, 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 предоставляет встроенные возможности удаления, и их можно включить, поставив галочку в флажке, если источник данных GridView поддерживает удаление. В настоящее время GridView привязан к ObjectDataSource (CategoriesDataSource), который не поддерживает удаление.

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

Теперь установите раскрывающийся список вкладки UPDATE в положение "(Нет)", но оставьте раскрывающийся список вкладки DELETE установленным на DeleteCategory. Мы вернемся к этому мастеру на шаге 6, чтобы добавить поддержку обновления.

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

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

Замечание

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

Объект ObjectDataSource теперь будет содержать значение для его свойства DeleteMethod, а также DeleteParameter. Помните, что при использовании мастера для указания методов Visual Studio присваивает свойству ObjectDataSource OldValuesParameterFormatString значение original_{0}, в результате чего возникают проблемы с вызовами метода обновления и удаления. Таким образом, удалите это свойство полностью или сбросите его до значения по умолчанию {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 (щелкните, чтобы просмотреть изображение полного размера)

Уделите мгновение, чтобы проверить функцию удаления. Существует внешний ключ между Products таблицами CategoryID и 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));
        }
    }
}

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

Замечание

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

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

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

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

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

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

Щелкните ссылку "Настройка источника данных" мастера 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 не имеет интерфейса редактирования для PdfPath

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

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

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

  • Оставьте брошюру категории as-is,
  • Обновите брошюру категории, загрузив новую брошюру, или
  • Удалите брошюру категории полностью (в случае, если категория больше не имеет связанной брошюры).

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

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

  • Использование текущей брошюры
  • Удаление текущей брошюры
  • Отправка новой брошюры

Присвойте первому ListItemSelected свойству значение true.

Добавьте три элемента списка в 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, свойство элемента управления FileUpload имеет значение Visible только в том случае, если элемент управления true RadioButtonList SelectedValue равен 3, который является Value для новой брошюры ListItemUpload.

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

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

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

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

Рис. 15. Выбор параметра "Отправить новую брошюру" отображает элемент управления FileUpload (щелкните, чтобы просмотреть изображение полного размера)

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

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

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

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

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

Ниже приведен код для этих двух методов. Обратите внимание на сходство между ProcessBrochureUpload и обработчиком событий ItemInserting 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));
    }
}

Обработчики событий GridView RowUpdating и RowUpdated используют методы 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, имя которого является значением свойства DataImageUrlField ImageField, а значение параметра — это значение, введенное в текстовое поле в интерфейсе редактирования. Это поведение подходит при сохранении образа в виде файла в файловой системе и 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 одинаковы.

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

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

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

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

Преобразование ImageField в TemplateField таким образом создает TemplateField с двумя шаблонами. Как показано в следующем декларативном синтаксисе, ItemTemplate содержит элемент управления Image Web, свойство которого ImageUrl устанавливается с помощью синтаксиса привязки данных на основе свойств DataImageUrlField и DataImageUrlFormatString ImageField. 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 s EditItemTemplate в раскрывающемся списке. В шаблоне вы увидите текстовое поле, удалите его. Затем перетащите элемент управления FileUpload из панели элементов в шаблон, задав его значение IDPictureUpload. Кроме того, добавьте текст, чтобы изменить рисунок категории, укажите новое изображение. Чтобы изображение категории оставалось прежним, оставьте поле пустым в шаблоне.

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

Рис. 17. Добавление элемента управления FileUpload в EditItemTemplate (нажмите, чтобы увидеть изображение в полном размере)

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

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

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

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

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

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

// 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; он вызывается только в том случае, если файл рисунка отправлен. Если файл не загружен, параметр рисунка не задан и поэтому использует его значение 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 и Description boundFields в DetailsView и GridView должны быть преобразованы в TemplateFields. Поскольку значения CategoryName не допускаются в NULL, необходимо добавить RequiredFieldValidator. И текстовое Description поле, вероятно, должно быть преобразовано в многострочное текстовое поле. Я оставляю эти завершающие штрихи в качестве упражнения для вас.

Сводка

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

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

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

Сведения о авторе

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

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

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