Incluir uma opção de upload de arquivo ao adicionar um novo registro (C#)

por Scott Mitchell

Baixar PDF

Este tutorial mostra como criar uma interface da Web que permite que o usuário insira dados de texto e carregue arquivos binários. Para ilustrar as opções disponíveis para armazenar dados binários, um arquivo será salvo no banco de dados enquanto o outro estiver armazenado no sistema de arquivos.

Introdução

Nos dois tutoriais anteriores, exploramos técnicas para armazenar dados binários associados ao modelo de dados do aplicativo, analisamos como usar o controle FileUpload para enviar arquivos do cliente para o servidor Web e vimos como apresentar esses dados binários em um controle web de dados. No entanto, ainda não falamos sobre como associar dados carregados ao modelo de dados.

Neste tutorial, criaremos uma página da Web para adicionar uma nova categoria. Além de TextBoxes para o nome e a descrição da categoria, esta página precisará incluir dois controles FileUpload um para a nova imagem da categoria e um para o folheto. A imagem carregada será armazenada diretamente na coluna do novo registro, Picture enquanto o folheto será salvo na ~/Brochures pasta com o caminho para o arquivo salvo na coluna do novo registro.BrochurePath

Antes de criar essa nova página da Web, precisaremos atualizar a arquitetura. A CategoriesTableAdapter consulta s main não recupera a Picture coluna. Consequentemente, o método gerado Insert automaticamente tem apenas entradas para os CategoryNamecampos , Descriptione BrochurePath . Portanto, precisamos criar um método adicional no TableAdapter que solicite todos os quatro Categories campos. A CategoriesBLL classe na Camada de Lógica De Negócios também precisará ser atualizada.

Etapa 1: Adicionar umInsertWithPicturemétodo aoCategoriesTableAdapter

Quando criamos o back no tutorial Criando uma camada de acesso a dados, configuramos-o CategoriesTableAdapter para gerar INSERTautomaticamente instruções , UPDATEe DELETE com base na consulta main. Além disso, instruímos o TableAdapter a empregar a abordagem do BD Direct, que criou os métodos Insert, Updatee Delete. Esses métodos executam as instruções , UPDATEe geradas automaticamente eDELETE, consequentemente, aceitam parâmetros de entrada com base nas colunas retornadas INSERTpela consulta main. No tutorial Carregando Arquivos, aumentamos a CategoriesTableAdapter consulta s main para usar a BrochurePath coluna.

Como a CategoriesTableAdapter consulta s main não faz referência à Picture coluna, não podemos adicionar um novo registro nem atualizar um registro existente com um valor para a Picture coluna. Para capturar essas informações, podemos criar um novo método no TableAdapter que é usado especificamente para inserir um registro com dados binários ou podemos personalizar a instrução gerada INSERT automaticamente. O problema com a personalização da instrução gerada INSERT automaticamente é que corremos o risco de ter nossas personalizações substituídas pelo assistente. Por exemplo, imagine que personalizamos a INSERT instrução para incluir o Picture uso da coluna. Isso atualizaria o método TableAdapter s Insert para incluir um parâmetro de entrada adicional para os dados binários da imagem da categoria. Em seguida, poderíamos criar um método na Camada de Lógica de Negócios para usar esse método DAL e invocar esse método BLL por meio da Camada de Apresentação, e tudo funcionaria maravilhosamente. Ou seja, até a próxima vez que configuramos o TableAdapter por meio do assistente de Configuração tableAdapter. Assim que o assistente for concluído, nossas personalizações para a INSERT instrução serão substituídas, o Insert método reverter à sua forma antiga e nosso código não seria mais compilado!

Observação

Esse aborrecimento não é um problema ao usar procedimentos armazenados em vez de instruções SQL ad hoc. Um tutorial futuro explorará o uso de procedimentos armazenados em vez de instruções SQL ad hoc na Camada de Acesso a Dados.

Para evitar essa possível dor de cabeça, em vez de personalizar as instruções SQL geradas automaticamente, vamos criar um novo método para o TableAdapter. Esse método, chamado InsertWithPicture, aceitará valores para as CategoryNamecolunas , Description, BrochurePathe Picture e e executará uma instrução INSERT que armazena todos os quatro valores em um novo registro.

Abra o Conjunto de Dados Digitado e, no Designer, clique com o botão direito do CategoriesTableAdapter mouse no cabeçalho s e escolha Adicionar Consulta no menu de contexto. Isso inicia o Assistente de Configuração de Consulta TableAdapter, que começa perguntando como a consulta TableAdapter deve acessar o banco de dados. Escolha Usar instruções SQL e clique em Avançar. A próxima etapa solicita que o tipo de consulta seja gerado. Como estamos criando uma consulta para adicionar um novo registro à Categories tabela, escolha INSERT e clique em Avançar.

Selecione a opção INSERT

Figura 1: selecione a opção INSERT (Clique para exibir a imagem em tamanho real)

Agora precisamos especificar a INSERT instrução SQL. O assistente sugere automaticamente uma instrução INSERT correspondente à consulta main do TableAdapter. Nesse caso, é uma instrução INSERT que insere os CategoryNamevalores , Descriptione BrochurePath . Atualize a instrução para que a Picture coluna seja incluída junto com um @Picture parâmetro, da seguinte forma:

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

A tela final do assistente nos pede para nomear o novo método TableAdapter. Insira InsertWithPicture e clique em Concluir.

Nomeie o novo método TableAdapter InsertWithPicture

Figura 2: nomeie o novo método InsertWithPicture TableAdapter (clique para exibir a imagem em tamanho real)

Etapa 2: Atualizando a camada de lógica de negócios

Como a Camada de Apresentação deve interface apenas com a Camada de Lógica de Negócios em vez de ignorá-la para ir diretamente para a Camada de Acesso a Dados, precisamos criar um método BLL que invoque o método DAL que acabamos de criar (InsertWithPicture). Para este tutorial, crie um método na CategoriesBLL classe chamada InsertWithPicture que aceita como entrada três string s e uma byte matriz. Os string parâmetros de entrada são para o nome, a descrição e o caminho do arquivo de folheto da categoria, enquanto a byte matriz é para o conteúdo binário da imagem da categoria. Como mostra o código a seguir, esse método BLL invoca o método DAL correspondente:

[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);
}

