Partilhar via


Atualizando e excluindo dados binários existentes (VB)

por Scott Mitchell

Descarregar PDF

Em tutoriais anteriores, vimos como o controle GridView simplifica a edição e a exclusão de dados de texto. Neste tutorial, vemos como o controle GridView também torna possível editar e excluir dados binários, quer esses dados binários sejam 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çámos por adicionar uma BrochurePath coluna à Categories tabela e atualizámos a arquitetura em conformidade. Também adicionamos os métodos Data Access Layer e Business Logic Layer para trabalhar com a coluna existente Picture da tabela Categories, que contém o conteúdo binário s de um arquivo de imagem. Nós construímos páginas da web para apresentar os dados binários em uma GridView, incluindo um link para download do folheto, com a imagem da categoria mostrada em um elemento <img>, e adicionámos uma DetailsView para permitir que os utilizadores adicionem uma nova categoria e façam o upload dos dados da brochura e da imagem.

Tudo o que resta a ser implementado é a capacidade de editar e excluir categorias existentes, o que faremos 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 a brochura, podem optar por utilizar a brochura existente, carregar uma nova brochura ou indicar que a categoria já não tem uma brochura associada. Vamos começar!

Etapa 1: Atualizando a camada de acesso a dados

A DAL gerou os métodos Insert, Update e Delete automaticamente, mas esses métodos foram gerados com base na consulta principal CategoriesTableAdapter, que não inclui a coluna Picture. Portanto, os Insert métodos 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 Typed DataSet e, no Designer, clique com o botão direito no cabeçalho de CategoriesTableAdapter e escolha Adicionar Consulta no menu de contexto para iniciar o Assistente de Configuração de Consulta do TableAdapter. Este 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 o tipo de consulta a ser gerada. Como estamos criando uma consulta para adicionar um novo registro à Categories tabela, escolha ATUALIZAR e clique em Avançar.

Selecione a opção UPDATE

Figura 1: Selecione a opção UPDATE (Clique para visualizar a imagem em tamanho real)

Agora precisamos especificar a UPDATE instrução SQL. O assistente sugere automaticamente uma UPDATE instrução correspondente à consulta principal do TableAdapter (que atualiza os CategoryName, Description, e BrochurePath valores). 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. Entre UpdateWithPicture e clique em Concluir.

Nomeie o novo método TableAdapter UpdateWithPicture

Figura 2: Nomeie o novo método UpdateWithPicture TableAdapter (Clique para visualizar a imagem em tamanho real)

Etapa 2: Adicionando os métodos da camada de lógica de negócios

Além de atualizar o DAL, precisamos atualizar o BLL para incluir métodos de atualização e exclusão de uma categoria. Estes são os métodos que serão invocados a partir da camada de apresentação.

Para excluir uma categoria, podemos usar o método CategoriesTableAdapter auto-gerado 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ários e invoca o UpdateWithPicture método que acabamos de adicionar ao CategoriesTableAdapter e outro que aceita apenas o CategoryName, Descriptione BrochurePath valores e usa CategoriesTableAdapter a instrução gerada automaticamente Update pela classe s. 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, caso em que o usuário terá que carregar a nova imagem. Os dados binários da imagem carregada podem então ser utilizados 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, então precisamos fornecer essas informações também. Isso exigiria uma viagem extra ao banco de dados para trazer de volta os dados da imagem para o registro que está sendo editado. Por conseguinte, queremos dois UPDATE métodos. A camada de lógica de negócios determinará qual usar com base em se os dados de imagem são fornecidos ao atualizar a categoria.

Para facilitar isso, adicione dois métodos à CategoriesBLL classe, ambos chamados UpdateCategory. O primeiro deve aceitar três String s, uma Byte matriz e um Integer como seus parâmetros de entrada, o segundo, apenas três String s e um Integer. Os String parâmetros de entrada destinam-se ao nome da categoria, à descrição e ao caminho do ficheiro da brochura. A matriz Byte destina-se ao conteúdo binário da imagem da categoria, e o Integer identifica o CategoryID do registo a ser atualizado. Observe que a primeira sobrecarga invoca a segunda se a matriz passada 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: Transferir a funcionalidade de inserir e visualizar

