Обновление и удаление существующих двоичных данных (C#)
В предыдущих руководствах мы видели, как элемент управления 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 и нажмите кнопку Далее.
Рис. 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
и нажмите кнопку Готово.
Рис. 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
.
Рис. 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, чтобы добавить поддержку обновления.
Рис. 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
.
Рис. 5. Включение поддержки удаления в GridView (щелкните для просмотра полноразмерного изображения)
Уделите немного времени, чтобы проверить функцию удаления. Между таблицами и таблицами CategoryID
существует внешний ключProducts
, поэтому при попытке удалить одну из первых восьми категорий вы получите исключение нарушения ограничения внешнего Categories
ключа.CategoryID
Чтобы протестировать эту функцию, добавьте новую категорию, предоставив брошюру и рисунок. Моя категория тестов, показанная на рис. 6, включает файл тестовой брошюры с именем Test.pdf
и изображение теста. На рисунке 7 показан Элемент GridView после добавления тестовой категории.
Рис. 6. Добавление тестовой категории с помощью брошюры и изображения (щелкните для просмотра полноразмерного изображения)
Рис. 7. После вставки категории "Тест" она отображается в GridView (щелкните для просмотра полноразмерного изображения)
В Visual Studio обновите Обозреватель решений. Теперь в папке ~/Brochures
Test.pdf
должен появиться новый файл (см. рис. 8).
Затем щелкните ссылку Удалить в строке Категория теста, что приведет к обратной отправке страницы и CategoriesBLL
вызову метода класса DeleteCategory
. Это вызовет метод DAL, Delete
что приведет к отправке соответствующей DELETE
инструкции в базу данных. Затем данные возвращаются в GridView, а разметка отправляется обратно клиенту с отсутствием категории тестирования.
Хотя рабочий процесс удаления успешно удалил запись категории тестирования из Categories
таблицы, файл брошюры не был удален из файловой системы веб-сервера. Обновите Обозреватель решений, и вы увидите, что Test.pdf
он по-прежнему находится в папке~/Brochures
.
Рис. 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
. Затем вызывается класс 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
). Измените это значение таким образом, чтобы он использовал перегрузку с пятью параметрами.
Рис. 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
добавлению кнопки "Изменить" (и "Обновить" и "Отмена" для редактируемой строки).
Рис. 10. Настройка GridView для поддержки редактирования (щелкните для просмотра полноразмерного изображения)
Перейдите на страницу в браузере и нажмите одну из кнопок Изменить в строке. Поля CategoryName
и Description
BoundField отрисовываются в виде текстовых полей. TemplateField BrochurePath
не имеет EditItemTemplate
, поэтому он продолжает показывать ссылку ItemTemplate
на брошюру. ImageField Picture
отрисовывается в виде элемента TextBox, свойству которого Text
присваивается значение ImageField, DataImageUrlField
в данном случае CategoryID
.
Рис. 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
.
Рис. 12. Добавление трех ListItem
в RadioButtonList
Под элементом RadioButtonList добавьте элемент управления FileUpload с именем BrochureUpload
. Для его свойства Visible
задайте значение false
.
Рис. 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
для брошюры ListItem
Upload new .
Используя этот код, уделите время, чтобы протестировать интерфейс редактирования. Нажмите кнопку Изменить для строки. Изначально должен быть выбран параметр Использовать текущую брошюру. Изменение выбранного индекса вызывает обратную передачу. Если выбран третий параметр, отображается элемент управления FileUpload, в противном случае он скрыт. На рисунке 14 показан интерфейс редактирования при первом нажатии кнопки Изменить. На рисунке 15 показан интерфейс после выбора параметра Отправить новый буклет.
Рис. 14. Изначально выбран параметр Использовать текущую брошюру (щелкните для просмотра полноразмерного изображения)
Рис. 15. Выбор параметра "Отправить новый буклет" Отображает элемент управления FileUpload (Щелкните для просмотра полноразмерного изображения)
Сохранение файла брошюры и обновление столбцаBrochurePath
При нажатии кнопки Update GridView срабатывает событие RowUpdating
. Вызывается команда обновления ObjectDataSource, а затем запускается событие GridView RowUpdated
. Как и в случае с рабочим процессом удаления, необходимо создать обработчики событий для обоих этих событий. В обработчике RowUpdating
событий необходимо определить, какое действие следует предпринять на SelectedValue
основе элемента BrochureOptions
RadioButtonList:
SelectedValue
Если имеет значение 1, мы хотим использовать тот жеBrochurePath
параметр. Поэтому необходимо задать для параметра ObjectDataSourcebrochurePath
существующееBrochurePath
значение обновляемой записи. Параметр ObjectDataSourcebrochurePath
можно задать с помощью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));
}
}
Обработчики 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.
Рис. 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
. Кроме того, добавьте текст Чтобы изменить рисунок категории, укажите новый рисунок. Чтобы оставить изображение категории прежним, оставьте поле пустым для шаблона.
Рис. 17. Добавление элемента управления FileUpload в EditItemTemplate
(Щелкните для просмотра полноразмерного изображения)
После настройки интерфейса редактирования просмотрите ход выполнения в браузере. При просмотре строки в режиме только для чтения изображение категории отображается как раньше, но при нажатии кнопки Изменить столбец рисунка отображается в виде текста с элементом управления 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, выполнив следующие действия:
- Сохраните растровые изображения на жестком диске. Перейдите на страницу
UpdatingAndDeleting.aspx
браузера и для каждой из первых восьми категорий щелкните изображение правой кнопкой мыши и выберите сохранить изображение. - Откройте изображение в выбранном редакторе изображений. Например, можно использовать Microsoft Paint.
- Сохраните растровое изображение в формате JPG.
- Обновите изображение категории с помощью интерфейса редактирования с помощью 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.