Observação

Verifique se você salvou o Conjunto de Dados Digitado antes de adicionar o InsertWithPicture método à BLL. Como o CategoriesTableAdapter código de classe é gerado automaticamente com base no Conjunto de Dados Digitado, se você não salvar as alterações no Conjunto de Dados Digitado, a Adapter propriedade não saberá sobre o InsertWithPicture método .

Etapa 3: Listar as categorias existentes e seus dados binários

Neste tutorial, criaremos uma página que permite que um usuário final adicione uma nova categoria ao sistema, fornecendo uma imagem e um folheto para a nova categoria. No tutorial anterior , usamos um GridView com um TemplateField e ImageField para exibir o nome, a descrição, a imagem e um link de cada categoria para baixar o folheto. Vamos replicar essa funcionalidade para este tutorial, criando uma página que lista todas as categorias existentes e permite que novas sejam criadas.

Comece abrindo a DisplayOrDownload.aspx página da BinaryData pasta. Vá para o modo de exibição Source e copie a sintaxe declarativa de GridView e ObjectDataSource, colando-a dentro do <asp:Content> elemento em UploadInDetailsView.aspx. Além disso, não se esqueça de copiar o GenerateBrochureLink método da classe code-behind de DisplayOrDownload.aspx para UploadInDetailsView.aspx.

Copiar e colar a sintaxe declarativa de DisplayOrDownload.aspx para UploadInDetailsView.aspx

Figura 3: Copiar e colar a sintaxe declarativa de DisplayOrDownload.aspx para UploadInDetailsView.aspx (Clique para exibir imagem em tamanho real)

Depois de copiar a sintaxe declarativa e GenerateBrochureLink o método para a UploadInDetailsView.aspx página, exiba a página por meio de um navegador para garantir que tudo tenha sido copiado corretamente. Você deve ver um GridView listando as oito categorias que incluem um link para baixar o folheto, bem como a imagem da categoria.

Agora você deve ver cada categoria junto com seus dados binários

Figura 4: agora você deve ver cada categoria junto com seus dados binários (clique para exibir a imagem em tamanho real)

Etapa 4: Configurando oCategoriesDataSourcepara dar suporte à inserção

O CategoriesDataSource ObjectDataSource usado pelo Categories GridView atualmente não fornece a capacidade de inserir dados. Para dar suporte à inserção por meio desse controle de fonte de dados, precisamos mapear seu Insert método para um método em seu objeto subjacente, CategoriesBLL. Em particular, queremos mapeá-lo para o CategoriesBLL método que adicionamos novamente na Etapa 2, InsertWithPicture.

