Compartilhar via


Atualizar e excluir dados binários existentes (C#)

por Scott Mitchell

Baixar 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 possibilita editar e excluir dados binários, independentemente de esses dados binários serem 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 de 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 um link de download para o folheto, com a imagem da categoria mostrada em um <img> elemento e adicionamos um DetailsView para permitir que os usuários adicionem uma nova categoria e carreguem 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 tem métodos , Updatee gerados Insertautomaticamente, mas esses métodos foram gerados com base na CategoriesTableAdapter consulta s main, que não inclui a PictureDelete coluna. 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 Conjunto de Dados Digitado e, no Designer, clique com o botão direito do CategoriesTableAdapter mouse no cabeçalho s e escolha Adicionar Consulta no menu de contexto 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.

Selecione a opção UPDATE

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

Agora precisamos especificar a UPDATE instrução SQL. O assistente sugere automaticamente uma instrução UPDATE correspondente à consulta main do TableAdapter (aquela que atualiza os CategoryNamevalores , Descriptione 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 UpdateWithPicture e clique em Concluir.

Nomeie o novo método TableAdapter UpdateWithPicture

Figura 2: nomeie o novo método UpdateWithPicture TableAdapter (clique para exibir a imagem em tamanho real)

Etapa 2: Adicionar 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. Esses 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 bool DeleteCategory(int categoryID)
{
    int rowsAffected = Adapter.Delete(categoryID);
    // Return true if precisely one row was deleted, otherwise false
    return rowsAffected == 1;
}

Para este tutorial, vamos criar dois métodos para atualizar uma categoria : um que espera os dados de imagem binária e invoca o UpdateWithPicture método que acabamos de adicionar ao CategoriesTableAdapter e outro que aceita apenas os CategoryNamevalores , Descriptione BrochurePath e e usa CategoriesTableAdapter a instrução gerada Update automaticamente da 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, 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, também precisamos fornecer essas informações. Isso exigiria uma viagem extra ao banco de dados para trazer de volta os dados de imagem do registro que está sendo editado. Portanto, queremos dois UPDATE métodos. A Camada lógica de negócios determinará qual deles 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 int como seus parâmetros de entrada; o segundo, apenas três string s e um int. Os string parâmetros de entrada são para o nome, a descrição e o caminho do arquivo de folheto da categoria, a byte matriz é para o conteúdo binário da imagem da categoria e identifica o intCategoryID do registro a ser atualizado. Observe que a primeira sobrecarga invocará a segunda se a matriz passada byte for null:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateCategory(string categoryName, string description, 
    string brochurePath, byte[] picture, int categoryID)
{
    // If no picture is specified, use other overload
    if (picture == null)
        return UpdateCategory(categoryName, description, brochurePath, categoryID);
    // Update picture, as well
    int rowsAffected = Adapter.UpdateWithPicture
        (categoryName, description, brochurePath, picture, categoryID);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateCategory(string categoryName, string description, 
    string brochurePath, int categoryID)
{
    int rowsAffected = Adapter.Update
        (categoryName, description, brochurePath, categoryID);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

Etapa 3: Copiar sobre 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 a edição e a exclusão do suporte. Em vez de continuar a trabalhar com UploadInDetailsView.aspxo , 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 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 UploadInDetailsView.aspx classe code-behind da página para UpdatingAndDeleting.aspx.

Copiar a Marcação Declarativa do UploadInDetailsView.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 do usuário com UploadInDetailsView.aspx a página do tutorial anterior.

Etapa 4: adicionando suporte à 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 tick de uma caixa de seleção se a fonte de dados subjacente da grade der suporte à exclusão. Atualmente, o ObjectDataSource ao qual GridView está associado (CategoriesDataSource) não dá suporte à exclusão.

Para corrigir isso, clique na opção Configurar Fonte de Dados da marca inteligente ObjectDataSource para iniciar o assistente. A primeira tela mostra que ObjectDataSource está configurado para funcionar com a CategoriesBLL classe . Pressione Avançar. Atualmente, apenas as propriedades e SelectMethod ObjectDataSource InsertMethod são especificadas. No entanto, o assistente preencheu automaticamente as listas suspensas nas guias UPDATE e DELETE com os UpdateCategory métodos 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 (Nenhum), mas deixe a lista suspensa da guia DELETE definida como DeleteCategory. Retornaremos a esse assistente na Etapa 6 para adicionar suporte de atualização.

Configurar o ObjectDataSource para usar o método DeleteCategory

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 de controles da 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 original_{0}ObjectDataSource como OldValuesParameterFormatString , 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 da inserção, atualização e exclusão de dados .

Depois de concluir o assistente e corrigir o OldValuesParameterFormatString, a marcação declarativa do 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 recursos de exclusão ao GridView marcando a caixa de seleção Habilitar Exclusão da marca inteligente GridView. Isso adicionará um CommandField ao GridView cuja ShowDeleteButton propriedade está definida como true.

Habilitar o suporte para exclusão no GridView

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 Products tabelas e CategoryID as Categories tabelas, CategoryIDportanto, você receberá uma exceção de violação de restrição de chave estrangeira se tentar excluir qualquer uma das oito primeiras 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 chamado Test.pdf e uma imagem de teste. A Figura 7 mostra o GridView após a adição da categoria de teste.

Adicionar uma categoria de teste com um folheto e uma imagem

Figura 6: Adicionar uma categoria de teste com um folheto e uma imagem (clique para exibir 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 exibir a imagem em tamanho real)

No Visual Studio, atualize o Gerenciador de Soluções. Agora você deve ver um novo arquivo na ~/Brochures pasta Test.pdf (consulte 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 classe seja DeleteCategory acionado. Isso invocará o método da DAL, Delete fazendo com que a instrução apropriada DELETE seja enviada ao banco de dados. Em seguida, os dados são recuperados para o GridView e a marcação é enviada de volta ao cliente com a Categoria de Teste não mais presente.

Embora o fluxo de trabalho de exclusão tenha removido com êxito o registro categoria de teste da Categories tabela, 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á sentado 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 folheto de categoria excluída

Uma das desvantagens de armazenar dados binários externos ao banco de dados é que etapas extras devem ser executadas para limpo esses arquivos quando o registro de banco de dados associado for excluído. GridView e ObjectDataSource fornecem eventos que são disparados antes e depois que o comando delete é 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 GridView é RowDeleting acionado antes que o comando de exclusão objectDataSource seja 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 
string deletedCategorysPdfPath = null;
protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    // Determine the PDF path for the category being deleted...
    int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    if (category.IsBrochurePathNull())
        deletedCategorysPdfPath = null;
    else
        deletedCategorysPdfPath = category.BrochurePath;
}
protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    // Delete the brochure file if there were no problems deleting the record
    if (e.Exception == null)
    {
        // Is there a file to delete?
        if (deletedCategorysPdfPath != null)
        {
            System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
        }
    }
}

RowDeleting No manipulador de eventos, o CategoryID da linha que está sendo excluída é capturado da coleção GridViewDataKeys, que pode ser acessada nesse manipulador de eventos por meio da e.Keys coleção. Em seguida, as CategoriesBLL classes s GetCategoryByCategoryID(categoryID) são invocadas para retornar informações sobre o registro que está sendo excluído. Se o objeto retornado CategoriesDataRow tiver um valor diferente,NULL``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 BrochurePath detalhes do Categories registro que está sendo excluído no RowDeleting manipulador de eventos, poderíamos ter adicionado alternativamente o BrochurePath à propriedade gridView e DataKeyNames acessado o valor do registro por meio da e.Keys coleção. 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 objectDataSource tiver sido invocado, o manipulador de eventos gridView RowDeleted 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 limpo os dados binários da categoria associados à sua imagem. Isso ocorre porque os dados da 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 esse 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 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 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 UpdateCategory sobrecarga que aceita quatro parâmetros de entrada (para todas as colunas, menos Picture). Altere isso para que ele use a sobrecarga com cinco parâmetros.

Configurar o ObjectDataSource para usar o método UpdateCategory que inclui um parâmetro para imagem

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 sua UpdateMethod propriedade, bem como s correspondentes UpdateParameter . 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 à 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, marcar a opção Habilitar Edição da marca inteligente gridView. Isso definirá a propriedade trueCommandField como ShowEditButton , 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 dar suporte à edição

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 Editar da linha. 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 ItemTemplate um link para o folheto. O Picture ImageField é renderizado como uma TextBox cuja Text propriedade recebe o valor do valor de DataImageUrlField ImageField, nesse 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 exibir imagem em tamanho real)

Personalizando aBrochurePathinterface de edição s

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

  • Deixe o folheto da categoria no estado em que se encontra,
  • Atualizar o folheto da categoria carregando um novo folheto ou
  • Remova completamente o folheto da categoria (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 marca inteligente GridView, clique no link Editar Modelos e selecione TemplateField BrochurePath s EditItemTemplate na lista suspensa. Adicione um controle Web RadioButtonList a esse modelo, definindo sua ID propriedade como BrochureOptions e sua AutoPostBack propriedade como true. No janela Propriedades, clique nas reticências na Items propriedade , que abrirá o ListItem Editor Coleção. Adicione as três opções a seguir com Value s 1, 2 e 3, respectivamente:

  • Usar o folheto atual
  • Remover o folheto atual
  • Carregar novo folheto

Defina a primeira ListItem propriedade s Selected como true.

Adicionar Três ListItems ao RadioButtonList

Figura 12: Adicionar três ListItem s ao RadioButtonList

Abaixo do RadioButtonList, adicione um controle FileUpload chamado BrochureUpload. Defina sua Visible propriedade como false.

Adicionar um controle RadioButtonList e FileUpload ao EditItemTemplate

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 void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e)
{
    // Get a reference to the RadioButtonList and its Parent
    RadioButtonList BrochureOptions = (RadioButtonList)sender;
    Control parent = BrochureOptions.Parent;
    // Now use FindControl("controlID") to get a reference of the 
    // FileUpload control
    FileUpload BrochureUpload = 
        (FileUpload)parent.FindControl("BrochureUpload");
    // Only show BrochureUpload if SelectedValue = "3"
    BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3");
}

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 SelectedIndexChanged manipulador de eventos é passado uma referência do RadioButtonList no sender parâmetro de entrada. Para obter o controle FileUpload, precisamos obter o controle pai do RadioButtonList e usar o FindControl("controlID") método de lá. Depois que tivermos uma referência aos controles RadioButtonList e FileUpload, a propriedade do Visible controle FileUpload será definida true como somente se o RadioButtonList for SelectedValue igual a 3, que é o Value para o folheto ListItemCarregar novo .

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. Alterar o índice selecionado causa 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.

Inicialmente, a opção Usar folheto atual é Selecionada

Figura 14: Inicialmente, a opção Usar folheto atual é Selecionada (Clique para exibir a imagem em tamanho real)

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

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 Atualização do GridView é clicado, seu RowUpdating evento é acionado. O comando de atualização objectDataSource é invocado e, em seguida, o evento GridView é RowUpdated acionado. Assim como acontece com o fluxo de trabalho de exclusão, precisamos criar manipuladores de eventos para ambos os eventos. RowUpdating No manipulador de eventos, precisamos determinar qual ação tomar com base no SelectedValue de BrochureOptions RadioButtonList:

  • Se for SelectedValue 1, queremos continuar usando a mesma BrochurePath configuração. Portanto, precisamos definir o parâmetro ObjectDataSource para brochurePath o valor existente BrochurePath do registro que está sendo atualizado. O parâmetro ObjectDataSource pode brochurePath ser definido usando e.NewValues["brochurePath"] = value.
  • Se for SelectedValue 2, queremos definir o valor do BrochurePath registro como NULL. Isso pode ser feito definindo o parâmetro ObjectDataSource como brochurePath , o que resulta em um banco de dados NULL sendo usado na UPDATE instrução .Nothing Se houver um arquivo de folheto existente que está sendo removido, precisamos excluir o arquivo existente. No entanto, só queremos fazer isso se a atualização for concluída sem gerar uma exceção.
  • Se for SelectedValue 3, queremos garantir que o usuário tenha carregado um arquivo PDF e, em seguida, salve-o no sistema de arquivos e atualize o valor da coluna do BrochurePath registro. 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 RadioButtonList s SelectedValue for 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 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 ou null se nenhum arquivo foi salvo.
  • DeleteRememberedBrochurePath exclui o arquivo especificado pelo caminho na variável deletedCategorysPdfPath de página se deletedCategorysPdfPath não nullfor .

O código para esses dois métodos segue. Observe a similaridade entre ProcessBrochureUpload e o manipulador de ItemInserting eventos 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 detailsView.

private string ProcessBrochureUpload
    (FileUpload BrochureUpload, out bool CancelOperation)
{
    CancelOperation = false;    // by default, do not cancel operation
    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;
            CancelOperation = true;
            return null;
        }
        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));
        return brochurePath;
    }
    else
    {
        // No file uploaded
        return null;
    }
}
private void DeleteRememberedBrochurePath()
{
    // Is there a file to delete?
    if (deletedCategorysPdfPath != null)
    {
        System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
    }
}

