Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
por Scott Mitchell
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 CategoriesTableAdapter não recupera a coluna Picture. Consequentemente, o método Insert gerado automaticamente 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 criámos o CategoriesTableAdapter no tutorial Criar uma Camada de Acesso a Dados, configurámos para gerar automaticamente instruções INSERT, UPDATE e DELETE 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 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 que as nossas personalizações sejam substituídas pelo assistente. Por exemplo, imagine que personalizamos a INSERT instrução para incluir a coluna Picture. Isso atualizaria o método Insert do TableAdapter 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 fosse concluído, nossas personalizações para a INSERT instrução seriam substituídas, o Insert método reverteria para sua forma antiga e nosso código não seria mais compilado!
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 no cabeçalho CategoriesTableAdapter 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 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.
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.
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 Sub InsertWithPicture(categoryName As String, description As String, _
brochurePath As String, picture() As Byte)
Adapter.InsertWithPicture(categoryName, description, brochurePath, picture)
End Sub
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 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 da classe de código subjacente 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 para a página UploadInDetailsView.aspx, visualize a página num navegador para garantir 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.
Figura 4: Agora você deve ver cada categoria junto com seus dados binários (Clique para visualizar a imagem em tamanho real)
Etapa 4: Configurando o 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 meio deste controlo de fonte de dados, precisamos mapear o seu método Insert para um método no seu objeto subjacente, CategoriesBLL. Em especial, queremos mapeá-lo para o CategoriesBLL método que voltámos a adicionar 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 a guia INSERT e escolha o InsertWithPicture método na lista suspensa. Clique em Concluir para finalizar o assistente.
Figura 5: Configurar o ObjectDataSource para usar o método (InsertWithPicture imagem em tamanho real)
Observação
Ao concluir o assistente, o Visual Studio pode perguntar ao utilizador se deseja atualizar campos e chaves, o que irá regenerar os campos dos controlos de dados da Web. 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 sua InsertMethod propriedade, bem como InsertParameters para as quatro colunas de categoria, como ilustra a seguinte marcação declarativa:
<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 Toolbox para o Designer acima do GridView, definindo a sua propriedade ID para NewCategory e limpando os valores das propriedades Height e Width. Na etiqueta inteligente do DetailsView, ligue-a ao existente CategoriesDataSource e marque a caixa de seleção de Ativar inserção.
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 propriedade InsertVisible está definida como False. Esses BoundFields existem porque são as colunas retornadas pelo método GetCategories(), 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 HeaderText propriedades do 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 esse novo TemplateField um HeaderText valor de Picture. Mova o Picture TemplateField para que ele fique entre o TemplateField e o BrochurePath CommandField.
Figura 7: Vincule o DetailsView ao CategoriesDataSource e habilite 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 esses dois TemplateFields para que InsertItemTemplate utilizem controles FileUpload.
Na etiqueta inteligente DetailsView, escolha a opção Edit Templates e, em seguida, selecione o BrochurePath TemplateFields 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.
Figura 8: Adicione um controlo de Carregamento de Ficheiros a 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, precisaríamos estender o modelo 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, esvazie a 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 rende o texto em um estilo de 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 Description interface de inserção, por exemplo, provavelmente seria mais adequada através 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. 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 se desenrola. Primeiro, o evento DetailsView s ItemInserting é acionado. Em seguida, o método Insert() s ObjectDataSource é invocado, o que resulta em um novo registo sendo adicionado à 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 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 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 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 precisa brochurePath``InsertParameter ser atualizado para que esse nome de arquivo seja gravado no banco de dados. Como vimos no tutorial Uploading Files, o arquivo pode ser salvo usando o método do SaveAs(path) controle FileUpload. Para atualizar o parâmetro brochurePath do ObjectDataSource, 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 registro, precisamos atribuir o conteúdo binário carregado ao parâmetro ObjectDataSource s no evento DetailsView picture. 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
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 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 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: Corrigindo aDisplayCategoryPicture.aspxpágina
Vamos dedicar um momento para testar a interface de inserção e o manipulador de eventos ItemInserting que foi criado nas últimas etapas. Visite a página UploadInDetailsView.aspx num navegador e tente adicionar uma categoria, mas omita a imagem ou especifique uma imagem que não seja JPG ou uma brochura que não seja PDF. Em qualquer um desses casos, uma mensagem de erro será exibida e o fluxo de trabalho de inserção será cancelado.
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 terá um novo envio e será adicionado um novo registo à 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.
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 é retirado do conteúdo binário da coluna Picture antes de ser devolvido 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, use o seguinte código no DisplayCategoryPicture.aspx para remover os cabeçalhos OLE apenas das 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.
Figura 11: As imagens JPG para novas categorias são renderizadas corretamente (Clique para visualizar a imagem em tamanho real)
Etapa 9: Excluir o folheto em 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 ao fazer uma 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, 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() de ObjectDataSource é invocado, que chama o método CategoriesBLL da classe InsertWithPicture, que 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 da brochura carregado armazenado no sistema de ficheiros 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 em uma página ASP.NET, quando uma exceção é lançada de dentro das profundezas 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 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á 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 carregamento 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.