Comece clicando no link Configurar Fonte de Dados da marca inteligente ObjectDataSource. A primeira tela mostra o objeto com o qual a fonte de dados está configurada para trabalhar, CategoriesBLL. Deixe essa configuração no estado em que se encontra e clique em Avançar para avançar para a tela Definir Métodos de Dados. Vá para a guia INSERT e escolha o InsertWithPicture método na lista suspensa. Clique em Concluir para concluir o assistente.

Configurar o ObjectDataSource para usar o método InsertWithPicture

Figura 5: configurar o ObjectDataSource para usar o InsertWithPicture Método (Clique para exibir a imagem em tamanho real)

Observação

Ao concluir o assistente, o Visual Studio pode perguntar se você deseja atualizar campos e chaves, o que regenerará os campos de controles da Web de dados. Escolha Não, pois escolher Sim substituirá todas as personalizações de campo que você possa ter feito.

Depois de concluir o assistente, o ObjectDataSource agora incluirá um valor para sua InsertMethod propriedade, bem como InsertParameters para as quatro colunas de categoria, como ilustra a marcação declarativa a seguir:

<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>

Etapa 5: Criando a interface de inserção

Conforme abordado pela primeira vez em Uma Visão Geral da Inserção, Atualização e Exclusão de Dados, o controle DetailsView fornece uma interface de inserção interna que pode ser utilizada ao trabalhar com um controle de fonte de dados que dá suporte à inserção. Vamos adicionar um controle DetailsView a esta página acima do GridView que renderizará permanentemente sua interface de inserção, permitindo que um usuário adicione rapidamente uma nova categoria. Ao adicionar uma nova categoria no DetailsView, o GridView abaixo dela atualizará e exibirá automaticamente a nova categoria.

Comece arrastando um DetailsView da Caixa de Ferramentas para o Designer acima de GridView, definindo sua ID propriedade como NewCategory e limpando os valores de Height propriedade e Width . Na marca inteligente DetailsView, associe-a à existente CategoriesDataSource e, em seguida, marcar caixa de seleção Habilitar Inserção.

Captura de tela mostrando DetailsView aberto com a propriedade CategoryID definida como NewCategory, valores vazios da propriedade Height e Width e a caixa de seleção Habilitar Inserção selecionada.

Figura 6: Associar o DetailsView ao e Habilitar a CategoriesDataSource Inserção (Clique para exibir a imagem em tamanho real)

Para renderizar permanentemente o DetailsView em sua interface de inserção, defina sua DefaultMode propriedade como Insert.

Observe que o DetailsView tem cinco BoundFields CategoryID, CategoryName, Description, NumberOfProductse BrochurePath embora o CategoryID BoundField não seja renderizado na interface de inserção porque sua InsertVisible propriedade está definida falsecomo . Esses BoundFields existem porque são as colunas retornadas pelo GetCategories() método , que é o que o ObjectDataSource invoca para recuperar seus dados. No entanto, para inserir, não queremos permitir que o usuário especifique um valor para NumberOfProducts. Além disso, precisamos permitir que eles carreguem uma imagem para a nova categoria, bem como carreguem um PDF para o folheto.

Remova o NumberOfProducts BoundField do DetailsView completamente e atualize as HeaderText propriedades de CategoryName e BrochurePath BoundFields para Category e Brochure, respectivamente. Em seguida, converta o BrochurePath BoundField em um TemplateField e adicione um novo TemplateField para a imagem, dando a este novo TemplateField um HeaderText valor de Imagem. Mova o Picture TemplateField para que ele fique entre TemplateField BrochurePath e CommandField.

Captura de tela da janela de campos com TemplateField, Picture e HeaderText realçados.

Figura 7: Associar o DetailsView ao CategoriesDataSource e Habilitar a Inserção

Se você converteu o BrochurePath BoundField em um TemplateField por meio da caixa de diálogo Editar Campos, o TemplateField inclui um ItemTemplate, EditItemTemplatee InsertItemTemplate. No entanto, somente o InsertItemTemplate é necessário, portanto, fique à vontade para remover os outros dois modelos. Neste ponto, a sintaxe declarativa de DetailsView deve ser semelhante à seguinte:

<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>

Adicionando controles FileUpload para os campos folheto e imagem

Atualmente, o BrochurePath TemplateField s InsertItemTemplate contém um TextBox, enquanto o Picture TemplateField não contém nenhum modelo. Precisamos atualizar esses dois TemplateField s InsertItemTemplate para usar controles FileUpload.

