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

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

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

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

Введение

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

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

Так как уровень представления должен только интерфейсировать со слоем бизнес-логики, а не обходить его для перехода непосредственно к уровню доступа к данным, необходимо создать метод BLL, который вызывает только что созданный метод DAL (InsertWithPicture). В этом руководстве создайте метод в классе с именем InsertWithPicture , который принимает в CategoriesBLL качестве входных данных три string и byte массив. Входные string параметры предназначены для имени категории, описания и пути к файлу буклета, а byte массив — для двоичного содержимого рисунка категории. Как показано в следующем коде, этот метод 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);
}

Примечание

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

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

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

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

Скопируйте и вставьте декларативный синтаксис из DisplayOrDownload.aspx в UploadInDetailsView.aspx

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

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

Теперь вы должны увидеть каждую категорию вместе с ее двоичными данными

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

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

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

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

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

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

Примечание

После завершения работы мастера 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 с панели элементов на Designer над GridView, присвоив его ID свойству значение NewCategory и очислив Height значения свойств и Width . В смарт-теге DetailsView привяжите его к существующемуCategoriesDataSource, а затем проверка флажок Включить вставку.

Снимок экрана: открытый элемент DetailsView со свойством CategoryID, для свойства NewCategory, пустыми значениями свойств Height и Width и установленным флажком Включить вставку.

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

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

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

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

Снимок экрана: окно полей с выделенными полями TemplateField, Picture и HeaderText.

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

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

<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 InsertItemTemplate содержит элемент TextBox, а Picture TemplateField не содержит никаких шаблонов. Нам нужно обновить эти два templateField s для InsertItemTemplate использования элементов управления FileUpload.

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

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

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

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

<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 или JPG? Чтобы разрешить различные типы файлов, необходимо расширить Categories схему, включив столбец, который фиксирует тип файла, чтобы этот тип можно было отправить клиенту через Response.ContentType в DisplayCategoryPicture.aspx. Так как у нас нет такого столбца, было бы разумно ограничить пользователей только предоставлением определенного типа файла изображения. Существующие Categories изображения таблицы являются растровыми изображениями, но JPG являются более подходящим форматом файлов для изображений, передаваемых через Интернет.

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

Примечание

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

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

Когда пользователь вводит значения для новой категории и нажимает кнопку Вставка, происходит обратная связь и разворачивается рабочий процесс вставки. Во-первых, срабатывает событие DetailsViewItemInserting. Затем вызывается метод ObjectDataSource Insert() , что приводит к добавлению новой записи в таблицу Categories . После этого срабатывает событие DetailsViewItemInserted.

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

// 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. Затем, если брошюра была загружена, проверяется расширение отправленного файла. Если расширение не .PDF, отображается предупреждение, вставка отменяется, а выполнение обработчика событий завершается.

Примечание

Использование расширения отправленного файла не является верным методом для обеспечения того, что отправленный файл является 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 control s SaveAs(path) . Чтобы обновить параметр ObjectDataSource, brochurePath используйте коллекцию 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 в picture событии ItemInserting DetailsView. Однако перед выполнением этого задания необходимо сначала убедиться, что загруженное изображение является JPG, а не каким-либо другим типом изображения. Как и в шаге 6, давайте используем расширение файла загруженного изображения, чтобы определить его тип.

Хотя таблица 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, чтобы при возникновении проблем с отправкой рисунка обработчик событий завершит работу перед сохранением файла брошюры в файловой системе.

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

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

Обработчик событий CompleteItemInserting

Для полноты ниже приведен 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, или брошюру, не относясь к PDF. В любом из этих случаев отобразится сообщение об ошибке, а рабочий процесс вставки будет отменен.

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

Рис. 9. При отправке недопустимого типа файла отображается предупреждающее сообщение (щелкните для просмотра полноразмерного изображения)

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

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

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

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

Так как теперь в Categories таблице есть точечные рисунки с заголовками OLE и JPG, нам нужно обновить DisplayCategoryPicture.aspx , чтобы она обрезала заголовки OLE для исходных восьми категорий и обходила эту зачистку для новых записей категорий. В следующем руководстве мы рассмотрим, как обновить существующее изображение записи, и обновим все старые изображения категорий, чтобы они были JPG. Пока, однако, используйте следующий код в , 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. Удаление брошюры перед лицом исключения

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

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

Как обсуждалось ранее в руководстве По обработке исключений BLL- и DAL-Level в ASP.NET Page , при возникновении исключения из глубин архитектуры оно перемещается по различным уровням. На уровне представления можно определить, произошло ли исключение из события DetailsView ItemInserted . Этот обработчик событий также предоставляет значения объектов ObjectDataSource InsertParameters. Таким образом, можно создать обработчик событий для ItemInserted события, который проверяет наличие исключения и, если да, удаляет файл, указанный параметром ObjectDataSource:brochurePath

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 для каждого поля двоичных данных. Отправленные данные затем можно сохранить в файловой системе веб-сервера или назначить параметру источника данных в обработчике ItemInserting событий DetailsView.

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

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

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

Об авторе

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