Os manipuladores de RowUpdating eventos e RowUpdated GridView usam os ProcessBrochureUpload métodos e DeleteRememberedBrochurePath , como mostra o seguinte código:

protected void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    // Reference the RadioButtonList
    RadioButtonList BrochureOptions = 
        (RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions");
    // Get BrochurePath information about the record being updated
    int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    if (BrochureOptions.SelectedValue == "1")
    {
        // Use current value for BrochurePath
        if (category.IsBrochurePathNull())
            e.NewValues["brochurePath"] = null;
        else
            e.NewValues["brochurePath"] = category.BrochurePath;
    }
    else if (BrochureOptions.SelectedValue == "2")
    {
        // Remove the current brochure (set it to NULL in the database)
        e.NewValues["brochurePath"] = null;
    }
    else if (BrochureOptions.SelectedValue == "3")
    {
        // Reference the BrochurePath FileUpload control
        FileUpload BrochureUpload = 
            (FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload");
        // Process the BrochureUpload
        bool cancelOperation = false;
        e.NewValues["brochurePath"] = 
            ProcessBrochureUpload(BrochureUpload, out cancelOperation);
        e.Cancel = cancelOperation;
    }
    else
    {
        // Unknown value!
        throw new ApplicationException(
            string.Format("Invalid BrochureOptions value, {0}", 
                BrochureOptions.SelectedValue));
    }
    if (BrochureOptions.SelectedValue == "2" || 
        BrochureOptions.SelectedValue == "3")
    {
        // "Remember" that we need to delete the old PDF file
        if (category.IsBrochurePathNull())
            deletedCategorysPdfPath = null;
        else
            deletedCategorysPdfPath = category.BrochurePath;
    }
}
protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    // If there were no problems and we updated the PDF file, 
    // then delete the existing one
    if (e.Exception == null)
    {
        DeleteRememberedBrochurePath();
    }
}

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 Picture interface de edição imagefield é renderizada como uma caixa de texto preenchida com o valor de sua DataImageUrlField propriedade. Durante o fluxo de trabalho de edição, o GridView passa um parâmetro para ObjectDataSource com o nome do parâmetro o valor da propriedade ImageField s DataImageUrlField e o valor do parâmetro que o valor inseriu 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 salvo de volta no banco de dados. Concedida, essa interface padrão não permite que o usuário carregue uma nova imagem, mas permite que ele altere a URL da imagem do valor atual para outra. 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 chamado CategoryID com o valor 50. No entanto, antes que o GridView envie esse parâmetro (e os CategoryName parâmetros e Description ), ele adiciona os valores da DataKeys coleção. Portanto, ele substitui o CategoryID parâmetro pelo valor subjacente CategoryID da linha atual, 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 e DataImageUrlField do valor da DataKey grade são um no mesmo.

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 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 que o usuário possa carregar uma nova imagem ou deixar a imagem atual como está.

