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
Em tutoriais anteriores, vimos como o controle GridView torna simples editar e excluir dados de texto. Neste tutorial, vemos como o controle GridView também possibilita editar e excluir dados binários, se esses dados binários são salvos no banco de dados ou armazenados no sistema de arquivos.
Introdução
Nos últimos três tutoriais, adicionamos um pouco de funcionalidade para trabalhar com dados binários. Começamos adicionando uma BrochurePath coluna à Categories tabela e atualizamos a arquitetura adequadamente. Também adicionamos métodos camada de acesso a dados e camada lógica de negócios para trabalhar com a coluna existente Picture da tabela Categorias, que contém o conteúdo binário de um arquivo de imagem. Criamos páginas da Web para apresentar os dados binários em um GridView e um link para download do folheto, com a imagem da categoria mostrada em um elemento <img>, e adicionamos um DetailsView para permitir que os usuários adicionem uma nova categoria e façam upload de seus dados de folheto e imagem.
Tudo o que resta a ser implementado é a capacidade de editar e excluir categorias existentes, o que realizaremos neste tutorial usando os recursos internos de edição e exclusão do GridView. Ao editar uma categoria, o usuário poderá, opcionalmente, carregar uma nova imagem ou fazer com que a categoria continue a usar a existente. Para o folheto, eles podem optar por usar o folheto existente, carregar um novo folheto ou indicar que a categoria não tem mais um folheto associado a ele. Vamos começar!
Etapa 1: Atualizando a camada de acesso a dados
O DAL gerou automaticamente Insert, Update e Delete métodos, mas esses métodos foram gerados com base na consulta principal CategoriesTableAdapter, que não inclui a coluna Picture. Portanto, os métodos Insert e Update não incluem parâmetros para especificar os dados binários para a imagem da categoria. Como fizemos no tutorial anterior, precisamos criar um novo método TableAdapter para atualizar a Categories tabela ao especificar dados binários.
Abra o Conjunto de Dados Tipado e, no Designer, clique com o botão direito do mouse no cabeçalho do CategoriesTableAdapter e escolha Adicionar Consulta no menu de contexto para iniciar o Assistente de Configuração de Consulta TableAdapter. Esse assistente 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 ATUALIZAR e clique em Avançar.
Figura 1: Selecione a opção UPDATE (Clique para exibir a imagem em tamanho real)
Agora precisamos especificar a instrução UPDATE SQL. O assistente sugere automaticamente uma instrução UPDATE correspondente à consulta principal do TableAdapter (uma que atualiza os valores de CategoryName, Description e BrochurePath). Altere a instrução para que a Picture coluna seja incluída junto com um @Picture parâmetro, da seguinte forma:
UPDATE [Categories] SET
[CategoryName] = @CategoryName,
[Description] = @Description,
[BrochurePath] = @BrochurePath ,
[Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))
A tela final do assistente nos pede para nomear o novo método TableAdapter. Insira e clique em UpdateWithPicture Concluir.
Figura 2: Nomeie o novo método UpdateWithPicture TableAdapter (clique para exibir a imagem em tamanho real)
Etapa 2: adicionando os métodos de camada de lógica de negócios
Além de atualizar o DAL, precisamos atualizar a BLL para incluir métodos para atualizar e excluir uma categoria. Estes são os métodos que serão invocados da Camada de Apresentação.
Para excluir uma categoria, podemos usar o CategoriesTableAdapter método gerado automaticamente Delete . Adicione o seguinte método à classe CategoriesBLL:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Delete, True)> _
Public Function DeleteCategory(ByVal categoryID As Integer) As Boolean
Dim rowsAffected As Integer = Adapter.Delete(categoryID)
' Return true if precisely one row was deleted, otherwise false
Return rowsAffected = 1
End Function
Para este tutorial, vamos criar dois métodos para atualizar uma categoria - um que espera os dados de imagem binária e invoca o método UpdateWithPicture que acabamos de adicionar ao CategoriesTableAdapter e outro que aceita apenas os valores CategoryName, Description e BrochurePath e usa a instrução CategoriesTableAdapter gerada automaticamente da classe Update. A lógica por trás do uso de dois métodos é que, em algumas circunstâncias, um usuário pode querer atualizar a imagem da categoria junto com seus outros campos, nesse caso, o usuário terá que carregar a nova imagem. Os dados binários da imagem carregada podem ser usados na instrução UPDATE . Em outros casos, o usuário pode estar interessado apenas em atualizar, digamos, o nome e a descrição. Mas se a UPDATE instrução espera os dados binários para a Picture coluna também, precisamos fornecer essas informações também. Isso exigiria uma viagem extra ao banco de dados para trazer de volta os dados de imagem para o registro que está sendo editado. Portanto, queremos dois UPDATE métodos. A Camada de Lógica de Negócios determinará qual deles usar com base na disponibilidade dos dados de imagem ao atualizar a categoria.
Para facilitar isso, adicione dois métodos à CategoriesBLL classe, ambos nomeados UpdateCategory. O primeiro deve aceitar três String s, uma Byte matriz e um Integer como parâmetros de entrada; o segundo, apenas três String s e um Integer. Os parâmetros de entrada String são para o nome, a descrição e o caminho do arquivo do folheto da categoria. A matriz Byte é para o conteúdo binário da imagem da categoria, e Integer identifica o CategoryID do registro a ser atualizado. Observe que a primeira sobrecarga invoca a segunda se a matriz fornecida Byte for Nothing:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Update, False)> _
Public Function UpdateCategory(categoryName As String, description As String, _
brochurePath As String, picture() As Byte, categoryID As Integer) As Boolean
' If no picture is specified, use other overload
If picture Is Nothing Then
Return UpdateCategory(categoryName, description, brochurePath, categoryID)
End If
' Update picture, as well
Dim rowsAffected As Integer = Adapter.UpdateWithPicture _
(categoryName, description, brochurePath, picture, categoryID)
' Return true if precisely one row was updated, otherwise false
Return rowsAffected = 1
End Function
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateCategory(categoryName As String, description As String, _
brochurePath As String, categoryID As Integer) As Boolean
Dim rowsAffected As Integer = Adapter.Update _
(categoryName, description, brochurePath, categoryID)
' Return true if precisely one row was updated, otherwise false
Return rowsAffected = 1
End Function
Etapa 3: Copiando a funcionalidade Inserir e Exibir
No tutorial anterior , criamos uma página chamada UploadInDetailsView.aspx que listava todas as categorias em um GridView e fornecemos um DetailsView para adicionar novas categorias ao sistema. Neste tutorial, estenderemos o GridView para incluir suporte à edição e exclusão de itens. Em vez de continuar trabalhando a partir do UploadInDetailsView.aspx, vamos então colocar as alterações deste tutorial na página UpdatingAndDeleting.aspx da mesma pasta, ~/BinaryData. Copie e cole a marcação declarativa e o código de UploadInDetailsView.aspx para UpdatingAndDeleting.aspx.
Comece abrindo a UploadInDetailsView.aspx página. Copie toda a sintaxe declarativa dentro do <asp:Content> elemento, conforme mostrado na Figura 3. Em seguida, abra UpdatingAndDeleting.aspx e cole essa marcação dentro de seu <asp:Content> elemento. Da mesma forma, copie o código da classe 'code-behind' da página UploadInDetailsView.aspx para UpdatingAndDeleting.aspx.
Figura 3: Copiar a Marcação Declarativa de UploadInDetailsView.aspx (Clique para exibir a imagem em tamanho real)
Depois de copiar a marcação declarativa e o código, visite UpdatingAndDeleting.aspx. Você deve ver a mesma saída e ter a mesma experiência de usuário que com a página UploadInDetailsView.aspx do tutorial anterior.
Etapa 4: Adicionando suporte de exclusão ao ObjectDataSource e GridView
Como discutimos novamente no tutorial Uma Visão Geral de Inserção, Atualização e Exclusão de Dados , o GridView fornece recursos internos de exclusão e esses recursos podem ser habilitados no tique de uma caixa de seleção se a fonte de dados subjacente da grade der suporte à exclusão. Atualmente, o ObjectDataSource ao qual o GridView está associado (CategoriesDataSource) não dá suporte à exclusão.
Para corrigir isso, clique na opção Configurar Fonte de Dados da etiqueta inteligente ObjectDataSource para iniciar o assistente. A primeira tela mostra que o ObjectDataSource está configurado para funcionar com a CategoriesBLL classe. Clique em Avançar. Atualmente, somente as propriedades InsertMethod e SelectMethod do ObjectDataSource são especificadas. No entanto, o assistente preencheu automaticamente as listas suspensas nas guias UPDATE e DELETE com os métodos UpdateCategory e DeleteCategory, respectivamente. Isso ocorre porque, na classe CategoriesBLL, marcamos esses métodos como os métodos padrão usando DataObjectMethodAttribute para atualizar e excluir.
Por enquanto, ajuste a lista suspensa da guia UPDATE para (Nenhum), mas mantenha a lista suspensa da guia DELETE configurada como DeleteCategory. Retornaremos a esse assistente na Etapa 6 para adicionar suporte de atualização.
Figura 4: Configurar o ObjectDataSource para usar o DeleteCategory 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.
O ObjectDataSource agora incluirá um valor para sua DeleteMethod propriedade, bem como um DeleteParameter. Lembre-se de que, ao usar o assistente para especificar os métodos, o Visual Studio define a propriedade ObjectDataSource OldValuesParameterFormatString como original_{0}, o que causa problemas com as invocações de método de atualização e exclusão. Portanto, desmarque completamente essa propriedade ou redefina-a para o padrão. {0} Se você precisar atualizar sua memória nesta propriedade ObjectDataSource, consulte o tutorial Uma visão geral de inserir, atualizar e excluir dados .
Depois de concluir o assistente e corrigir a OldValuesParameterFormatString, a marcação declarativa do ObjectDataSource deve ser semelhante ao formato seguinte:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory">
<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>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
Depois de configurar o ObjectDataSource, adicione recursos de exclusão ao GridView marcando a caixa de seleção Habilitar Exclusão do tag inteligente GridView. Isso adicionará um CommandField ao GridView cuja ShowDeleteButton propriedade está definida como True.
Figura 5: Habilitar o suporte para exclusão no GridView (clique para exibir a imagem em tamanho real)
Tire um momento para testar a funcionalidade de exclusão. Há uma chave estrangeira entre as tabelas Products e CategoryID, portanto, você receberá uma exceção de violação de restrição de chave estrangeira se tentar excluir qualquer uma das primeiras oito categorias. Para testar essa funcionalidade, adicione uma nova categoria, fornecendo um folheto e uma imagem. Minha categoria de teste, mostrada na Figura 6, inclui um arquivo de folheto de teste nomeado Test.pdf e uma imagem de teste. A Figura 7 mostra o GridView após a adição da categoria de teste.
Figura 6: Adicionar uma categoria de teste com um folheto e uma imagem (clique para exibir imagem em tamanho real)
Figura 7: Depois de inserir a categoria de teste, ela é exibida no GridView (clique para exibir a imagem em tamanho real)
No Visual Studio, atualize o Gerenciador de Soluções. Agora você deve ver um novo arquivo na pasta ~/Brochures, Test.pdf (consulte a Figura 8).
Em seguida, clique no link Excluir na linha Categoria de Teste, fazendo com que a página faça o postback e o CategoriesBLL método da DeleteCategory classe seja acionado. Isso invocará o método DAL Delete, que fará com que a instrução apropriada DELETE seja enviada ao banco de dados. Em seguida, os dados são novamente vinculados ao GridView e as marcações são enviadas de volta ao cliente, sem a presença da Categoria de Teste.
Embora o fluxo de trabalho de exclusão tenha removido com êxito o registro da Categoria de Teste da tabela Categories, ele não removeu seu arquivo de folheto do sistema de arquivos do servidor web. Atualize o Gerenciador de Soluções e você verá que Test.pdf ainda está localizado na pasta ~/Brochures.
Figura 8: O Test.pdf arquivo não foi excluído do sistema de arquivos do servidor Web
Etapa 5: Removendo o arquivo de folheto de categoria excluído
Uma das desvantagens de armazenar dados binários externos ao banco de dados é que etapas extras devem ser tomadas para limpar esses arquivos quando o registro de banco de dados associado é excluído. O GridView e o ObjectDataSource fornecem eventos que são disparados antes e depois que o comando de exclusão foi executado. Na verdade, precisamos criar manipuladores de eventos para os eventos pré e pós-ação. Antes que o Categories registro seja excluído, precisamos determinar o caminho do arquivo PDF, mas não queremos excluir o PDF antes que a categoria seja excluída caso haja alguma exceção e a categoria não seja excluída.
O evento RowDeleting GridView é acionado antes que o comando de deletar do ObjectDataSource seja invocado, enquanto o evento RowDeleted é acionado depois. Crie manipuladores de eventos para esses dois eventos usando o seguinte código:
' A page variable to "remember" the deleted category's BrochurePath value
Private deletedCategorysPdfPath As String = Nothing
Protected Sub Categories_RowDeleting(sender As Object, e As GridViewDeleteEventArgs) _
Handles Categories.RowDeleting
' Determine the PDF path for the category being deleted...
Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID"))
Dim categoryAPI As New CategoriesBLL()
Dim categoriesData As Northwind.CategoriesDataTable = _
categoryAPI.GetCategoryByCategoryID(categoryID)
Dim category As Northwind.CategoriesRow = categoriesData(0)
If category.IsBrochurePathNull() Then
deletedCategorysPdfPath = Nothing
Else
deletedCategorysPdfPath = category.BrochurePath
End If
End Sub
Protected Sub Categories_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
Handles Categories.RowDeleted
' Delete the brochure file if there were no problems deleting the record
If e.Exception Is Nothing Then
DeleteRememberedBrochurePath()
End If
End Sub
No RowDeleting manipulador de eventos, o CategoryID da linha que está sendo excluída é obtido da coleção do GridView s DataKeys, que pode ser acessada nesse manipulador de eventos pela coleção e.Keys. Em seguida, a CategoriesBLL classe GetCategoryByCategoryID(categoryID) é invocada para retornar informações sobre o registro que está sendo excluído. Se o objeto retornado CategoriesDataRow tiver um valor diferenteNULL``BrochurePath , ele será armazenado na variável deletedCategorysPdfPath de página para que o arquivo possa ser excluído no RowDeleted manipulador de eventos.
Observação
Em vez de recuperar os detalhes BrochurePath do registro Categories que está sendo excluído no manipulador de eventos RowDeleting, poderíamos ter adicionado o BrochurePath à propriedade DataKeyNames do GridView e acessado o valor do registro através da coleção e.Keys. Isso aumentaria ligeiramente o tamanho do estado de exibição do GridView, mas reduziria a quantidade de código necessária e salvaria uma viagem ao banco de dados.
Depois que o comando de exclusão subjacente do ObjectDataSource tiver sido invocado, o manipulador de eventos do RowDeleted GridView será acionado. Se não houver exceções na exclusão dos dados e houver um valor para deletedCategorysPdfPath, o PDF será excluído do sistema de arquivos. Observe que esse código extra não é necessário para limpar os dados binários da categoria associados à sua imagem. Isso ocorre porque os dados de imagem são armazenados diretamente no banco de dados, portanto, excluir a Categories linha também exclui os dados de imagem dessa categoria.
Depois de adicionar os dois manipuladores de eventos, execute este caso de teste novamente. Ao excluir a categoria, seu PDF associado também é excluído.
A atualização de dados binários associados a um registro existente fornece alguns desafios interessantes. O restante deste tutorial se aprofunda na adição de recursos de atualização ao folheto e à imagem. A etapa 6 explora técnicas para atualizar as informações do folheto enquanto a Etapa 7 analisa a atualização da imagem.
Etapa 6: Atualizando um folheto de categoria
Conforme discutido no tutorial Uma Visão Geral de Inserir, Atualizar e Excluir Dados, o GridView oferece suporte interno de edição em nível de linha que pode ser implementado pelo tique de uma caixa de seleção se sua fonte de dados subjacente estiver configurada adequadamente. Atualmente, o CategoriesDataSource ObjectDataSource ainda não está configurado para incluir o suporte de atualização, portanto, vamos adicioná-lo.
Clique no link Configurar Fonte de Dados do assistente ObjectDataSource e prossiga para a segunda etapa. Devido ao DataObjectMethodAttribute usado em CategoriesBLL, a lista suspensa UPDATE deve ser preenchida automaticamente com a sobrecarga de UpdateCategory que aceita quatro parâmetros de entrada (para todas as colunas, exceto Picture). Altere isso para que ele use a sobrecarga com cinco parâmetros.
Figura 9: Configurar o ObjectDataSource para usar o método para o UpdateCategory qual inclui um parâmetro Picture (clique para exibir a imagem em tamanho real)
O ObjectDataSource agora incluirá um valor para a sua propriedade UpdateMethod e também para os UpdateParameter correspondentes. Conforme observado na Etapa 4, o Visual Studio define a propriedade ObjectDataSource como OldValuesParameterFormatStringoriginal_{0} ao usar o assistente Configurar Fonte de Dados. Isso causará problemas com as invocações de método de atualização e exclusão. Portanto, desmarque completamente essa propriedade ou redefina-a para o padrão. {0}
Depois de concluir o assistente e corrigir o OldValuesParameterFormatString, a marcação declarativa do ObjectDataSource deve ser semelhante ao seguinte:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory">
<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>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
<asp:Parameter Name="categoryID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
Para ativar os recursos de edição internos do GridView, verifique a opção Habilitar Edição na marca inteligente do GridView. Isso definirá a propriedade CommandField ShowEditButton como True, resultando na adição de um botão Editar (e botões Atualizar e Cancelar para a linha que está sendo editada).
Figura 10: Configurar o GridView para dar suporte à edição (clique para exibir a imagem em tamanho real)
Visite a página por meio de um navegador e clique em um dos botões da linha Editar. O CategoryName e Description BoundFields são renderizados como caixas de texto. O BrochurePath TemplateField não tem um EditItemTemplate, portanto, ele continua a mostrar seu ItemTemplate link para o folheto. O Picture ImageField é renderizado como um TextBox cuja propriedade Text recebe o valor da propriedade DataImageUrlField do ImageField, nesse caso CategoryID.
Figura 11: O GridView não tem uma interface de edição para BrochurePath (clique para exibir a imagem em tamanho real)
Personalizando aBrochurePathinterface de edição
Precisamos criar uma interface de edição para o BrochurePath TemplateField, uma que permita ao usuário:
- Deixe o folheto da categoria as-is.
- Atualizar o folheto da categoria carregando um novo folheto ou
- Remova o folheto da categoria completamente (caso a categoria não tenha mais um folheto associado).
Também precisamos atualizar a Picture interface de edição do ImageField, mas chegaremos a isso na Etapa 7.
Na tag inteligente do GridView, clique no link Editar Modelos e selecione o BrochurePath TemplateField, na lista suspensa EditItemTemplate. Adicione um controle Web RadioButtonList a este modelo, definindo a propriedade ID como BrochureOptions e a propriedade AutoPostBack como True. Na janela Propriedades, clique nas reticências na propriedade Items, o Editor de Coleção ListItem será aberto. Adicione as três opções a seguir numeradas com Value os números 1, 2 e 3, respectivamente.
- Usar o folheto atual
- Remover o folheto atual
- Carregar novo folheto
Defina a propriedade do primeiro ListItem como SelectedTrue.
Figura 12: Adicionar três ListItem s ao RadioButtonList
Abaixo do RadioButtonList, adicione um controle FileUpload chamado BrochureUpload. Defina a propriedade Visible dele como False.
Figura 13: Adicionar um controle RadioButtonList e FileUpload ao EditItemTemplate (Clique para exibir a imagem em tamanho real)
Este RadioButtonList fornece as três opções para o usuário. A ideia é que o controle FileUpload será exibido somente se a última opção, Carregar novo folheto, estiver selecionada. Para fazer isso, crie um manipulador de eventos para o evento RadioButtonList SelectedIndexChanged e adicione o seguinte código:
Protected Sub BrochureOptions_SelectedIndexChanged _
(sender As Object, e As EventArgs)
' Get a reference to the RadioButtonList and its Parent
Dim BrochureOptions As RadioButtonList = _
CType(sender, RadioButtonList)
Dim parent As Control = BrochureOptions.Parent
' Now use FindControl("controlID") to get a reference of the
' FileUpload control
Dim BrochureUpload As FileUpload = _
CType(parent.FindControl("BrochureUpload"), FileUpload)
' Only show BrochureUpload if SelectedValue = "3"
BrochureUpload.Visible = (BrochureOptions.SelectedValue = "3")
End Sub
Como os controles RadioButtonList e FileUpload estão dentro de um modelo, precisamos escrever um pouco de código para acessar programaticamente esses controles. O manipulador de eventos SelectedIndexChanged recebe uma referência ao RadioButtonList no parâmetro de entrada sender. Para obter o controle FileUpload, precisamos obter o controle pai de RadioButtonList e usar o método FindControl("controlID") a partir daí. Depois que tivermos uma referência aos controles RadioButtonList e FileUpload, a propriedade do controle Visible FileUpload será definida para True somente se o RadioButtonList for igual a SelectedValue 3, que é o Value para a opção Carregar novo folheto ListItem.
Com esse código em vigor, reserve um momento para testar a interface de edição. Clique no botão Editar para uma linha. Inicialmente, a opção Usar folheto atual deve ser selecionada. A alteração do índice selecionado provoca um postback. Se a terceira opção estiver selecionada, o controle FileUpload será exibido, caso contrário, ficará oculto. A Figura 14 mostra a interface de edição quando o botão Editar é clicado pela primeira vez; A Figura 15 mostra a interface depois que a opção Carregar novo folheto é selecionada.
Figura 14: Inicialmente, a opção Usar folheto atual está selecionada (clique para exibir a imagem em tamanho real)
Figura 15: Escolher a opção Carregar novo folheto exibe o controle FileUpload (clique para exibir a imagem em tamanho real)
Salvando o arquivo de folheto e atualizando aBrochurePathcoluna
Quando o botão Atualizar do GridView é clicado, o evento RowUpdating é acionado. O comando de atualização do ObjectDataSource é invocado e, em seguida, o evento do GridView é RowUpdated acionado. Assim como com o fluxo de trabalho de exclusão, precisamos criar manipuladores de eventos para ambos os eventos. No manipulador de eventos RowUpdating, precisamos determinar qual ação tomar com base no SelectedValue do BrochureOptions RadioButtonList:
- Se for
SelectedValue1, queremos continuar usando a mesmaBrochurePathconfiguração. Portanto, precisamos definir o parâmetro ObjectDataSourcebrochurePathpara o valor existenteBrochurePathdo registro que está sendo atualizado. O parâmetrobrochurePathdo ObjectDataSource pode ser definido usandoe.NewValues["brochurePath"] = value. - Se o
SelectedValuefor 2, então queremos definir o valor do registroBrochurePathcomoNULL. Isso pode ser feito definindo o parâmetro ObjectDataSourcebrochurePathcomoNothing, o que resulta em um banco de dadosNULLsendo usado naUPDATEinstrução. Se houver um arquivo de folheto existente que está sendo removido, precisaremos excluir o arquivo existente. No entanto, só queremos fazer isso se a atualização for concluída sem gerar uma exceção. - Se
SelectedValuefor 3, queremos garantir que o usuário tenha feito upload de um arquivo PDF e então salvá-lo no sistema de arquivos e atualizar o valor da coluna do registroBrochurePath. Além disso, se houver um arquivo de folheto existente que está sendo substituído, precisaremos excluir o arquivo anterior. No entanto, só queremos fazer isso se a atualização for concluída sem gerar uma exceção.
As etapas necessárias para serem concluídas quando o valor de RadioButtonList SelectedValue é 3 são praticamente idênticas às usadas pelo manipulador de eventos do DetailsView ItemInserting. Esse manipulador de eventos é executado quando um novo registro de categoria é adicionado do controle DetailsView que adicionamos no tutorial anterior. Portanto, cabe a nós refatorar essa funcionalidade em métodos separados. Especificamente, mudei a funcionalidade comum para dois métodos:
-
ProcessBrochureUpload(FileUpload, out bool)aceita como entrada uma instância de controle FileUpload e um valor booliano de saída que especifica se a operação de exclusão ou edição deve continuar ou se deve ser cancelada devido a algum erro de validação. Esse método retorna o caminho para o arquivo salvo ounullse nenhum arquivo foi salvo. -
DeleteRememberedBrochurePathexclui o arquivo especificado pelo caminho na variável de páginadeletedCategorysPdfPathsedeletedCategorysPdfPathnão fornull.
O código para esses dois métodos segue. Observe a similaridade entre ProcessBrochureUpload e o manipulador de eventos do ItemInserting DetailsView do tutorial anterior. Neste tutorial, atualizei os manipuladores de eventos do DetailsView para usar esses novos métodos. Baixe o código associado a este tutorial para ver as modificações nos manipuladores de eventos do DetailsView.
Private Function ProcessBrochureUpload _
(BrochureUpload As FileUpload, CancelOperation As Boolean) As String
CancelOperation = False ' by default, do not cancel operation
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
CancelOperation = True
Return Nothing
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))
Return brochurePath
Else
' No file uploaded
Return Nothing
End If
End Function
Private Sub DeleteRememberedBrochurePath()
' Is there a file to delete?
If deletedCategorysPdfPath IsNot Nothing Then
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath))
End If
End Sub
Os manipuladores de eventos do RowUpdating e RowUpdated GridView usam os métodos ProcessBrochureUpload e DeleteRememberedBrochurePath, como mostra o código a seguir:
Protected Sub Categories_RowUpdating _
(sender As Object, e As GridViewUpdateEventArgs) _
Handles Categories.RowUpdating
' Reference the RadioButtonList
Dim BrochureOptions As RadioButtonList = _
CType(Categories.Rows(e.RowIndex).FindControl("BrochureOptions"), _
RadioButtonList)
' Get BrochurePath information about the record being updated
Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID"))
Dim categoryAPI As New CategoriesBLL()
Dim categoriesData As Northwind.CategoriesDataTable = _
categoryAPI.GetCategoryByCategoryID(categoryID)
Dim category As Northwind.CategoriesRow = categoriesData(0)
If BrochureOptions.SelectedValue = "1" Then
' Use current value for BrochurePath
If category.IsBrochurePathNull() Then
e.NewValues("brochurePath") = Nothing
Else
e.NewValues("brochurePath") = category.BrochurePath
End If
ElseIf BrochureOptions.SelectedValue = "2" Then
' Remove the current brochure (set it to NULL in the database)
e.NewValues("brochurePath") = Nothing
ElseIf BrochureOptions.SelectedValue = "3" Then
' Reference the BrochurePath FileUpload control
Dim BrochureUpload As FileUpload = _
CType(categories.Rows(e.RowIndex).FindControl("BrochureUpload"), _
FileUpload)
' Process the BrochureUpload
Dim cancelOperation As Boolean = False
e.NewValues("brochurePath") = _
ProcessBrochureUpload(BrochureUpload, cancelOperation)
e.Cancel = cancelOperation
Else
' Unknown value!
Throw New ApplicationException( _
String.Format("Invalid BrochureOptions value, {0}", _
BrochureOptions.SelectedValue))
End If
If BrochureOptions.SelectedValue = "2" OrElse _
BrochureOptions.SelectedValue = "3" Then
' "Remember" that we need to delete the old PDF file
If (category.IsBrochurePathNull()) Then
deletedCategorysPdfPath = Nothing
Else
deletedCategorysPdfPath = category.BrochurePath
End If
End If
End Sub
Protected Sub Categories_RowUpdated _
(sender As Object, e As GridViewUpdatedEventArgs) _
Handles Categories.RowUpdated
' If there were no problems and we updated the PDF file,
' then delete the existing one
If e.Exception Is Nothing Then
DeleteRememberedBrochurePath()
End If
End Sub
Observe como o RowUpdating manipulador de eventos usa uma série de instruções condicionais para executar a ação apropriada com base no BrochureOptions valor da SelectedValue propriedade RadioButtonList.
Com esse código em vigor, você pode editar uma categoria e fazer com que ela use seu folheto atual, não use nenhum folheto ou carregue um novo. Vá em frente e experimente. Defina pontos RowUpdating de interrupção nos manipuladores de eventos e RowUpdated para ter uma noção do fluxo de trabalho.
Etapa 7: Carregando uma nova imagem
A interface de edição do Picture ImageField é renderizada como uma caixa de texto preenchida com o valor de sua propriedade DataImageUrlField. Durante o fluxo de trabalho de edição, o GridView passa um parâmetro para o ObjectDataSource, onde o nome do parâmetro é o valor da propriedade DataImageUrlField do ImageField, e o valor do parâmetro é o valor que foi inserido na caixa de texto na interface de edição. Esse comportamento é adequado quando a imagem é salva como um arquivo no sistema de arquivos e DataImageUrlField contém a URL completa da imagem. Com essas circunstâncias, a interface de edição exibe a URL da imagem na caixa de texto, que o usuário pode alterar e salvar de volta no banco de dados. É verdade que essa interface padrão não permite que o usuário carregue uma nova imagem, mas permite que ele altere a URL da imagem de seu valor atual para outro. Para este tutorial, no entanto, a interface de edição padrão do ImageField não é suficiente porque os Picture dados binários estão sendo armazenados diretamente no banco de dados e a DataImageUrlField propriedade contém apenas o CategoryID.
Para entender melhor o que acontece em nosso tutorial quando um usuário edita uma linha com um ImageField, considere o seguinte exemplo: um usuário edita uma linha com 10, fazendo com CategoryID que o Picture ImageField seja renderizado como uma caixa de texto com o valor 10. Imagine que o usuário altere o valor nesta caixa de texto para 50 e clique no botão Atualizar. Ocorre um postback e o GridView cria inicialmente um parâmetro nomeado CategoryID com o valor 50. No entanto, antes que o GridView envie esse parâmetro (e os parâmetros CategoryName e Description), ele adiciona os valores da coleção DataKeys. Portanto, ele substitui o parâmetro CategoryID pelo valor subjacente CategoryID da linha atual, que é 10. Em suma, a interface de edição do ImageField não afeta o fluxo de trabalho de edição deste tutorial porque os nomes da propriedade ImageField DataImageUrlField e do valor da grade DataKey são o mesmo.
Embora o ImageField facilite a exibição de uma imagem com base nos dados do banco de dados, não queremos fornecer uma caixa de texto na interface de edição. Em vez disso, queremos oferecer um controle FileUpload que o usuário final pode usar para alterar a imagem da categoria. Ao contrário do BrochurePath valor, para esses tutoriais decidimos exigir que cada categoria tenha uma imagem. Portanto, não precisamos permitir que o usuário indique que não há nenhuma imagem associada. O usuário pode carregar uma nova imagem ou deixar a imagem atual as-is.
Para personalizar a interface de edição do ImageField, precisamos convertê-la em um TemplateField. Na tag inteligente do GridView, clique no link Editar Colunas, selecione o ImageField e clique no link Converter este campo em um TemplateField.
Figura 16: Converter o ImageField em um TemplateField
Converter o ImageField em um TemplateField dessa maneira gera um TemplateField com dois modelos. Como mostra a sintaxe declarativa a seguir, a ItemTemplate contém um Web Controle de imagem cuja propriedade ImageUrl é atribuída usando a sintaxe de associação de dados com base nas propriedades DataImageUrlField e DataImageUrlFormatString do ImageField. Um TextBox, cuja propriedade EditItemTemplate está associada ao valor especificado pela propriedade Text, está contido em DataImageUrlField.
<asp:TemplateField>
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Eval("CategoryID") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# Eval("CategoryID",
"DisplayCategoryPicture.aspx?CategoryID={0}") %>' />
</ItemTemplate>
</asp:TemplateField>
Precisamos atualizar o EditItemTemplate para usar um controle de upload de arquivo. Na tag inteligente do GridView, clique no link Editar Modelos e selecione o TemplateField Picture na lista suspensa EditItemTemplate. No modelo, você deverá ver um TextBox, remova-o. Em seguida, arraste um controle FileUpload da Caixa de Ferramentas para o modelo, definindo-o ID como PictureUpload. Adicione também o texto Para alterar a imagem da categoria, especifique uma nova imagem. Para manter a imagem da categoria inalterada, deixe o campo vazio no modelo também.
Figura 17: Adicionar um controle FileUpload à EditItemTemplate(Clique para exibir a imagem em tamanho real)
Depois de personalizar a interface de edição, exiba seu progresso em um navegador. Ao visualizar uma linha no modo somente leitura, a imagem da categoria é exibida como era antes, mas ao clicar no botão Editar, a coluna da imagem é transformada em uma representação de texto com um controle de upload de arquivo.
Figura 18: A interface de edição inclui um controle FileUpload (clique para exibir a imagem em tamanho real)
Lembre-se de que o ObjectDataSource está configurado para chamar o método CategoriesBLL da classe UpdateCategory que aceita como entrada os dados binários para a imagem como uma matriz Byte. Se essa matriz for Nothing, no entanto, a sobrecarga alternativa UpdateCategory será chamada, o que emitirá a UPDATE instrução SQL que não modifica a Picture coluna, deixando assim a imagem atual da categoria intacta. Portanto, no manipulador de eventos do RowUpdating GridView, precisamos referenciar programaticamente o PictureUpload controle FileUpload e determinar se um arquivo foi carregado. Se um não tiver sido carregado, não queremos especificar um valor para o picture parâmetro. Por outro lado, se um arquivo foi carregado no PictureUpload controle FileUpload, queremos garantir que ele seja um arquivo JPG. Se estiver, poderemos enviar seu conteúdo binário para o ObjectDataSource por meio do picture parâmetro.
Assim como com o código usado na Etapa 6, grande parte do código necessário aqui já existe no manipulador de eventos DetailsView ItemInserting . Portanto, refatorei a funcionalidade comum em um novo método ValidPictureUploade atualizei o ItemInserting manipulador de eventos para usar esse método.
Adicione o código a seguir ao início do manipulador de eventos do RowUpdating GridView. É importante que esse código venha antes do código que salva o arquivo de folheto, pois não queremos salvar o folheto no sistema de arquivos do servidor Web se um arquivo de imagem inválido for carregado.
' Reference the PictureUpload FileUpload
Dim PictureUpload As FileUpload = _
CType(categories.Rows(e.RowIndex).FindControl("PictureUpload"), _
FileUpload)
If PictureUpload.HasFile Then
' Make sure the picture upload is valid
If ValidPictureUpload(PictureUpload) Then
e.NewValues("picture") = PictureUpload.FileBytes
Else
' Invalid file upload, cancel update and exit event handler
e.Cancel = True
Exit Sub
End If
End If
O ValidPictureUpload(FileUpload) método usa um controle FileUpload como seu único parâmetro de entrada e verifica a extensão do arquivo carregado para garantir que o arquivo carregado seja um JPG; ele só será chamado se um arquivo de imagem for carregado. Se nenhum arquivo for carregado, o parâmetro de imagem não será definido e, portanto, usará seu valor padrão de Nothing. Se uma imagem foi carregada e ValidPictureUpload retorna True, o parâmetro picture recebe os dados binários da imagem carregada; se o método retorna False, o fluxo de trabalho de atualização é cancelado e o manipulador de eventos é encerrado.
O ValidPictureUpload(FileUpload) código do método, que foi refatorado do manipulador de eventos do DetailsView ItemInserting, segue:
Private Function ValidPictureUpload(ByVal PictureUpload As FileUpload) As Boolean
' 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
Return False
Else
Return True
End If
End Function
Etapa 8: Substituindo as imagens de categorias originais por JPGs
Lembre-se de que as imagens de oito categorias originais são arquivos bitmap encapsulados em um cabeçalho OLE. Agora que adicionamos a capacidade de editar uma imagem de registro existente, tire um momento para substituir esses bitmaps por JPGs. Se quiser continuar a usar as imagens de categoria atuais, você poderá convertê-las em JPGs executando as seguintes etapas:
- Salve as imagens de bitmap no disco rígido. Visite a
UpdatingAndDeleting.aspxpágina no navegador e, para cada uma das oito primeiras categorias, clique com o botão direito do mouse na imagem e escolha salvar a imagem. - Abra a imagem no editor de imagens de sua escolha. Você pode usar o Microsoft Paint, por exemplo.
- Salve o bitmap como uma imagem JPG.
- Atualize a imagem da categoria por meio da interface de edição, usando o arquivo JPG.
Depois de editar uma categoria e carregar a imagem JPG, a imagem não será renderizada no navegador porque a DisplayCategoryPicture.aspx página está retirando os primeiros 78 bytes das imagens das oito primeiras categorias. Corrija isso removendo o código que executa a remoção do cabeçalho OLE. Depois de fazer isso, o DisplayCategoryPicture.aspx``Page_Load manipulador de eventos deve ter apenas o seguinte código:
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)
' 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 Sub
Observação
As UpdatingAndDeleting.aspx interfaces de inserção e edição da página poderiam ser aprimoradas. Os BoundFields em CategoryName e Description no DetailsView e GridView devem ser convertidos em TemplateFields. Como CategoryName não permite NULL valores, um RequiredFieldValidator deve ser adicionado. E o Description TextBox provavelmente deve ser convertido em um TextBox de várias linhas. Eu deixo esses retoques finais como um exercício para você.
Resumo
Este tutorial conclui nossa análise sobre como trabalhar com dados binários. Neste tutorial e nos três anteriores, vimos como os dados binários podem ser armazenados no sistema de arquivos ou diretamente no banco de dados. Um usuário fornece dados binários para o sistema selecionando um arquivo de seu disco rígido e carregando-os no servidor Web, onde podem ser armazenados no sistema de arquivos ou inseridos no banco de dados. ASP.NET 2.0 inclui um controle FileUpload que torna o fornecimento de uma interface tão fácil quanto arrastar e soltar. No entanto, conforme observado no tutorial Carregar Arquivos , o controle FileUpload só é adequado para uploads de arquivos relativamente pequenos, o ideal é não exceder um megabyte. Também exploramos como associar dados carregados ao modelo de dados subjacente, bem como editar e excluir os dados binários de registros existentes.
Nosso próximo conjunto de tutoriais explora várias técnicas de cache. O cache fornece um meio de melhorar o desempenho geral de um aplicativo, tirando os resultados de operações caras e armazenando-os em um local que pode ser acessado mais rapidamente.
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. A revisora principal deste tutorial foi Teresa Murphy. Interessado em revisar meus próximos artigos do MSDN? Se assim for, deixe-me uma linha em mitchell@4GuysFromRolla.com.