Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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 da 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 Picture novo registro, enquanto o folheto será salvo ~/Brochures na pasta com o caminho para o arquivo salvo na coluna do BrochurePath novo registro.
Antes de criar essa nova página da Web, precisaremos atualizar a arquitetura. A CategoriesTableAdapter consulta principal não recupera a Picture coluna. Consequentemente, o método gerado automaticamente Insert tem apenas entradas para os campos CategoryName, Description e BrochurePath. Portanto, precisamos criar um método adicional no TableAdapter que solicite todos os quatro Categories campos. A CategoriesBLL classe na Camada lógica de negócios também precisará ser atualizada.
Etapa 1: adicionando umInsertWithPicturemétodo aoCategoriesTableAdapter
Quando criamos o CategoriesTableAdapter no tutorial Criando uma Camada de Acesso a Dados, nós o configuramos para gerar instruções de INSERT, UPDATE e DELETE automaticamente, com base na consulta principal. Além disso, instruímos o TableAdapter a empregar a abordagem do BD Direto, que criou os métodos Insert, Update e Delete. Esses métodos executam as instruções auto-geradas INSERT, UPDATE e DELETE, e, consequentemente, aceitam parâmetros de entrada com base nas colunas retornadas pela consulta principal. No tutorial Carregar Arquivos , aumentamos a CategoriesTableAdapter consulta principal para usar a BrochurePath coluna.
Como a CategoriesTableAdapter consulta principal não faz referência à Picture coluna, não podemos adicionar um novo registro nem atualizar um registro existente com um valor para a Picture coluna. Para capturar essas informações, podemos criar um novo método no TableAdapter que é usado especificamente para inserir um registro com dados binários ou podemos personalizar a instrução gerada INSERT automaticamente. O problema com a personalização da instrução gerada INSERT automaticamente é que corremos o risco de ter nossas personalizações substituídas pelo assistente. Por exemplo, imagine que personalizamos a INSERT instrução para incluir o uso da Picture coluna. 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. Em seguida, poderíamos criar um método na Camada 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 do TableAdapter. Assim que o assistente for terminado, nossas personalizações para a instrução INSERT serão sobrescritas, o método Insert voltará à sua forma antiga e nosso código não será 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 dor de cabeça potencial, em vez de personalizar as instruções SQL geradas automaticamente, vamos criar um novo método para o TableAdapter. Esse método, nomeado InsertWithPicture, aceitará valores para o CategoryName, Descriptione BrochurePathcolunas e Picture executará uma instrução INSERT que armazena todos os quatro valores em um novo registro.
Abra o Typed DataSet e, no Designer, clique com o botão direito do mouse 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 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 instrução INSERT SQL. O assistente sugere automaticamente uma instrução INSERT correspondente à consulta principal do TableAdapter. Nesse caso, é uma instrução INSERT que insere os valores CategoryName, 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 e clique em InsertWithPicture Concluir.
Figura 2: Nomeie o novo método InsertWithPicture TableAdapter (clique para exibir a imagem em tamanho real)
Etapa 2: Atualizando a camada lógica de negócios
Como a Camada de Apresentação só deve fazer interface com a Camada 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 nomeada 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 Tipado, se você não salvar suas alterações no Conjunto de Dados Digitado, 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 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 a exibição de origem e copie a sintaxe declarativa do GridView e ObjectDataSource, colando-a no elemento <asp:Content> dentro de UploadInDetailsView.aspx. Além disso, não se esqueça de copiar o método GenerateBrochureLink da classe code-behind de DisplayOrDownload.aspx para UploadInDetailsView.aspx.
Figura 3: Copiar e colar a sintaxe declarativa de DisplayOrDownload.aspx até UploadInDetailsView.aspx (Clique para exibir a imagem em tamanho real)
Depois de copiar para a página GenerateBrochureLink a sintaxe declarativa e o método UploadInDetailsView.aspx, exiba a página por meio de um navegador para garantir que tudo foi 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 suportar 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 método Insert para um método em seu objeto subjacente, CategoriesBLL. Em específico, queremos mapear isso para o método CategoriesBLL que reintroduzimos na Etapa 2. InsertWithPicture
Comece clicando 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. Mova para a guia INSERIR e escolha o método InsertWithPicture na lista suspensa. Clique em "Finalizar" para completar 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 dos controles 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 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 na visão geral de inserir, atualizar e excluir 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 dele atualizará e exibirá automaticamente a nova categoria.
Comece arrastando um DetailsView da Caixa de Ferramentas para o Designer acima do GridView, definindo sua ID propriedade como NewCategory e limpando os valores das propriedades Height e Width. Na marca inteligente do DetailsView, associe-a ao existente CategoriesDataSource e marque a caixa de seleção Habilitar Inserção.
Figura 6: Vincular o DetailsView a CategoriesDataSource e habilitar a inserção (Clique para ver 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, DescriptionNumberOfProductse BrochurePath embora o CategoryID BoundField não seja renderizado na interface de inserção porque sua InsertVisible propriedade está definida como false. Esses BoundFields existem porque são as colunas retornadas pelo GetCategories() método, que é o que o ObjectDataSource invoca para recuperar seus dados. Para inserir, no entanto, 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 propriedades de HeaderText e CategoryName BoundFields para categoria e brochura, 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 esteja entre TemplateField BrochurePath e CommandField.
Figura 7: Associar o DetailsView à e habilitar a CategoriesDataSource inserção
Se você converteu o BrochurePath BoundField em um TemplateField por meio da caixa de diálogo Editar Campos, o TemplateField inclui um ItemTemplate, EditItemTemplatee InsertItemTemplate. No entanto, somente o InsertItemTemplate é necessário, portanto, fique à vontade para remover os outros dois modelos. Neste ponto, a sintaxe declarativa 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 de folheto e imagem
Atualmente, o BrochurePath TemplateField InsertItemTemplate contém um TextBox, enquanto o Picture TemplateField não contém nenhum template. Precisamos atualizar esses dois TemplateField s InsertItemTemplate para usar controles FileUpload.
Na tag inteligente do DetailsView, escolha a opção Editar Modelos e selecione o campo BrochurePath TemplateField da lista suspensa. Remova a Caixa de Texto e arraste um controle FileUpload da Caixa de Ferramentas para o modelo. Defina o controle FileUpload como IDBrochureUpload. Da mesma forma, adicione um controle FileUpload ao Picture TemplateField s InsertItemTemplate. Defina esse controle FileUpload como IDPictureUpload.
Figura 8: Adicionar um controle FileUpload à 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 esquema Categories para incluir uma coluna que capture o tipo de arquivo, de forma que esse tipo possa ser enviado ao cliente por meio de 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 o formato de arquivo JPG é mais apropriado para imagens disponibilizadas na 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 de rótulo abaixo do DetailsView. Defina a propriedade ID como UploadWarning, limpe sua propriedade Text, defina a propriedade CssClass como Aviso, e as propriedades Visible e EnableViewState como false. A Warning classe CSS é definida Styles.css e renderiza o texto em uma fonte grande, vermelha, em itálico e em negrito.
Observação
Idealmente, os BoundFields CategoryName e Description seriam convertidos em TemplateFields, com suas interfaces de entrada 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 a Personalização da Interface de Modificação de Dados para uma análise 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 é iniciado. Primeiro, o evento DetailsView é ItemInserting acionado. Em seguida, o método Insert() do ObjectDataSource é invocado, o que resulta em um novo registro sendo adicionado à tabela Categories. Depois disso, o eventoItemInserted DetailsView é acionado.
Antes que o método ObjectDataSource Insert() seja 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 ItemInserting e adicione o seguinte código:
// Reference the FileUpload control
FileUpload BrochureUpload =
(FileUpload)NewCategory.FindControl("BrochureUpload");
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension
(BrochureUpload.FileName), ".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
O manipulador de eventos começa fazendo referência ao BrochureUpload controle FileUpload dos modelos 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
Depender da 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 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. Tais abordagens completas, porém, são muitas vezes um exagero; verificar a extensão é suficiente para a maioria dos cenários.
Conforme discutido no tutorial Carregar 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 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 já existe um arquivo com o nome do arquivo especificado. Nesse caso, ele continua a tentar novos nomes de arquivo para o folheto até que nenhum conflito seja encontrado.
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory,
fileNameWithoutExtension, "-", iteration, ".pdf");
iteration++;
}
Depois que um nome de arquivo válido for encontrado, o arquivo precisará ser salvo no sistema de arquivos e o valor do brochurePath``InsertParameter ObjectDataSource precisa ser atualizado para que esse nome de arquivo seja gravado no banco de dados. Como vimos no tutorial Carregar Arquivos , o arquivo pode ser salvo usando o método de SaveAs(path) controle FileUpload. Para atualizar o parâmetro brochurePath do ObjectDataSource, use 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 picture no evento DetailsView ItemInserting . Antes de fazermos essa atribuição, no entanto, precisamos primeiro verificar se a imagem carregada é um JPG e não algum outro tipo de imagem. Assim como na Etapa 6, vamos usar a extensão de arquivo da 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 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 completar, aqui está o ItemInserting manipulador de eventos por completo.
protected void NewCategory_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
else
{
// No picture uploaded!
UploadWarning.Text =
"You must provide a picture for the new category.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;
// Reference the FileUpload controls
FileUpload BrochureUpload =
(FileUpload)NewCategory.FindControl("BrochureUpload");
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName),
".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension,
"-", iteration, ".pdf");
iteration++;
}
// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
e.Values["brochurePath"] = brochurePath;
}
}
Etapa 8: Corrigindo aDisplayCategoryPicture.aspxpágina
Vamos 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 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. Após clicar no botão Inserir, a página fará um postback e um novo registro será adicionado à tabela Categories 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 conteúdo binário da coluna Picture antes de ser enviado 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 sejam JPGs. Por enquanto, porém, use o seguinte código 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 a imagem em tamanho real)
Etapa 9: Excluindo o folheto em caso de 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 Insert() do ObjectDataSource é invocado, que chama o método CategoriesBLL da classe InsertWithPicture, que chama o método CategoriesTableAdapter do 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 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 Manejo de Exceções BLL e DAL-Level em uma Página ASP.NET, quando uma exceção é lançada 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 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, em caso afirmativo, exclui o arquivo especificado pelo parâmetro ObjectDataSource brochurePath :
protected void NewCategory_ItemInserted
(object sender, DetailsViewInsertedEventArgs e)
{
if (e.Exception != null)
{
// Need to delete brochure file, if it exists
if (e.Values["brochurePath"] != null)
System.IO.File.Delete(Server.MapPath(
e.Values["brochurePath"].ToString()));
}
}
Resumo
Há 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ário. Os dados carregados podem então ser salvos no sistema de arquivos do servidor Web ou atribuídos a um parâmetro de fonte de dados no manipulador de eventos DetailsView ItemInserting .
Salvar dados binários no sistema de arquivos requer mais planejamento do que salvar dados diretamente no banco de dados. Um esquema de nomenclatura deve ser escolhido para evitar que o upload de um usuário sobrescreva 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 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.
Divirta-se programando!
Sobre o autor
Scott Mitchell, autor de sete livros 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 alcançado em mitchell@4GuysFromRolla.com.
Agradecimentos Especiais 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 revisar meus próximos artigos do MSDN? Se assim for, deixe-me uma linha em mitchell@4GuysFromRolla.com.