Partilhar via


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

por Scott Mitchell

Descarregar PDF

Este tutorial mostra como criar uma interface Web que permite ao usuário inserir dados de texto e carregar 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 é 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, examinamos 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 da Web de dados. No entanto, ainda não falamos sobre como associar os dados carregados ao modelo de dados.

Neste tutorial, vamos criar uma página 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 imagem da nova categoria e outro para o folheto. A imagem carregada será armazenada diretamente na coluna s Picture do novo registro, enquanto o folheto será salvo na ~/Brochures pasta com o caminho para o arquivo salvo na coluna s BrochurePath do novo registro.

Antes de criar esta nova página da Web, precisaremos atualizar a arquitetura. A consulta principal de CategoriesTableAdapter não recupera a coluna Picture. Consequentemente, o método gerado automaticamente Insert só tem entradas para os campos CategoryName, Description, e BrochurePath. Portanto, precisamos criar um método adicional no TableAdapter que solicita todos os quatro Categories campos. A CategoriesBLL classe na camada de lógica de negócios também precisará ser atualizada.

Etapa 1: Adicionando umInsertWithPicturemétodo aoCategoriesTableAdapter

Quando criamos o CategoriesTableAdapter no tutorial Criando uma camada de acesso a dados, configurámo-lo para gerar instruções INSERT, UPDATE e DELETE automaticamente com base na consulta principal. Além disso, instruímos o TableAdapter a empregar a abordagem DB Direct, que criou os métodos Insert, Updatee Delete. Esses métodos executam as instruções INSERT, UPDATE, e DELETE geradas automaticamente e, consequentemente, aceitam parâmetros de entrada com base nas colunas retornadas pela consulta principal. No tutorial Carregamento de Ficheiros aumentámos a consulta principal da CategoriesTableAdapter para usar a coluna BrochurePath.

Como a CategoriesTableAdapter consulta principal s 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 uso da coluna Picture. Isso atualizaria o método do TableAdapter Insert para incluir um parâmetro de entrada adicional para os dados binários da imagem da categoria. Poderíamos então criar um método na Camada de Lógica de Negócios para usar esse método DAL e invocar esse método BLL através da Camada de Apresentação, e tudo funcionaria maravilhosamente. Ou seja, até a próxima vez que configuramos o TableAdapter através do assistente de configuração do TableAdapter. Assim que o assistente de configuração fosse concluído, as nossas personalizações da instrução INSERT seriam sobrescritas, o método Insert reverteria para sua forma antiga e o nosso código deixaria de compilar!

Observação

Esse incômodo 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, deixe s criar um novo método para o TableAdapter. Esse método, chamado InsertWithPicture, aceitará valores para as CategoryNamecolunas , Description, BrochurePathe Picture executará uma INSERT instrução que armazena todos os quatro valores em um novo registro.

Abra o Typed DataSet e, no Designer, clique com o botão direito do rato no cabeçalho CategoriesTableAdapter e escolha "Add Query" 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 o tipo de consulta a ser gerada. Como estamos criando uma consulta para adicionar um novo registro à Categories tabela, escolha INSERIR e clique em Avançar.

Selecione a opção INSERIR

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

Agora precisamos especificar a INSERT instrução SQL. O assistente sugere automaticamente uma INSERT instrução correspondente à consulta principal do TableAdapter. Neste caso, é uma INSERT instrução que insere o CategoryName, Descriptione BrochurePath valores. 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. Entre InsertWithPicture e clique em Concluir.

Nomeie o novo método TableAdapter InsertWithPicture

Figura 2: Nomeie o novo método InsertWithPicture TableAdapter (Clique para visualizar a imagem em tamanho real)

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

Como a Camada de Apresentação deve interagir 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 aceite como entrada três string s e uma byte matriz. Os string parâmetros de entrada são para o nome da categoria, descrição e caminho do arquivo de brochura, 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

Certifique-se de ter salvo o DataSet Tipado antes de adicionar o método InsertWithPicture à BLL. Como o código de CategoriesTableAdapter classe é gerado automaticamente com base no DataSet Typed, se você não salvar primeiro suas alterações no DataSet Typed, a Adapter propriedade não saberá sobre o InsertWithPicture método.

Etapa 3: Listando 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 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 seu 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 por primeiro abrir a página DisplayOrDownload.aspx a partir da pasta BinaryData. Vá para a visualizaçã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 método GenerateBrochureLink do código subjacente da classe DisplayOrDownload.aspx para UploadInDetailsView.aspx.

Copie e cole a sintaxe declarativa de DisplayOrDownload.aspx para UploadInDetailsView.aspx

Figura 3: Copie e cole a sintaxe declarativa de DisplayOrDownload.aspx para UploadInDetailsView.aspx (Clique para visualizar a imagem em tamanho real)

Depois de copiar a sintaxe declarativa e o método GenerateBrochureLink na página UploadInDetailsView.aspx, visualize a página através de um navegador para confirmar que tudo foi copiado corretamente. Você verá um GridView listando as oito categorias que inclui 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 visualizar a imagem em tamanho real)