Para personalizar a interface de edição do ImageField, precisamos convertê-la em um TemplateField. Na marca inteligente GridView, clique no link Editar Colunas, selecione ImageField e clique no link Converter este campo em um 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 image cuja ImageUrl propriedade é atribuída usando a sintaxe de vinculação de dados com base nas propriedades e DataImageUrlFormatString do DataImageUrlField ImageField. O EditItemTemplate contém um TextBox cuja Text propriedade está associada 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 GridView, clique no link Editar Modelos e selecione TemplateField Picture s EditItemTemplate na lista suspensa. No modelo, você deve ver um TextBox remover isso. 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 igual, deixe o campo vazio para o modelo também.

Adicionar um controle FileUpload ao EditItemTemplate

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

Depois de personalizar a interface de edição, exiba seu progresso em um navegador. Ao exibir uma linha no modo somente leitura, a imagem da categoria é mostrada como era antes, mas clicar no botão Editar renderiza a coluna de imagem como texto com um controle FileUpload.

A interface de edição inclui um controle FileUpload

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

Lembre-se de que ObjectDataSource está configurado para chamar o CategoriesBLL método de classe s UpdateCategory que aceita como entrada os dados binários para a imagem como uma byte matriz. No entanto, se essa matriz tiver um null valor, a sobrecarga alternativa UpdateCategory será chamada, o que emitirá a UPDATE instrução SQL que não modifica a Picture coluna, deixando 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 for, poderemos enviar seu conteúdo binário para 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 ItemInserting eventos DetailsView. Portanto, refatorei a funcionalidade comum em um novo método e ValidPictureUploadatualizei o ItemInserting manipulador de eventos para usar esse método.