Na marca inteligente DetailsView, escolha a opção Editar Modelos e selecione TemplateField BrochurePath s InsertItemTemplate na lista suspensa. Remova o TextBox e arraste um controle FileUpload da Caixa de Ferramentas para o modelo. Defina o controle FileUpload como IDBrochureUpload. Da mesma forma, adicione um controle FileUpload ao Picture TemplateField s InsertItemTemplate. Defina esse controle FileUpload como IDPictureUpload.

Adicionar um controle FileUpload ao InsertItemTemplate

Figura 8: Adicionar um controle FileUpload ao InsertItemTemplate (Clique para exibir a imagem em tamanho real)

Depois de fazer essas adições, a sintaxe declarativa de dois TemplateField será:

<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>

Quando um usuário adiciona uma nova categoria, queremos garantir que o folheto e a imagem sejam do tipo de arquivo correto. Para o folheto, o usuário deve fornecer um PDF. Para a imagem, precisamos que o usuário carregue um arquivo de imagem, mas permitimos qualquer arquivo de imagem ou apenas arquivos de imagem de um tipo específico, como GIFs ou JPGs? Para permitir tipos de arquivo diferentes, precisamos estender o Categories esquema para incluir uma coluna que captura o tipo de arquivo para que esse tipo possa ser enviado ao cliente por meio Response.ContentType do em DisplayCategoryPicture.aspx. Como não temos essa coluna, seria prudente restringir os usuários a fornecer apenas um tipo de arquivo de imagem específico. As Categories imagens existentes da tabela são bitmaps, mas os JPGs são um formato de arquivo mais apropriado para imagens atendidas pela Web.

Se um usuário carregar um tipo de arquivo incorreto, precisaremos cancelar a inserção e exibir uma mensagem indicando o problema. Adicione um controle Web rótulo abaixo de DetailsView. Defina sua ID propriedade como UploadWarning, desmarque sua Text propriedade, defina a CssClass propriedade como Warning e as Visible propriedades e EnableViewState como false. A Warning classe CSS é definida em Styles.css e renderiza o texto em uma fonte grande, vermelha, itállica e em negrito.

Observação

O ideal é que o CategoryName e Description BoundFields sejam convertidos em TemplateFields e suas interfaces de inserção personalizadas. A Description interface de inserção, por exemplo, provavelmente seria mais adequada por meio de uma caixa de texto de várias linhas. E como a CategoryName coluna não aceita NULL valores, um RequiredFieldValidator deve ser adicionado para garantir que o usuário forneça um valor para o nome da nova categoria. Essas etapas são deixadas como um exercício para o leitor. Consulte Personalizando a Interface de Modificação de Dados para obter uma visão detalhada sobre como aumentar as interfaces de modificação de dados.

Etapa 6: salvar o folheto carregado no sistema de arquivos do servidor Web

Quando o usuário insere os valores de uma nova categoria e clica no botão Inserir, ocorre um postback e o fluxo de trabalho de inserção se desenrola. Primeiro, o evento DetailsView éItemInserting acionado. Em seguida, o método ObjectDataSource é Insert() invocado, o que resulta em um novo registro sendo adicionado à Categories tabela. Depois disso, o evento DetailsView éItemInserted acionado.

Antes que o método ObjectDataSource Insert() seja invocado, devemos primeiro garantir que os tipos de arquivo apropriados foram carregados pelo usuário e, em seguida, salvar o PDF do folheto no sistema de arquivos do servidor Web. Crie um manipulador de eventos para o evento DetailsView ItemInserting e adicione o seguinte código:

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

O manipulador de eventos começa referenciando o BrochureUpload controle FileUpload dos modelos do DetailsView. Em seguida, se um folheto tiver sido carregado, a extensão do arquivo carregado será examinada. Se a extensão não for .PDF, um aviso será exibido, a inserção será cancelada e a execução do manipulador de eventos terminará.

Observação

Confiar na extensão do arquivo carregado não é uma técnica segura para garantir que o arquivo carregado seja um documento PDF. O usuário pode ter um documento PDF válido com a extensão .Brochureou ter obtido um documento não PDF e dado a ele uma .pdf extensão. O conteúdo binário do arquivo precisaria ser examinado programaticamente para verificar mais conclusivamente o tipo de arquivo. Essas abordagens completas, porém, geralmente são um exagero; verificar a extensão é suficiente para a maioria dos cenários.