Etapa 4: Configurar CategoriesDataSource para suportar a inserção.

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

Comece por clicar no link Configurar Fonte de Dados na etiqueta inteligente do ObjectDataSource. A primeira tela mostra o objeto com o qual a fonte de dados está configurada para trabalhar, CategoriesBLL. Deixe essa configuração as-is e clique em Avançar para avançar para a tela Definir métodos de dados. Vá para o separador Inserir e escolha o método InsertWithPicture na lista suspensa. Clique em Concluir para finalizar o assistente.

Configurar o ObjectDataSource para usar o método InsertWithPicture

Figura 5: Configurar o ObjectDataSource para usar o método (InsertWithPicture imagem em tamanho real)

Observação

Ao concluir o assistente de configuração, o Visual Studio pode perguntar se o utilizador gostaria de atualizar campos e chaves, o que irá regenerar os campos de controlos da Web de dados. Escolha Não, porque escolher Sim substituirá quaisquer personalizações de campo que você possa ter feito.

Depois de concluir o assistente, o ObjectDataSource agora incluirá um valor para a sua propriedade InsertMethod, assim como para InsertParameters nas quatro colunas de categoria, conforme 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 oferece 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 dele será atualizado automaticamente e exibirá a nova categoria.

Comece por arrastar um DetailsView da Caixa de Ferramentas para o Designer acima do GridView, definindo sua propriedade ID para NewCategory e limpando os valores das propriedades Height e Width. Na etiqueta inteligente do DetailsView, vincule-a à existente CategoriesDataSource e marque a caixa de seleção Ativar Inserção.

Captura de tela mostrando DetailsView aberto com a propriedade CategoryID definida como NewCategory, valores de propriedade Height e Width vazios e a caixa de seleção Enable Inserting marcada.