No tutorial anterior , criamos uma página chamada UploadInDetailsView.aspx que listava todas as categorias em um GridView e fornecíamos um DetailsView para adicionar novas categorias ao sistema. Neste tutorial, estenderemos o GridView para incluir o suporte à edição e exclusão. Em vez de continuar a trabalhar a partir do UploadInDetailsView.aspx, vamos colocar as alterações deste tutorial na UpdatingAndDeleting.aspx página da mesma pasta, ~/BinaryData. Copie e cole a marcação declarativa e o código de UploadInDetailsView.aspx para UpdatingAndDeleting.aspx.

Comece por abrir a UploadInDetailsView.aspx página. Copie toda a sintaxe declarativa dentro do elemento <asp:Content>, como mostrado na Figura 3. Em seguida, abra UpdatingAndDeleting.aspx e cole essa marcação em seu <asp:Content> elemento . Da mesma forma, copie o código da classe de code-behind da página UploadInDetailsView.aspx para UpdatingAndDeleting.aspx.

Copie a marcação declarativa de UploadInDetailsView.aspx

Figura 3: Copie a marcação declarativa de UploadInDetailsView.aspx (Clique para visualizar 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 do utilizador que com a página UploadInDetailsView.aspx do tutorial anterior.

Etapa 4: Adicionando suporte de exclusão para o ObjectDataSource e GridView

Como discutimos anteriormente no tutorial Visão geral da inserção, atualização e exclusão de dados , o GridView fornece recursos internos de exclusão e esses recursos podem ser habilitados no momento de uma caixa de seleção se a fonte de dados subjacente da grade suportar exclusão. Atualmente, o ObjectDataSource ao qual o GridView está vinculado (CategoriesDataSource) não oferece suporte à exclusão.

Para corrigir isso, clique na opção Configurar fonte de dados na tag inteligente ObjectDataSource para abrir o assistente. A primeira tela mostra que o ObjectDataSource está configurado para trabalhar com a CategoriesBLL classe. Clique em Avançar. Atualmente, apenas as propriedades InsertMethod e SelectMethod do ObjectDataSource são especificadas. No entanto, o assistente preencheu automaticamente as listas suspensas nas abas UPDATE e DELETE com os métodos UpdateCategory e DeleteCategory, respectivamente. Isso ocorre porque na CategoriesBLL classe marcamos esses métodos usando o DataObjectMethodAttribute como os métodos padrão para atualizar e excluir.

Por enquanto, defina a lista suspensa da guia UPDATE como (Nenhuma), mas deixe a lista suspensa da guia DELETE definida como DeleteCategory. Voltaremos a este assistente na Etapa 6 para adicionar suporte às atualizações.

Configurar o ObjectDataSource para usar o método DeleteCategory

Figura 4: Configurar o ObjectDataSource para usar o método (DeleteCategory imagem em tamanho real)

Observação

Ao concluir o assistente, o Visual Studio pode perguntar ao utilizador se deseja atualizar campos e chaves, o que irá regenerar os campos dos controlos de dados da Web. Escolha Não, porque escolher Sim substituirá quaisquer personalizações de campo que você possa ter feito.

O ObjectDataSource agora incluirá um valor para a propriedade DeleteMethod, bem como um valor para DeleteParameter. Lembre-se de que, ao usar o assistente para especificar os métodos, o Visual Studio define a OldValuesParameterFormatString propriedade de ObjectDataSource comooriginal_{0}, que levanta problemas com as invocações de métodos de atualização e exclusão. Portanto, limpe essa propriedade completamente ou redefina-a para o padrão, {0}. Se você precisar atualizar sua memória nessa propriedade ObjectDataSource, consulte o tutorial Uma visão geral da inserção, atualização e exclusão de dados .

Depois de concluir o assistente e corrigir o OldValuesParameterFormatString, a marcação declarativa de ObjectDataSource deve ser semelhante à 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 capacidades de eliminação ao GridView marcando a caixa de seleção "Ativar eliminação" na etiqueta inteligente do GridView. Isso adicionará um CommandField ao GridView cuja ShowDeleteButton propriedade está definida como True.

Habilitar suporte para exclusão no GridView

Figura 5: Habilitar o suporte para exclusão no GridView (Clique para visualizar a imagem em tamanho real)

Reserve um momento para testar a funcionalidade de exclusão. Há uma chave estrangeira entre a Products tabela s CategoryID e a Categories tabela s CategoryID, portanto, você obterá uma exceção de violação de restrição de chave estrangeira se tentar excluir qualquer uma das oito primeiras categorias. Para testar esta funcionalidade, adicione uma nova categoria, fornecendo uma brochura e uma imagem. Minha categoria de teste, mostrada na Figura 6, inclui um arquivo de brochura de teste nomeado Test.pdf e uma imagem de teste. A Figura 7 mostra o GridView após a categoria de teste ter sido adicionada.

Adicionar uma categoria de teste com uma brochura e imagem

Figura 6: Adicionar uma categoria de teste com um folheto e uma imagem (Clique para visualizar a imagem em tamanho real)

Depois de inserir a categoria de teste, ela é exibida no GridView

Figura 7: Depois de inserir a categoria de teste, ela é exibida no GridView (Clique para visualizar a imagem em tamanho real)

No Visual Studio, atualize o Gerenciador de Soluções. Agora deves ver um novo ficheiro 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 seja recarregada e o método da classe seja executado. Isso invocará o método DAL s Delete , fazendo com que a instrução apropriada DELETE seja enviada para o banco de dados. Os dados são então redirecionados para o GridView e a marcação é enviada de volta para o cliente com a categoria de teste não mais presente.

Embora o fluxo de trabalho de exclusão tenha removido com êxito o registro de categoria de teste da Categories tabela, ele não removeu seu arquivo de brochura do sistema de arquivos do servidor Web. Atualize o Gerenciador de Soluções e você verá que Test.pdf ainda está na ~/Brochures pasta.

O arquivo Test.pdf não foi excluído do sistema de arquivos do servidor Web

Figura 8: O Test.pdf arquivo não foi excluído do sistema de arquivos do servidor Web

Etapa 5: Removendo o arquivo de brochura da categoria excluída

Uma das desvantagens do armazenamento de 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 acionados antes e depois da execução do comando delete. 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 s RowDeleting de GridView é acionado antes que o comando delete de ObjectDataSource tenha sido invocado, enquanto seu RowDeleted evento é 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

RowDeleting No manipulador de eventos, a CategoryID linha que está sendo excluída é capturada da coleção s DataKeys GridView, que pode ser acessada nesse manipulador de eventos por meio da e.Keys coleção. Em seguida, a CategoriesBLL classe s GetCategoryByCategoryID(categoryID) é invocada para retornar informações sobre o registro que está sendo excluído. Se o objeto retornado CategoriesDataRow tiver um valor diferente de NULL``BrochurePath, ele será armazenado na variável de página deletedCategorysPdfPath, permitindo a exclusão do arquivo no evento RowDeleted de manipulação.

Observação

Em vez de recuperar os BrochurePath detalhes do Categories registo que está a ser eliminado no manipulador de RowDeleting eventos, poderíamos, alternativamente, ter adicionado o BrochurePath à propriedade DataKeyNames de GridView e acedido ao valor do registo por meio 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 de o comando subjacente de eliminação do ObjectDataSource ter sido invocado, o evento RowDeleted do manipulador de 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, o PDF associado também é excluído.

Atualizar os 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 da brochura, enquanto a Etapa 7 analisa a atualização da imagem.

Passo 6: Atualizar uma brochura de categoria

Conforme discutido no tutorial Visão geral da inserção, atualização e exclusão de dados, o GridView oferece suporte interno à edição em nível de linha que pode ser implementado marcando 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 suporte à atualização, portanto, vamos adicioná-lo.

Clique no link Configurar Fonte de Dados do assistente de ObjectDataSource e prossiga para a segunda etapa. Devido ao DataObjectMethodAttribute usado no CategoriesBLL, a lista suspensa UPDATE deve ser automaticamente preenchida 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.

Configure o ObjectDataSource para usar o método UpdateCategory que inclui um parâmetro para Picture

Figura 9: Configurar o ObjectDataSource para usar o UpdateCategory método que inclui um parâmetro para Picture (Clique para visualizar a imagem em tamanho real)

O ObjectDataSource agora incluirá um valor para a sua propriedade UpdateMethod e os correspondentes UpdateParameter. Conforme observado na Etapa 4, o Visual Studio define a propriedade ObjectDataSource s OldValuesParameterFormatString como original_{0} ao usar o assistente Configurar Fonte de Dados. Isso causará problemas com as invocações do método de atualização e exclusão. Portanto, limpe essa propriedade completamente ou redefina-a para o padrão, {0}.

Depois de concluir o assistente e corrigir o OldValuesParameterFormatString, a marcação declarativa de ObjectDataSource deve ter a seguinte aparência:

<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, marque a opção Ativar edição na etiqueta inteligente do GridView. Isso definirá a propriedade s ShowEditButton de CommandField como True, resultando na adição de um botão Editar (e botões Atualizar e Cancelar para a linha que está sendo editada).

Configurar o GridView para suportar a edição

Figura 10: Configurar o GridView para dar suporte à edição (Clique para visualizar a imagem em tamanho real)

Visite a página através de um navegador e clique em um dos botões Editar das linhas. O CategoryName e Description BoundFields são renderizados como caixas de texto. O BrochurePath TemplateField não tem um EditItemTemplate; por isso, continua a mostrar um ItemTemplate link para a brochura. O Picture ImageField é renderizado como um TextBox cuja propriedade Text é atribuída o valor da propriedade DataImageUrlField do ImageField, neste caso CategoryID.

O GridView não tem uma interface de edição para BrochurePath

Figura 11: O GridView não tem uma interface de edição para BrochurePath (Clique para visualizar a imagem em tamanho real)

Personalizando a interface de edição

Precisamos criar uma interface de edição para o BrochurePath TemplateField, que permita ao usuário:

  • Deixe a brochura da categoria as-is,
  • Atualize a brochura da categoria carregando uma nova brochura, ou
  • Remova completamente a brochura da categoria (no caso de a categoria já não ter uma brochura associada).

Também precisamos atualizar a interface de edição do Picture ImageField, mas chegaremos a isso na Etapa 7.

Na marca inteligente do GridView, clique no link Editar modelos e selecione o BrochurePath TemplateField s EditItemTemplate na lista suspensa. Adicione um controle da Web RadioButtonList a este modelo, definindo sua ID propriedade como BrochureOptions e sua AutoPostBack propriedade como True. Na janela Propriedades, clique nas reticências da propriedade Items, que exibirão o Editor de Coleções ListItem. Adicione as três opções seguintes com Value s 1, 2 e 3, respetivamente:

  • Utilize a brochura atual
  • Remover a atual brochura
  • Carregar nova brochura

Defina a primeira ListItem propriedade s Selected como True.

Adicionar três Itens de Lista à Lista de Botões de Rádio

Figura 12: Adicionar três ListItem s à RadioButtonList

Abaixo de RadioButtonList, adicione um controle FileUpload chamado BrochureUpload. Defina a propriedade Visible como False.

Adicionar um controle RadioButtonList e FileUpload ao EditItemTemplate

Figura 13: Adicionar um controlo RadioButtonList e FileUpload ao EditItemTemplate (Clique para visualizar a imagem em tamanho real)

Este RadioButtonList fornece as três opções para o usuário. A ideia é que o controle FileUpload seja exibido somente se a última opção, Carregar novo folheto, for selecionada. Para fazer isso, crie um manipulador de eventos para o evento RadioButtonList s 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, temos que escrever um pouco de código para acessar programaticamente esses controles. O SelectedIndexChanged manipulador de eventos recebe uma referência de RadioButtonList no sender parâmetro de entrada. Para obter o controlo FileUpload, precisamos de aceder ao controlo pai da RadioButtonList e depois utilizar o método FindControl("controlID") a partir daí. Quando tivermos uma referência aos controlos RadioButtonList e FileUpload, a propriedade do controlo FileUpload será Visible definida como True somente se o RadioButtonList SelectedValue for igual a 3, que é o Value para '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 brochura atual deve ser selecionada. Alterar o índice selecionado provoca um postback. Se a terceira opção for 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 nova brochura é selecionada.

Inicialmente, a opção Usar brochura atual está selecionada

Figura 14: Inicialmente, a opção Usar brochura atual está selecionada (Clique para visualizar a imagem em tamanho real)

Escolher a opção Carregar novo folheto exibe o controle FileUpload

Figura 15: Escolhendo a opção Upload new brochure Exibe o controle FileUpload (Clique para visualizar a imagem em tamanho real)

Guardar o ficheiro da brochura e atualizar aBrochurePathcoluna

Quando o botão Atualizar do GridView é clicado, o seu evento RowUpdating é acionado. O comando de atualização do ObjectDataSource é invocado e, em seguida, o evento RowUpdated do GridView é acionado. Como no 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 de BrochureOptions RadioButtonList.

  • Se o SelectedValue for 1, queremos continuar usando a mesma BrochurePath configuração. Portanto, precisamos definir o parâmetro ObjectDataSource s brochurePath para o valor existente BrochurePath do registro que está sendo atualizado. O parâmetro s brochurePath ObjectDataSource pode ser definido usando e.NewValues["brochurePath"] = value.
  • Se o SelectedValue for 2, então queremos definir o valor s BrochurePath do registro como NULL. Isso pode ser feito definindo o parâmetro do ObjectDataSource brochurePath como Nothing, o que resulta em um banco de dados NULL sendo usado na instrução UPDATE. Se houver um arquivo de brochura existente que está sendo removido, precisamos excluir o arquivo existente. No entanto, só queremos fazer isso se a atualização for concluída sem criar uma exceção.
  • Se o SelectedValue for 3, queremos garantir que o utilizador fez o upload de um ficheiro PDF e depois guardá-lo no sistema de ficheiros e atualizar o valor da coluna do registo BrochurePath. Além disso, se houver um arquivo de brochura existente que está sendo substituído, precisamos excluir o arquivo anterior. No entanto, só queremos fazer isso se a atualização for concluída sem criar uma exceção.

As etapas necessárias para serem concluídas quando o RadioButtonList s SelectedValue é 3 são praticamente idênticas às usadas pelo manipulador de eventos DetailsView ItemInserting . Esse manipulador de eventos é executado quando um novo registro de categoria é adicionado a partir do controle DetailsView que adicionamos no tutorial anterior. Portanto, cabe a nós refatorar essa funcionalidade em métodos separados. Especificamente, desloquei a funcionalidade comum para dois métodos:

  • ProcessBrochureUpload(FileUpload, out bool) aceita como entrada uma instância de controle FileUpload e um valor booleano de saída que especifica se a operação de exclusão ou edição deve prosseguir ou se deve ser cancelada devido a algum erro de validação. Esse método retorna o caminho para o arquivo salvo ou null se nenhum arquivo foi salvo.
  • DeleteRememberedBrochurePath exclui o arquivo especificado pelo caminho na variável de página deletedCategorysPdfPath se deletedCategorysPdfPathnull não for.

Segue-se o código para estes dois métodos. Observe a semelhança entre ProcessBrochureUpload e o manipulador de eventos DetailsView do ItemInserting tutorial anterior. Neste tutorial, atualizei os manipuladores de eventos DetailsView para usar esses novos métodos. Baixe o código associado a este tutorial para ver as modificações nos manipuladores de eventos 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 GridView, RowUpdating e RowUpdated, 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 valor da propriedade BrochureOptionsSelectedValue 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 de interrupção nos RowUpdating manipuladores de eventos e RowUpdated para ter uma noção do fluxo de trabalho.

Passo 7: Carregar uma nova imagem

A Picture interface de edição de ImageField é renderizada como uma caixa de texto preenchida com o valor da sua propriedade DataImageUrlField. Durante o fluxo de trabalho de edição, o GridView passa um parâmetro para o ObjectDataSource com o nome do parâmetro s o valor da propriedade ImageField s DataImageUrlField e o valor do parâmetro s o valor 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 contém DataImageUrlField o URL completo da imagem. Com tais circunstâncias, a interface de edição exibe o URL da imagem na caixa de texto, que o usuário pode alterar e ter salvo de volta no banco de dados. Concedido, esta interface padrão não permite que o utilizador carregue uma nova imagem, mas permite que ele altere o URL da imagem, passando do valor atual para outro. Para este tutorial, no entanto, a interface de edição padrão do ImageField não é suficiente, porque os dados binários Picture são armazenados diretamente no banco de dados e a propriedade DataImageUrlField contém apenas 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 CategoryID 10, fazendo com que o Picture ImageField seja renderizado como uma caixa de texto com o valor 10. Imagine que o usuário altera o valor nesta caixa de texto para 50 e clica no botão Atualizar. Um postback ocorre e o GridView inicialmente cria 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 resumo, a interface de edição do ImageField não afeta o fluxo de trabalho de edição deste tutorial porque os nomes da propriedade do DataImageUrlField ImageField e o valor da grelha DataKey são a mesma coisa.

Embora o ImageField facilite a exibição de uma imagem com base em dados de 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 estes tutoriais decidimos exigir que cada categoria tenha uma imagem. Portanto, não precisamos deixar o usuário indicar 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ê-lo em um TemplateField. Na tag inteligente do GridView, clique no link Editar colunas, selecione o ImageField e clique no link Converter este campo num TemplateField.

Converter o ImageField 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, o ItemTemplate contém um controle Web de Imagem cuja propriedade ImageUrl é atribuída usando sintaxe de vinculação de dados com base nas propriedades DataImageUrlField e DataImageUrlFormatString do ImageField. O EditItemTemplate contém um TextBox cuja Text propriedade está vinculada ao valor especificado pela DataImageUrlField propriedade.

<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 FileUpload. Na marca inteligente do GridView, clique no link Editar modelos e selecione o Picture TemplateField s EditItemTemplate na lista suspensa. No modelo, deverá encontrar uma caixa de texto; remova-a. 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 para o template também.

Adicionar um controle FileUpload ao EditItemTemplate

Figura 17: Adicionar um controlo FileUpload ao EditItemTemplate (Clique para visualizar a imagem em tamanho real)

Depois de personalizar a interface de edição, visualize seu progresso em um navegador. Ao visualizar uma linha no modo somente leitura, a imagem da categoria é mostrada como era antes, mas ao clicar no botão de edição, a coluna da imagem é renderizada como texto com um controlo de carregamento de ficheiros.

A interface de edição inclui um controle FileUpload

Figura 18: A interface de edição inclui um controle FileUpload (Clique para visualizar a imagem em tamanho real)

Lembre-se de que o ObjectDataSource está configurado para chamar o método da classe CategoriesBLL que aceita como entrada os dados binários da imagem como uma matriz UpdateCategory. Se essa matriz for Nothing, no entanto, a sobrecarga alternativa UpdateCategory será chamada, o que emitirá a instrução SQL UPDATE que não modifica a coluna Picture, deixando a imagem atual da categoria intacta. Portanto, no manipulador de eventos GridView RowUpdating , precisamos referenciar programaticamente o PictureUpload controle FileUpload e determinar se um arquivo foi carregado. Se um não foi carregado, então não precisamos especificar um valor para o picture parâmetro. Por outro lado, se um arquivo foi carregado no PictureUpload controle FileUpload, queremos garantir que seja um arquivo JPG. Se for, então podemos enviar seu conteúdo binário para o ObjectDataSource através do picture parâmetro.

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 manipulador de ItemInserting eventos para usar esse método.

Adicione o seguinte código ao início do manipulador de eventos GridView RowUpdating . É importante que este código venha antes do código que salva o arquivo de brochura, 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ó é chamado se um arquivo de imagem for carregado. Se nenhum arquivo for carregado, o parâmetro picture não será definido e, portanto, usará seu valor padrão de Nothing. Se uma imagem foi carregada e ValidPictureUpload retorna True, são atribuídos ao parâmetro picture 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 código do método ValidPictureUpload(FileUpload), que foi refatorado do manipulador de eventos do DetailsView ItemInserting, é o seguinte:

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 das categorias originais por JPGs

Lembre-se de que as imagens originais de oito categorias são arquivos bitmap encapsulados em um cabeçalho OLE. Agora que adicionamos a capacidade de editar uma imagem de registro existente, reserve um momento para substituir esses bitmaps por JPGs. Se quiser continuar a usar as imagens de categoria atuais, você pode convertê-las em JPGs executando as seguintes etapas:

  1. Salve as imagens bitmap no disco rígido. Visite a UpdatingAndDeleting.aspx página no seu navegador e, para cada uma das oito primeiras categorias, clique com o botão direito do rato na imagem e opte por guardar a imagem.
  2. Abra a imagem no editor de imagens de sua escolha. Você pode usar o Microsoft Paint, por exemplo.
  3. Salve o bitmap como uma imagem JPG.
  4. Atualize a imagem da categoria através 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á removendo os primeiros 78 bytes das imagens das oito primeiras categorias. Corrija isso removendo o código que executa a remoção de 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 interfaces de inserção e edição da página UpdatingAndDeleting.aspx poderiam ser melhoradas com um pouco mais de trabalho. Os CategoryName e Description BoundFields no DetailsView e GridView devem ser convertidos para TemplateFields. Como CategoryName não permite NULL valores, um RequiredFieldValidator deve ser adicionado. E o Description TextBox provavelmente deveria ser convertido numa TextBox de várias linhas. Deixo estes retoques finais como um exercício para vocês.

Resumo

Este tutorial completa 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-o para o servidor web, onde pode ser armazenado no sistema de arquivos ou inserido no banco de dados. ASP.NET 2.0 inclui um controle FileUpload que torna o fornecimento de tal interface tão fácil quanto arrastar e soltar. No entanto, como observado no tutorial Upload de arquivos , o controle FileUpload só é adequado para uploads de arquivos relativamente pequenos, idealmente não excedendo um megabyte. Também exploramos como associar os dados carregados ao modelo de dados subjacente, bem como editar e excluir os dados binários dos 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, pegando os resultados de operações caras e armazenando-os em um local que pode ser acessado mais rapidamente.

Feliz Programação!

Sobre o Autor

Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.

Um agradecimento especial a

Esta série de tutoriais foi revisada por muitos revisores úteis. A revisor principal deste tutorial foi Teresa Murphy. Interessado em rever meus próximos artigos do MSDN? Se for o caso, envie-me uma mensagem para mitchell@4GuysFromRolla.com.