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

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 Sub InsertWithPicture(categoryName As String, description As String, _
    brochurePath As String, picture() As Byte)
    
    Adapter.InsertWithPicture(categoryName, description, brochurePath, picture)
End Sub

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 de DetailsView com a propriedade CategoryID definida como NewCategory, os valores da propriedade Height e Width estão vazios 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 carregar 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 BoundField BrochurePath em um TemplateField e adicione um novo TemplateField para a imagem, dando a este novo TemplateField um HeaderText valor de Picture. Mova o Picture TemplateField para que ele esteja entre TemplateField BrochurePath e CommandField.

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

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

Se você converteu BoundField BrochurePath em um TemplateField por meio da caixa de diálogo Editar Campos, o TemplateField incluirá , ItemTemplateEditItemTemplatee InsertItemTemplate. No entanto, somente o InsertItemTemplate é necessário, portanto, fique à vontade para remover os outros dois modelos. Neste ponto, a sintaxe declarativa do 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 s ID como BrochureUpload. 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 capture o tipo de arquivo para que esse tipo possa ser enviado ao cliente por meio Response.ContentType de 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 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 UploadWarningcomo , limpe 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álico e em negrito.

Observação

Idealmente, o CategoryName e Description BoundFields seriam 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 Personalizar a Interface de Modificação de Dados para ver detalhadamente 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, um postback ocorre e o fluxo de trabalho de inserção se desenrola. Primeiro, o evento DetailsView é ItemInserting acionado. Em seguida, o método ObjectDataSource s 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 seja Insert() invocado, primeiro devemos 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 e ItemInserting adicione o seguinte código:

' Reference the FileUpload controls
Dim BrochureUpload As FileUpload = _
    CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
    ' Make sure that a PDF has been uploaded
    If String.Compare(System.IO.Path.GetExtension _
        (BrochureUpload.FileName), ".pdf", True) <> 0 Then
        UploadWarning.Text = _
            "Only PDF documents may be used for a category's brochure."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
End If

O manipulador de eventos começa referenciando o BrochureUpload controle FileUpload dos modelos 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

Contar com a 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 pode ter usado 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. Tais abordagens completas, no entanto, são muitas vezes 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 ~/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 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.pdfe 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 já existe um arquivo com o nome de arquivo especificado. Nesse caso, ele continuará tentando novos nomes de arquivo para o folheto até que nenhum conflito seja encontrado.

Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
    System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
    brochurePath = String.Concat(BrochureDirectory, _
        fileNameWithoutExtension, "-", iteration, ".pdf")
    iteration += 1
End While

Depois que um nome de arquivo válido for encontrado, o arquivo precisará ser salvo no sistema de arquivos e o valor de ObjectDataSource brochurePath``InsertParameter 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 do controle FileUpload.SaveAs(path) Para atualizar o parâmetro ObjectDataSource s 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 No entanto, antes de fazer essa atribuição, precisamos primeiro verificar se a imagem carregada é um JPG e não algum outro tipo de imagem. 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
Dim PictureUpload As FileUpload = _
    CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
    ' Make sure that a JPG has been uploaded
    If  String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
            ".jpg", True) <> 0 AndAlso _
        String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
            ".jpeg", True) <> 0 Then
        
        UploadWarning.Text = _
            "Only JPG documents may be used for a category's picture."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
Else
    ' No picture uploaded!
    UploadWarning.Text = _
        "You must provide a picture for the new category."
    UploadWarning.Visible = True
    e.Cancel = True
    Exit Sub
End If

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 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 fins de integridade, aqui está o ItemInserting manipulador de eventos em sua totalidade:

Protected Sub NewCategory_ItemInserting _
    (sender As Object, e As DetailsViewInsertEventArgs) _
    Handles NewCategory.ItemInserting
    
    ' Reference the FileUpload controls
    Dim PictureUpload As FileUpload = _
        CType(NewCategory.FindControl("PictureUpload"), FileUpload)
    If PictureUpload.HasFile Then
        ' Make sure that a JPG has been uploaded
        If  String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
                ".jpg", True) <> 0 AndAlso _
            String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
                ".jpeg", True) <> 0 Then
            
            UploadWarning.Text = _
                "Only JPG documents may be used for a category's picture."
            UploadWarning.Visible = True
            e.Cancel = True
            Exit Sub
        End If
    Else
        ' No picture uploaded!
        UploadWarning.Text = _
            "You must provide a picture for the new category."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
    ' Set the value of the picture parameter
    e.Values("picture") = PictureUpload.FileBytes
    ' Reference the FileUpload controls
    Dim BrochureUpload As FileUpload = _
        CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
    If BrochureUpload.HasFile Then
        ' Make sure that a PDF has been uploaded
        If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _
            ".pdf", True) <> 0 Then
            
            UploadWarning.Text = _
                "Only PDF documents may be used for a category's brochure."
            UploadWarning.Visible = True
            e.Cancel = True
            Exit Sub
        End If
        Const BrochureDirectory As String = "~/Brochures/"
        Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
        Dim fileNameWithoutExtension As String = _
            System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
        Dim iteration As Integer = 1
        While System.IO.File.Exists(Server.MapPath(brochurePath))
            brochurePath = String.Concat(BrochureDirectory, _
                fileNameWithoutExtension, "-", iteration, ".pdf")
            iteration += 1
        End While
        ' Save the file to disk and set the value of the brochurePath parameter
        BrochureUpload.SaveAs(Server.MapPath(brochurePath))
        e.Values("brochurePath") = brochurePath
    End If
End Sub

Etapa 8: Corrigir aDisplayCategoryPicture.aspxpágina

Vamos demorar um pouco 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 aceitar arquivos não PDF ou não JPG, adicione uma nova categoria com uma imagem JPG válida, deixando o campo Folheto vazio. Depois de clicar no botão Inserir, a página será postada 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 imagem da nova 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 é removido do Picture conteúdo binário da coluna antes que eles sejam 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 do 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 a imagem de um 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 Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim categoryID As Integer = Convert.ToInt32(Request.QueryString("CategoryID"))
    ' Get information about the specified category
    Dim categoryAPI As New CategoriesBLL()
    Dim categories As Northwind.CategoriesDataTable = _
        categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID)
    Dim category As Northwind.CategoriesRow = categories(0)
    If categoryID <= 8 Then
        ' 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 OleHeaderLength As Integer = 78
        Dim strippedImageLength As Integer = _
            category.Picture.Length - OleHeaderLength
        Dim strippedImageData(strippedImageLength) As Byte
        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)
    End If
End Sub

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 a 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, um postback ocorre e o evento DetailsView é ItemInserting acionado, salvando o folheto no sistema de arquivos do servidor Web. Em seguida, o método s Insert() ObjectDataSource é invocado, que chama o CategoriesBLL método da 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, o 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 Tratamento de Exceções de BLL e DAL-Level em uma página ASP.NET , quando uma exceção é gerada de dentro das profundezas da arquitetura, ela é gerada por meio 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 do 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 Sub NewCategory_ItemInserted _
    (sender As Object, e As DetailsViewInsertedEventArgs) _
    Handles NewCategory.ItemInserted
    
    If e.Exception IsNot Nothing Then
        ' Need to delete brochure file, if it exists
        If e.Values("brochurePath") IsNot Nothing Then
            System.IO.File.Delete(Server.MapPath _
                (e.Values("brochurePath").ToString()))
        End If
    End If
End Sub

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, é 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 for 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 ItemInserting eventos DetailsView.

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 extras 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 analisamos 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 Bernadesa Leigh. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, solte-me uma linha em mitchell@4GuysFromRolla.com.