Conforme discutido no tutorial Carregando Arquivos , é necessário ter cuidado ao salvar arquivos no sistema de arquivos para que o upload de um usuário não substitua outros s. Para este tutorial, tentaremos usar o mesmo nome que o arquivo carregado. Se já existir um arquivo no diretório com o ~/Brochures mesmo nome de arquivo, no entanto, acrescentaremos um número no final até que um nome exclusivo seja encontrado. Por exemplo, se o usuário carregar um arquivo de folheto chamado Meats.pdf, mas já houver um arquivo chamado Meats.pdf na ~/Brochures pasta, alteraremos o nome do arquivo salvo para Meats-1.pdf. Se isso existir, tentaremos Meats-2.pdf, e assim por diante, até que um nome de arquivo exclusivo seja encontrado.

O código a seguir usa o File.Exists(path) método para determinar se um arquivo já existe com o nome de arquivo especificado. Nesse caso, ele continuará a tentar novos nomes de arquivo para o folheto até que nenhum conflito seja encontrado.

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

Depois que um nome de arquivo válido for encontrado, o arquivo precisará ser salvo no sistema de arquivos e o valor do brochurePath``InsertParameter ObjectDataSource precisa ser atualizado para que esse nome de arquivo seja gravado no banco de dados. Como vimos no tutorial Carregando Arquivos , o arquivo pode ser salvo usando o método de SaveAs(path) controle FileUpload. Para atualizar o parâmetro ObjectDataSource, brochurePath use a e.Values coleção .

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

Etapa 7: salvar a imagem carregada no banco de dados

Para armazenar a imagem carregada no novo Categories registro, precisamos atribuir o conteúdo binário carregado ao parâmetro ObjectDataSource no picture evento DetailsView ItemInserting . Antes de fazer essa atribuição, no entanto, precisamos primeiro garantir que a imagem carregada seja um JPG e não algum outro tipo de imagem. Assim como na Etapa 6, vamos usar a extensão de arquivo de imagem carregada para verificar seu tipo.

Embora a Categories tabela permita NULL valores para a Picture coluna, todas as categorias atualmente têm uma imagem. Vamos forçar o usuário a fornecer uma imagem ao adicionar uma nova categoria por meio desta página. O código a seguir verifica se uma imagem foi carregada e se ela tem uma extensão apropriada.

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

Esse código deve ser colocado antes do código da Etapa 6 para que, se houver um problema com o upload de imagem, o manipulador de eventos seja encerrado antes que o arquivo de folheto seja salvo no sistema de arquivos.

Supondo que um arquivo apropriado tenha sido carregado, atribua o conteúdo binário carregado ao valor do parâmetro de imagem com a seguinte linha de código:

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

O manipulador de eventos completoItemInserting

Para concluir, aqui está o ItemInserting manipulador de eventos em sua totalidade:

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

Etapa 8: Corrigir aDisplayCategoryPicture.aspxpágina

Vamos fazer um momento para testar a interface de inserção e ItemInserting o manipulador de eventos que foi criado nas últimas etapas. Visite a UploadInDetailsView.aspx página por meio de um navegador e tente adicionar uma categoria, mas omita a imagem ou especifique uma imagem não JPG ou um folheto não PDF. Em qualquer um desses casos, uma mensagem de erro será exibida e o fluxo de trabalho de inserção será cancelado.

Uma mensagem de aviso será exibida se um tipo de arquivo inválido for carregado

Figura 9: Uma mensagem de aviso será exibida se um tipo de arquivo inválido for carregado (clique para exibir a imagem em tamanho real)

Depois de verificar se a página requer que uma imagem seja carregada e não aceite arquivos não PDF ou não JPG, adicione uma nova categoria com uma imagem JPG válida, deixando o campo Brochure vazio. Depois de clicar no botão Inserir, a página fará o postback e um novo registro será adicionado à Categories tabela com o conteúdo binário da imagem carregada armazenado diretamente no banco de dados. O GridView é atualizado e mostra uma linha para a categoria recém-adicionada, mas, como mostra a Figura 10, a nova imagem da categoria não é renderizada corretamente.

A Imagem da Nova Categoria não é Exibida

Figura 10: A Imagem da Nova Categoria não é Exibida (Clique para exibir a imagem em tamanho real)

O motivo pelo qual a nova imagem não é exibida é porque a DisplayCategoryPicture.aspx página que retorna uma imagem de categoria especificada está configurada para processar bitmaps que têm um cabeçalho OLE. Esse cabeçalho de 78 bytes é retirado do Picture conteúdo binário da coluna antes de serem enviados de volta para o cliente. Mas o arquivo JPG que acabamos de carregar para a nova categoria não tem esse cabeçalho OLE; portanto, bytes válidos e necessários estão sendo removidos dos dados binários da imagem.

