Incluir uma opção de upload de arquivo ao adicionar um novo registro (C#)
por Scott Mitchell
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 CategoryName
campos , Description
e 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 umInsertWithPicture
método aoCategoriesTableAdapter
Quando criamos o back no tutorial Criando uma camada de acesso a dados, configuramos-o CategoriesTableAdapter
para gerar INSERT
automaticamente instruções , UPDATE
e 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
, Update
e Delete
. Esses métodos executam as instruções , UPDATE
e geradas automaticamente eDELETE
, consequentemente, aceitam parâmetros de entrada com base nas colunas retornadas INSERT
pela 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 CategoryName
colunas , Description
, BrochurePath
e 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.
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 CategoryName
valores , Description
e 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.
Figura 2: nomeie o novo método InsertWithPicture
TableAdapter (clique para exibir a imagem em tamanho real)
Etapa 2: Atualizando a camada de lógica de negócios
Como a Camada de Apresentação deve interface apenas com a Camada de Lógica de Negócios em vez de ignorá-la para ir diretamente para a Camada de Acesso a Dados, precisamos criar um método BLL que invoque o método DAL que acabamos de criar (InsertWithPicture
). Para este tutorial, crie um método na CategoriesBLL
classe chamada InsertWithPicture
que aceita como entrada três string
s e uma byte
matriz. Os string
parâmetros de entrada são para o nome, a descrição e o caminho do arquivo de folheto da categoria, enquanto a byte
matriz é para o conteúdo binário da imagem da categoria. Como mostra o código a seguir, esse método BLL invoca o método DAL correspondente:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Insert, false)]
public void InsertWithPicture(string categoryName, string description,
string brochurePath, byte[] picture)
{
Adapter.InsertWithPicture(categoryName, description, brochurePath, picture);
}
Observação
Verifique se você salvou o Conjunto de Dados Digitado antes de adicionar o InsertWithPicture
método à BLL. Como o CategoriesTableAdapter
código de classe é gerado automaticamente com base no Conjunto de Dados Digitado, se você não salvar as alterações no Conjunto de Dados Digitado, a Adapter
propriedade não saberá sobre o InsertWithPicture
método .
Etapa 3: Listar as categorias existentes e seus dados binários
Neste tutorial, criaremos uma página que permite que um usuário final adicione uma nova categoria ao sistema, fornecendo uma imagem e um folheto para a nova categoria. No tutorial anterior , usamos um GridView com um TemplateField e ImageField para exibir o nome, a descrição, a imagem e um link de cada categoria para baixar o folheto. Vamos replicar essa funcionalidade para este tutorial, criando uma página que lista todas as categorias existentes e permite que novas sejam criadas.
Comece abrindo a DisplayOrDownload.aspx
página da BinaryData
pasta. Vá para o modo de exibição Source e copie a sintaxe declarativa de GridView e ObjectDataSource, colando-a dentro do <asp:Content>
elemento em UploadInDetailsView.aspx
. Além disso, não se esqueça de copiar o GenerateBrochureLink
método da classe code-behind de DisplayOrDownload.aspx
para UploadInDetailsView.aspx
.
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.
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 oCategoriesDataSource
para 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.
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.
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
, NumberOfProducts
e BrochurePath
embora o CategoryID
BoundField não seja renderizado na interface de inserção porque sua InsertVisible
propriedade está definida false
como . Esses BoundFields existem porque são as colunas retornadas pelo GetCategories()
método , que é o que o ObjectDataSource invoca para recuperar seus dados. No entanto, para inserir, não queremos permitir que o usuário especifique um valor para NumberOfProducts
. Além disso, precisamos permitir que eles carreguem uma imagem para a nova categoria, bem como carreguem um PDF para o folheto.
Remova o NumberOfProducts
BoundField do DetailsView completamente e atualize as HeaderText
propriedades de CategoryName
e BrochurePath
BoundFields para Category e Brochure, respectivamente. Em seguida, converta o BrochurePath
BoundField em um TemplateField e adicione um novo TemplateField para a imagem, dando a este novo TemplateField um HeaderText
valor de Imagem. Mova o Picture
TemplateField para que ele fique entre TemplateField BrochurePath
e CommandField.
Figura 7: Associar o DetailsView ao CategoriesDataSource
e Habilitar a Inserção
Se você converteu o BrochurePath
BoundField em um TemplateField por meio da caixa de diálogo Editar Campos, o TemplateField inclui um ItemTemplate
, EditItemTemplate
e InsertItemTemplate
. No entanto, somente o InsertItemTemplate
é necessário, portanto, fique à vontade para remover os outros dois modelos. Neste ponto, a sintaxe declarativa de DetailsView deve ser semelhante à seguinte:
<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False"
DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource"
DefaultMode="Insert">
<Fields>
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
InsertVisible="False" ReadOnly="True"
SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
<InsertItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
</InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture"></asp:TemplateField>
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
Adicionando controles FileUpload para os campos folheto e imagem
Atualmente, o BrochurePath
TemplateField s InsertItemTemplate
contém um TextBox, enquanto o Picture
TemplateField não contém nenhum modelo. Precisamos atualizar esses dois TemplateField s InsertItemTemplate
para usar controles FileUpload.
Na marca inteligente DetailsView, escolha a opção Editar Modelos e selecione TemplateField BrochurePath
s InsertItemTemplate
na lista suspensa. Remova o TextBox e arraste um controle FileUpload da Caixa de Ferramentas para o modelo. Defina o controle FileUpload como ID
BrochureUpload
. Da mesma forma, adicione um controle FileUpload ao Picture
TemplateField s InsertItemTemplate
. Defina esse controle FileUpload como ID
PictureUpload
.
Figura 8: Adicionar um controle FileUpload ao InsertItemTemplate
(Clique para exibir a imagem em tamanho real)
Depois de fazer essas adições, a sintaxe declarativa de dois TemplateField será:
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
<InsertItemTemplate>
<asp:FileUpload ID="BrochureUpload" runat="server" />
</InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
<InsertItemTemplate>
<asp:FileUpload ID="PictureUpload" runat="server" />
</InsertItemTemplate>
</asp:TemplateField>
Quando um usuário adiciona uma nova categoria, queremos garantir que o folheto e a imagem sejam do tipo de arquivo correto. Para o folheto, o usuário deve fornecer um PDF. Para a imagem, precisamos que o usuário carregue um arquivo de imagem, mas permitimos qualquer arquivo de imagem ou apenas arquivos de imagem de um tipo específico, como GIFs ou JPGs? Para permitir tipos de arquivo diferentes, precisamos estender o Categories
esquema para incluir uma coluna que captura o tipo de arquivo para que esse tipo possa ser enviado ao cliente por meio Response.ContentType
do em DisplayCategoryPicture.aspx
. Como não temos essa coluna, seria prudente restringir os usuários a fornecer apenas um tipo de arquivo de imagem específico. As Categories
imagens existentes da tabela são bitmaps, mas os JPGs são um formato de arquivo mais apropriado para imagens atendidas pela Web.
Se um usuário carregar um tipo de arquivo incorreto, precisaremos cancelar a inserção e exibir uma mensagem indicando o problema. Adicione um controle Web rótulo abaixo de DetailsView. Defina sua ID
propriedade como UploadWarning
, desmarque sua Text
propriedade, defina a CssClass
propriedade como Warning e as Visible
propriedades e EnableViewState
como false
. A Warning
classe CSS é definida em Styles.css
e renderiza o texto em uma fonte grande, vermelha, itállica e em negrito.
Observação
O ideal é que o CategoryName
e Description
BoundFields sejam convertidos em TemplateFields e suas interfaces de inserção personalizadas. A Description
interface de inserção, por exemplo, provavelmente seria mais adequada por meio de uma caixa de texto de várias linhas. E como a CategoryName
coluna não aceita NULL
valores, um RequiredFieldValidator deve ser adicionado para garantir que o usuário forneça um valor para o nome da nova categoria. Essas etapas são deixadas como um exercício para o leitor. Consulte Personalizando a Interface de Modificação de Dados para obter uma visão detalhada sobre como aumentar as interfaces de modificação de dados.
Etapa 6: salvar o folheto carregado no sistema de arquivos do servidor Web
Quando o usuário insere os valores de uma nova categoria e clica no botão Inserir, ocorre um postback e o fluxo de trabalho de inserção se desenrola. Primeiro, o evento DetailsView éItemInserting
acionado. Em seguida, o método ObjectDataSource é Insert()
invocado, o que resulta em um novo registro sendo adicionado à Categories
tabela. Depois disso, o evento DetailsView éItemInserted
acionado.
Antes que o método ObjectDataSource Insert()
seja invocado, devemos primeiro garantir que os tipos de arquivo apropriados foram carregados pelo usuário e, em seguida, salvar o PDF do folheto no sistema de arquivos do servidor Web. Crie um manipulador de eventos para o evento DetailsView ItemInserting
e adicione o seguinte código:
// Reference the FileUpload control
FileUpload BrochureUpload =
(FileUpload)NewCategory.FindControl("BrochureUpload");
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension
(BrochureUpload.FileName), ".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
O manipulador de eventos começa referenciando o BrochureUpload
controle FileUpload dos modelos do DetailsView. Em seguida, se um folheto tiver sido carregado, a extensão do arquivo carregado será examinada. Se a extensão não for .PDF, um aviso será exibido, a inserção será cancelada e a execução do manipulador de eventos terminará.
Observação
Confiar na extensão do arquivo carregado não é uma técnica segura para garantir que o arquivo carregado seja um documento PDF. O usuário pode ter um documento PDF válido com a extensão .Brochure
ou ter obtido um documento não PDF e dado a ele uma .pdf
extensão. O conteúdo binário do arquivo precisaria ser examinado programaticamente para verificar mais conclusivamente o tipo de arquivo. Essas abordagens completas, porém, geralmente são um exagero; verificar a extensão é suficiente para a maioria dos cenários.
Conforme discutido no tutorial Carregando Arquivos , é necessário ter cuidado ao salvar arquivos no sistema de arquivos para que o upload de um usuário não substitua outros s. Para este tutorial, tentaremos usar o mesmo nome que o arquivo carregado. Se já existir um arquivo no diretório com o ~/Brochures
mesmo nome de arquivo, no entanto, acrescentaremos um número no final até que um nome exclusivo seja encontrado. Por exemplo, se o usuário carregar um arquivo de folheto chamado Meats.pdf
, mas já houver um arquivo chamado Meats.pdf
na ~/Brochures
pasta, alteraremos o nome do arquivo salvo para Meats-1.pdf
. Se isso existir, tentaremos Meats-2.pdf
, e assim por diante, até que um nome de arquivo exclusivo seja encontrado.
O código a seguir usa o File.Exists(path)
método para determinar se um arquivo já existe com o nome de arquivo especificado. Nesse caso, ele continuará a tentar novos nomes de arquivo para o folheto até que nenhum conflito seja encontrado.
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory,
fileNameWithoutExtension, "-", iteration, ".pdf");
iteration++;
}
Depois que um nome de arquivo válido for encontrado, o arquivo precisará ser salvo no sistema de arquivos e o valor do brochurePath``InsertParameter
ObjectDataSource precisa ser atualizado para que esse nome de arquivo seja gravado no banco de dados. Como vimos no tutorial Carregando Arquivos , o arquivo pode ser salvo usando o método de SaveAs(path)
controle FileUpload. Para atualizar o parâmetro ObjectDataSource, brochurePath
use a e.Values
coleção .
// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
e.Values["brochurePath"] = brochurePath;
Etapa 7: salvar a imagem carregada no banco de dados
Para armazenar a imagem carregada no novo Categories
registro, precisamos atribuir o conteúdo binário carregado ao parâmetro ObjectDataSource no picture
evento DetailsView ItemInserting
. Antes de fazer essa atribuição, no entanto, precisamos primeiro garantir que a imagem carregada seja um JPG e não algum outro tipo de imagem. Assim como na Etapa 6, vamos usar a extensão de arquivo de imagem carregada para verificar seu tipo.
Embora a Categories
tabela permita NULL
valores para a Picture
coluna, todas as categorias atualmente têm uma imagem. Vamos forçar o usuário a fornecer uma imagem ao adicionar uma nova categoria por meio desta página. O código a seguir verifica se uma imagem foi carregada e se ela tem uma extensão apropriada.
// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
else
{
// No picture uploaded!
UploadWarning.Text =
"You must provide a picture for the new category.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
Esse código deve ser colocado antes do código da Etapa 6 para que, se houver um problema com o upload de imagem, o manipulador de eventos seja encerrado antes que o arquivo de folheto seja salvo no sistema de arquivos.
Supondo que um arquivo apropriado tenha sido carregado, atribua o conteúdo binário carregado ao valor do parâmetro de imagem com a seguinte linha de código:
// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;
O manipulador de eventos completoItemInserting
Para concluir, aqui está o ItemInserting
manipulador de eventos em sua totalidade:
protected void NewCategory_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
else
{
// No picture uploaded!
UploadWarning.Text =
"You must provide a picture for the new category.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;
// Reference the FileUpload controls
FileUpload BrochureUpload =
(FileUpload)NewCategory.FindControl("BrochureUpload");
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName),
".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension,
"-", iteration, ".pdf");
iteration++;
}
// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
e.Values["brochurePath"] = brochurePath;
}
}
Etapa 8: Corrigir aDisplayCategoryPicture.aspx
página
Vamos fazer um momento para testar a interface de inserção e ItemInserting
o manipulador de eventos que foi criado nas últimas etapas. Visite a UploadInDetailsView.aspx
página por meio de um navegador e tente adicionar uma categoria, mas omita a imagem ou especifique uma imagem não JPG ou um folheto não PDF. Em qualquer um desses casos, uma mensagem de erro será exibida e o fluxo de trabalho de inserção será cancelado.
Figura 9: Uma mensagem de aviso será exibida se um tipo de arquivo inválido for carregado (clique para exibir a imagem em tamanho real)
Depois de verificar se a página requer que uma imagem seja carregada e não aceite arquivos não PDF ou não JPG, adicione uma nova categoria com uma imagem JPG válida, deixando o campo Brochure vazio. Depois de clicar no botão Inserir, a página fará o postback e um novo registro será adicionado à Categories
tabela com o conteúdo binário da imagem carregada armazenado diretamente no banco de dados. O GridView é atualizado e mostra uma linha para a categoria recém-adicionada, mas, como mostra a Figura 10, a nova imagem da categoria não é renderizada corretamente.
Figura 10: A Imagem da Nova Categoria não é Exibida (Clique para exibir a imagem em tamanho real)
O motivo pelo qual a nova imagem não é exibida é porque a DisplayCategoryPicture.aspx
página que retorna uma imagem de categoria especificada está configurada para processar bitmaps que têm um cabeçalho OLE. Esse cabeçalho de 78 bytes é retirado do Picture
conteúdo binário da coluna antes de serem enviados de volta para o cliente. Mas o arquivo JPG que acabamos de carregar para a nova categoria não tem esse cabeçalho OLE; portanto, bytes válidos e necessários estão sendo removidos dos dados binários da imagem.
Como agora há bitmaps com cabeçalhos OLE e JPGs na Categories
tabela, precisamos atualizar DisplayCategoryPicture.aspx
para que ele faça a remoção de cabeçalho OLE para as oito categorias originais e ignore essa remoção para os registros de categoria mais recentes. Em nosso próximo tutorial, examinaremos como atualizar uma imagem de registro existente e atualizaremos todas as imagens de categoria antigas para que elas sejam JPGs. Por enquanto, porém, use o seguinte código em DisplayCategoryPicture.aspx
para remover os cabeçalhos OLE somente para essas oito categorias originais:
protected void Page_Load(object sender, EventArgs e)
{
int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
// Get information about the specified category
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (categoryID <= 8)
{
// For older categories, we must strip the OLE header... images are bitmaps
// Output HTTP headers providing information about the binary data
Response.ContentType = "image/bmp";
// Output the binary data
// But first we need to strip out the OLE header
const int OleHeaderLength = 78;
int strippedImageLength = category.Picture.Length - OleHeaderLength;
byte[] strippedImageData = new byte[strippedImageLength];
Array.Copy(category.Picture, OleHeaderLength, strippedImageData,
0, strippedImageLength);
Response.BinaryWrite(strippedImageData);
}
else
{
// For new categories, images are JPGs...
// Output HTTP headers providing information about the binary data
Response.ContentType = "image/jpeg";
// Output the binary data
Response.BinaryWrite(category.Picture);
}
}
Com essa alteração, a imagem JPG agora é renderizada corretamente no GridView.
Figura 11: As imagens JPG para novas categorias são renderizadas corretamente (clique para exibir imagem em tamanho real)
Etapa 9: excluindo o folheto na face de uma exceção
Um dos desafios de armazenar dados binários no sistema de arquivos do servidor Web é que ele introduz uma desconexão entre o modelo de dados e seus dados binários. Portanto, sempre que um registro é excluído, os dados binários correspondentes no sistema de arquivos também devem ser removidos. Isso também pode entrar em jogo durante a inserção. Considere o seguinte cenário: um usuário adiciona uma nova categoria, especificando uma imagem e um folheto válidos. Ao clicar no botão Inserir, ocorrerá um postback e o evento DetailsView ItemInserting
será acionado, salvando o folheto no sistema de arquivos do servidor Web. Em seguida, o método ObjectDataSource é Insert()
invocado, que chama o CategoriesBLL
método de classe s InsertWithPicture
, que chama o CategoriesTableAdapter
método s InsertWithPicture
.
Agora, o que acontece se o banco de dados estiver offline ou se houver um erro na instrução INSERT
SQL? Claramente, insert falhará, portanto, nenhuma nova linha de categoria será adicionada ao banco de dados. Mas ainda temos o arquivo de folheto carregado no sistema de arquivos do servidor Web! Esse arquivo precisa ser excluído diante de uma exceção durante o fluxo de trabalho de inserção.
Conforme discutido anteriormente no tutorial Manipulando exceções de BLL e DAL-Level em uma página de ASP.NET , quando uma exceção é gerada de dentro das profundidades da arquitetura, ela é gerada através das várias camadas. Na Camada de Apresentação, podemos determinar se ocorreu uma exceção do evento DetailsView.ItemInserted
Esse manipulador de eventos também fornece os valores de ObjectDataSource s InsertParameters
. Portanto, podemos criar um manipulador de eventos para o ItemInserted
evento que verifica se houve uma exceção e, nesse caso, exclui o arquivo especificado pelo parâmetro ObjectDataSource:brochurePath
protected void NewCategory_ItemInserted
(object sender, DetailsViewInsertedEventArgs e)
{
if (e.Exception != null)
{
// Need to delete brochure file, if it exists
if (e.Values["brochurePath"] != null)
System.IO.File.Delete(Server.MapPath(
e.Values["brochurePath"].ToString()));
}
}
Resumo
Há várias etapas que devem ser executadas para fornecer uma interface baseada na Web para adicionar registros que incluem dados binários. Se os dados binários estiverem sendo armazenados diretamente no banco de dados, provavelmente você precisará atualizar a arquitetura, adicionando métodos específicos para lidar com o caso em que os dados binários estão sendo inseridos. Depois que a arquitetura tiver sido atualizada, a próxima etapa será criar a interface de inserção, que pode ser realizada usando um DetailsView que foi personalizado para incluir um controle FileUpload para cada campo de dados binários. Os dados carregados podem ser salvos no sistema de arquivos do servidor Web ou atribuídos a um parâmetro de fonte de dados no manipulador de eventos DetailsView.ItemInserting
Salvar dados binários no sistema de arquivos requer mais planejamento do que salvar dados diretamente no banco de dados. Um esquema de nomenclatura deve ser escolhido para evitar que um usuário carregue substituindo outros s. Além disso, etapas adicionais devem ser executadas para excluir o arquivo carregado se a inserção do banco de dados falhar.
Agora temos a capacidade de adicionar novas categorias ao sistema com um folheto e uma imagem, mas ainda não vimos como atualizar os dados binários de uma categoria existente ou como remover corretamente os dados binários de uma categoria excluída. Exploraremos esses dois tópicos no próximo tutorial.
Programação feliz!
Sobre o autor
Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.
Agradecimentos Especiais
Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Dave Gardner, Teresa Murphy e Berne Leigh. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.