Adicione o código a seguir ao início do manipulador de eventos GridView RowUpdating . É 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
FileUpload PictureUpload = 
    (FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
    // Make sure the picture upload is valid
    if (ValidPictureUpload(PictureUpload))
    {
        e.NewValues["picture"] = PictureUpload.FileBytes;
    }
    else
    {
        // Invalid file upload, cancel update and exit event handler
        e.Cancel = true;
        return;
    }
}

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 null. Se uma imagem foi carregada e ValidPictureUpload retorna , o picture parâmetro 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 trueeventos é encerrado.

O ValidPictureUpload(FileUpload) código do método, que foi refatorado do manipulador de eventos DetailsView ItemInserting , segue:

private bool ValidPictureUpload(FileUpload PictureUpload)
{
    // 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;
        return false;
    }
    else
    {
        return true;
    }
}

Etapa 8: Substituindo as imagens de 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, tire um momento para substituir esses bitmaps por JPGs. Se você quiser continuar a usar as imagens de categoria atuais, poderá convertê-las em JPGs executando as seguintes etapas:

  1. Salve as imagens de bitmap no disco rígido. Visite a UpdatingAndDeleting.aspx pá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.
  2. Abra a imagem no editor de imagens de sua escolha. Você pode usar Microsoft Paint, por exemplo.
  3. Salve o bitmap como uma imagem JPG.
  4. 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 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];
    // 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);
}

Observação

As UpdatingAndDeleting.aspx interfaces de inserção e edição da página podem usar um pouco mais de trabalho. O CategoryName e Description BoundFields em 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 estes retoques finais como um exercício para você.

Resumo

Este tutorial conclui nossa análise de 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 em seu disco rígido e carregando-os no servidor Web, onde eles 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 Carregando 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.

Programação feliz!

Sobre o autor

Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.

Agradecimentos Especiais

Esta série de tutoriais foi revisada por muitos revisores úteis. A revisora principal deste tutorial foi Teresa Murphy. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.