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

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

Скачивание примера приложения или Загрузка PDF-файла

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

Введение

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

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

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

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

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

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

Note

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

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

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

выбрать параметр вставки

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

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

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

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

назовите новый метод TableAdapter Инсертвиспиктуре

Рис. 2. Именование нового метода адаптера таблицы InsertWithPicture (щелкните, чтобы просмотреть изображение с полным размером)

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

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

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Insert, false)] 
public void InsertWithPicture(string categoryName, string description, 
    string brochurePath, byte[] picture)
{
    Adapter.InsertWithPicture(categoryName, description, brochurePath, picture);
}

Note

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

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

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

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

скопировать и вставить декларативный синтаксис из Дисплайордовнлоад. aspx в Уплоадиндетаилсвиев. aspx.

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

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

должны отобразиться все категории вместе с двоичными данными

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

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

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

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

настроить ObjectDataSource для использования метода Инсертвиспиктуре

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

Note

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

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

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture">
    <InsertParameters>
        <asp:Parameter Name="categoryName" Type="String" />
        <asp:Parameter Name="description" Type="String" />
        <asp:Parameter Name="brochurePath" Type="String" />
        <asp:Parameter Name="picture" Type="Object" />
    </InsertParameters>
</asp:ObjectDataSource>

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

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

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

привязать DetailsView к CategoriesDataSource и включить вставку

Рис. 6. привязка элемента DetailsView к CategoriesDataSource и включение вставки (щелкните, чтобы просмотреть изображение с полным размером)

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

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

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

Привязка DetailsView к CategoriesDataSource и включение вставки

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

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

<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    DefaultMode="Insert">
    <Fields>
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
            <InsertItemTemplate>
                <asp:TextBox ID="TextBox1" runat="server"
                    Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
            </InsertItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Picture"></asp:TemplateField>
        <asp:CommandField ShowInsertButton="True" />
    </Fields>
</asp:DetailsView>

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

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

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

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

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

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

<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
    <InsertItemTemplate>
        <asp:FileUpload ID="BrochureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
    <InsertItemTemplate>
        <asp:FileUpload ID="PictureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>

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

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

Note

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

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

Когда пользователь вводит значения для новой категории и нажимает кнопку «Вставить», происходит обратная передача, а рабочий процесс вставки размещается. Во первых, срабатывает событиеItemInserting DetailsView s. Затем вызывается метод ObjectDataSource Insert(), который приводит к добавлению новой записи в Categories таблицу. После этого срабатывает событиеItemInserted DetailsView s.

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

// Reference the FileUpload control
FileUpload BrochureUpload = 
    (FileUpload)NewCategory.FindControl("BrochureUpload");
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;
        e.Cancel = true;
        return;
    }
}

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

Note

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

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

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

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++;
}

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

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

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

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

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

// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
    // 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;
        e.Cancel = true;
        return;
    }
}
else
{
    // No picture uploaded!
    UploadWarning.Text = 
        "You must provide a picture for the new category.";
    UploadWarning.Visible = true;
    e.Cancel = true;
    return;
}

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

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

// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;

Полный обработчик событийItemInserting

Для полноты здесь ItemInserting обработчик событий целиком:

protected void NewCategory_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    // Reference the FileUpload controls
    FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
    if (PictureUpload.HasFile)
    {
        // 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;
            e.Cancel = true;
            return;
        }
    }
    else
    {
        // No picture uploaded!
        UploadWarning.Text = 
            "You must provide a picture for the new category.";
        UploadWarning.Visible = true;
        e.Cancel = true;
        return;
    }
    // Set the value of the picture parameter
    e.Values["picture"] = PictureUpload.FileBytes;
    
    
    // Reference the FileUpload controls
    FileUpload BrochureUpload = 
        (FileUpload)NewCategory.FindControl("BrochureUpload");
    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;
            e.Cancel = true;
            return;
        }
        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));
        e.Values["brochurePath"] = brochurePath;
    }
}

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

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

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

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

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

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

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

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

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

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];
    if (categoryID <= 8)
    {
        // For older categories, we must strip the OLE header... images are bitmaps
        // Output HTTP headers providing information about the binary data
        Response.ContentType = "image/bmp";
        // Output the binary data
        // But first we need to strip out the OLE header
        const int OleHeaderLength = 78;
        int strippedImageLength = category.Picture.Length - OleHeaderLength;
        byte[] strippedImageData = new byte[strippedImageLength];
        Array.Copy(category.Picture, OleHeaderLength, strippedImageData, 
            0, strippedImageLength);
        Response.BinaryWrite(strippedImageData);
    }
    else
    {
        // For new categories, images are JPGs...
        
        // Output HTTP headers providing information about the binary data
        Response.ContentType = "image/jpeg";
        // Output the binary data
        Response.BinaryWrite(category.Picture);
    }
}

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

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

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

Шаг 9. Удаление буклета на лицевой стороне исключения

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

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

Как обсуждалось ранее в разделе обработка исключений BLL и DAL в учебнике по страницам ASP.NET , когда исключение создается из глубины архитектуры, она перемещается вверх по различным слоям. На уровне представления можно определить, возникло ли исключение из события ItemInserted DetailsView s. Этот обработчик событий также предоставляет значения InsertParametersObjectDataSource. Таким образом, можно создать обработчик события ItemInserted, который проверяет, было ли исключение, и, если это так, удаляет файл, заданный параметром brochurePath ObjectDataSource:

protected void NewCategory_ItemInserted
    (object sender, DetailsViewInsertedEventArgs e)
{
    if (e.Exception != null)
    {
        // Need to delete brochure file, if it exists
        if (e.Values["brochurePath"] != null)
            System.IO.File.Delete(Server.MapPath(
                e.Values["brochurePath"].ToString()));
    }
}

Сводка

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

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

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

Поздравляем с программированием!

Об авторе

Скотт Митчелл, автор семи книг по ASP/ASP. NET и основатель 4GuysFromRolla.com, работал с веб-технологиями Майкрософт с 1998. Скотт работает как независимый консультант, преподаватель и модуль записи. Его последняя книга — Sams обучать себя ASP.NET 2,0 за 24 часа. Он доступен по адресу mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.

Специальная благодарность

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