Figura 6: Vincular o DetailsView ao CategoriesDataSource e habilitar a inserção (Clique para visualizar 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, NumberOfProducts, e BrochurePath, embora o CategoryID BoundField não seja renderizado na interface de inserção porque a sua InsertVisible propriedade está definida como false. Esses BoundFields existem porque são as colunas retornadas pelo GetCategories() método, que é o que o ObjectDataSource invoca para recuperar os seus dados. Para inserir, no entanto, não queremos permitir que o usuário especifique um valor para NumberOfProducts. Além disso, temos de permitir que carreguem uma imagem para a nova categoria, bem como um PDF para a brochura.

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

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

Figura 7: Vincular o DetailsView ao CategoriesDataSource e permitir 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. Apenas o InsertItemTemplate é necessário, no entanto, então sinta-se livre para remover os outros dois modelos. Neste ponto, a sintaxe declarativa de DetailsView deve ter a seguinte aparência:

<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 Brochura e Imagem

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

Na etiqueta inteligente do DetailsView, escolha a opção Editar modelos de estrutura e, em seguida, selecione o BrochurePath TemplateField InsertItemTemplate na lista suspensa. Remova o TextBox e, em seguida, arraste um controle FileUpload da caixa de ferramentas para o modelo. Defina o controle FileUpload s ID como BrochureUpload. Da mesma forma, adicione um controle FileUpload ao Picture TemplateField s InsertItemTemplate. Defina este controle FileUpload s ID como PictureUpload.

Adicionar um controle FileUpload ao InsertItemTemplate

Figura 8: Adicionar um controlo FileUpload ao InsertItemTemplate (Clique para visualizar 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 a brochura, o utilizador 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 diferentes tipos de ficheiro, precisamos de estender o esquema Categories para incluir uma coluna que capture o tipo de ficheiro, para que esse tipo possa ser enviado ao cliente através do Response.ContentType 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 JPGs são um formato de arquivo mais apropriado para imagens veiculadas na Web.

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

Observação

Idealmente, os CategoryName e Description BoundFields seriam convertidos em TemplateFields e as suas interfaces de inserção seriam personalizadas. A interface de inserção Description, por exemplo, provavelmente seria melhor se fosse apresentada numa 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. Estes passos são deixados 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: Salvando o folheto carregado no sistema de arquivos do servidor Web

Quando o utilizador insere os valores para uma nova categoria e clica no botão Inserir, ocorre um postback e o fluxo de trabalho de inserção é iniciado. Primeiro, o evento DetailsView s ItemInserting é acionado. Em seguida, o método do ObjectDataSource Insert() é invocado, o que resulta na adição de um novo registo à tabela Categories. Depois disso, o evento DetailsView s ItemInserted é acionado.

Antes que o método ObjectDataSource s 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 s 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 fazendo referência ao BrochureUpload controle FileUpload dos modelos DetailsView. Em seguida, se uma brochura tiver sido carregada, a extensão do ficheiro carregado é examinada. Se a extensão não estiver .PDF, um aviso será exibido, a inserção será cancelada e a execução do manipulador de eventos será encerrada.

Observação

Confiar na extensão do arquivo carregado não é uma técnica infalível para garantir que o arquivo carregado seja um documento PDF. O usuário poderia ter um documento PDF válido com a extensão .Brochure, ou poderia ter pego 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 de forma mais conclusiva o tipo de arquivo. No entanto, estas abordagens exaustivas são muitas vezes exageradas; verificar a extensão é suficiente para a maioria dos cenários.

Conforme discutido no tutorial Upload de arquivos , é preciso ter cuidado ao salvar arquivos no sistema de arquivos para que o upload de um usuário não substitua outro. Para este tutorial, tentaremos usar o mesmo nome do arquivo carregado. Se já existir um arquivo no ~/Brochures diretório com esse 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 brochura chamado Meats.pdf, mas já houver um arquivo nomeado 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. Em caso afirmativo, ele continua 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 de ObjectDataSource precisa brochurePath``InsertParameter ser atualizado para que esse nome de arquivo seja gravado no banco de dados. Como vimos no tutorial Carregamento de Ficheiros, o ficheiro pode ser salvo usando o método do controlo SaveAs(path) FileUpload. Para atualizar o parâmetro ObjectDataSource brochurePath, utilize a coleção e.Values.

// 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 registo, precisamos atribuir o conteúdo binário carregado ao parâmetro picture do ObjectDataSource no evento ItemInserting do DetailsView. Antes de fazermos essa atribuição, no entanto, precisamos primeiro nos certificar de que a imagem carregada é um JPG e não algum outro tipo de imagem. Como no Passo 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 através 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 da imagem, o manipulador de eventos seja encerrado antes que o arquivo de brochura seja salvo no sistema de arquivos.

Supondo que um arquivo apropriado tenha sido carregado, atribua o conteúdo binário carregado ao valor s 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 completar, aqui está o manipulador de ItemInserting 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: Corrigindo aDisplayCategoryPicture.aspxpágina

Vamos reservar um momento para testar a interface de inserção e ItemInserting o manipulador de eventos que foi criado nas últimas etapas. Visite a página UploadInDetailsView.aspx através de um navegador e tente adicionar uma categoria, mas omita a imagem ou especifique uma imagem que não seja JPG ou um ficheiro PDF que não seja uma brochura. 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 é exibida se um tipo de arquivo inválido for carregado

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

Depois de verificar que a página requer o upload de uma imagem e não aceita arquivos não PDF ou não JPG, adicione uma nova categoria com uma imagem JPG válida, deixando o campo Brochura vazio. Depois de clicar no botão Inserir, a página será recarregada e um novo registo será adicionado à tabela Categories com o conteúdo binário da imagem carregada armazenado diretamente na base de dados. O GridView é atualizado e mostra uma linha para a categoria recém-adicionada, mas, como mostra a Figura 10, a imagem da nova categoria não é processada corretamente.

A imagem da nova categoria não é exibida

Figura 10: A imagem da nova categoria não é exibida (Clique para visualizar a imagem em tamanho real)

A razão pela qual a nova imagem não é exibida é porque a página que retorna uma imagem de categoria especificada está configurada DisplayCategoryPicture.aspx para processar bitmaps que têm um cabeçalho OLE. Esse cabeçalho de 78 bytes é removido do conteúdo binário da coluna Picture antes de ser enviado de volta ao 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 sejam JPGs. Por enquanto, porém, use o seguinte código em DisplayCategoryPicture.aspx para remover apenas os cabeçalhos OLE das 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 visualizar a imagem em tamanho real)

Passo 9: Eliminar o folheto diante 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 ao inserir. 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, ocorre um postback e o evento DetailsView é ItemInserting acionado, salvando o folheto no sistema de arquivos do servidor Web. Em seguida, o método Insert() do ObjectDataSource é invocado, o que chama o método CategoriesBLL da classe InsertWithPicture, que por sua vez chama o método CategoriesTableAdapter de InsertWithPicture.

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

Como discutido anteriormente no tutorial Manipulando Exceções de BLL e DAL-Level numa Página ASP.NET, quando uma exceção é lançada nas camadas mais profundas da arquitetura, ela é propagada através das várias camadas. Na camada de apresentação, podemos determinar se ocorreu uma exceção do evento s ItemInserted DetailsView. Este 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, em caso afirmativo, 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á uma série de 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, é provável que você precise 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 é 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 o upload de um utilizador substitua o de outro. Além disso, etapas adicionais devem ser tomadas 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 uma brochura e imagem, mas ainda temos que olhar para como atualizar os dados binários de uma categoria existente ou como remover corretamente os dados binários para uma categoria excluída. Exploraremos esses dois tópicos no próximo tutorial.

Feliz Programação!

Sobre o Autor

Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.

Um agradecimento especial a

Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Dave Gardner, Teresa Murphy e Bernadette Leigh. Interessado em rever meus próximos artigos do MSDN? Se for o caso, envie-me uma mensagem para mitchell@4GuysFromRolla.com.