Como agora há bitmaps com cabeçalhos OLE e JPGs na Categories tabela, precisamos atualizar DisplayCategoryPicture.aspx para que ele faça a remoção de cabeçalho OLE para as oito categorias originais e ignore essa remoção para os registros de categoria mais recentes. Em nosso próximo tutorial, examinaremos como atualizar uma imagem de registro existente e atualizaremos todas as imagens de categoria antigas para que elas sejam JPGs. Por enquanto, porém, use o seguinte código em DisplayCategoryPicture.aspx para remover os cabeçalhos OLE somente para essas oito categorias originais:

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

Com essa alteração, a imagem JPG agora é renderizada corretamente no GridView.

As imagens JPG para novas categorias são renderizadas corretamente

Figura 11: As imagens JPG para novas categorias são renderizadas corretamente (clique para exibir imagem em tamanho real)

Etapa 9: excluindo o folheto na face de uma exceção

Um dos desafios de armazenar dados binários no sistema de arquivos do servidor Web é que ele introduz uma desconexão entre o modelo de dados e seus dados binários. Portanto, sempre que um registro é excluído, os dados binários correspondentes no sistema de arquivos também devem ser removidos. Isso também pode entrar em jogo durante a inserção. Considere o seguinte cenário: um usuário adiciona uma nova categoria, especificando uma imagem e um folheto válidos. Ao clicar no botão Inserir, ocorrerá um postback e o evento DetailsView ItemInserting será acionado, salvando o folheto no sistema de arquivos do servidor Web. Em seguida, o método ObjectDataSource é Insert() invocado, que chama o CategoriesBLL método de classe s InsertWithPicture , que chama o CategoriesTableAdapter método s InsertWithPicture .

Agora, o que acontece se o banco de dados estiver offline ou se houver um erro na instrução INSERT SQL? Claramente, insert falhará, portanto, nenhuma nova linha de categoria será adicionada ao banco de dados. Mas ainda temos o arquivo de folheto carregado no sistema de arquivos do servidor Web! Esse arquivo precisa ser excluído diante de uma exceção durante o fluxo de trabalho de inserção.

Conforme discutido anteriormente no tutorial Manipulando exceções de BLL e DAL-Level em uma página de ASP.NET , quando uma exceção é gerada de dentro das profundidades da arquitetura, ela é gerada através das várias camadas. Na Camada de Apresentação, podemos determinar se ocorreu uma exceção do evento DetailsView.ItemInserted Esse manipulador de eventos também fornece os valores de ObjectDataSource s InsertParameters. Portanto, podemos criar um manipulador de eventos para o ItemInserted evento que verifica se houve uma exceção e, nesse caso, exclui o arquivo especificado pelo parâmetro 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()));
    }
}

Resumo

Há várias etapas que devem ser executadas para fornecer uma interface baseada na Web para adicionar registros que incluem dados binários. Se os dados binários estiverem sendo armazenados diretamente no banco de dados, provavelmente você precisará atualizar a arquitetura, adicionando métodos específicos para lidar com o caso em que os dados binários estão sendo inseridos. Depois que a arquitetura tiver sido atualizada, a próxima etapa será criar a interface de inserção, que pode ser realizada usando um DetailsView que foi personalizado para incluir um controle FileUpload para cada campo de dados binários. Os dados carregados podem ser salvos no sistema de arquivos do servidor Web ou atribuídos a um parâmetro de fonte de dados no manipulador de eventos DetailsView.ItemInserting

Salvar dados binários no sistema de arquivos requer mais planejamento do que salvar dados diretamente no banco de dados. Um esquema de nomenclatura deve ser escolhido para evitar que um usuário carregue substituindo outros s. Além disso, etapas adicionais devem ser executadas para excluir o arquivo carregado se a inserção do banco de dados falhar.

Agora temos a capacidade de adicionar novas categorias ao sistema com um folheto e uma imagem, mas ainda não vimos como atualizar os dados binários de uma categoria existente ou como remover corretamente os dados binários de uma categoria excluída. Exploraremos esses dois tópicos no próximo tutorial.

Programação feliz!

Sobre o autor

Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.

Agradecimentos Especiais

Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Dave Gardner, Teresa Murphy e Berne Leigh. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.