Compartilhar via


Tratar de exceções de nível BLL e DAL em uma página do ASP.NET (VB)

por Scott Mitchell

Baixar PDF

Neste tutorial, veremos como exibir uma mensagem de erro informativa e amigável caso ocorra uma exceção durante uma operação de inserção, atualização ou exclusão de um controle web de dados ASP.NET.

Introdução

Trabalhar com dados de um aplicativo Web ASP.NET usando uma arquitetura de aplicativo em camadas envolve as três etapas gerais a seguir:

  1. Determine qual método da Camada de Lógica de Negócios precisa ser invocado e quais valores de parâmetros passar. Os valores de parâmetro podem ser codificados, atribuídos programaticamente ou entradas inseridas pelo usuário.
  2. Invoque o método.
  3. Processe os resultados. Ao chamar um método BLL que retorna dados, isso pode envolver a associação dos dados a um controle da Web de dados. Para métodos BLL que modificam dados, isso pode incluir a execução de alguma ação com base em um valor retornado ou manipulando normalmente qualquer exceção que surgiu na Etapa 2.

Como vimos no tutorial anterior, tanto o ObjectDataSource quanto os controles da Web de dados fornecem pontos de extensibilidade para as Etapas 1 e 3. O GridView, por exemplo, dispara seu RowUpdating evento antes de atribuir seus valores de campo à coleção objectDataSource UpdateParameters ; seu RowUpdated evento é gerado após o ObjectDataSource concluir a operação.

Já examinamos os eventos que são acionados durante a Etapa 1 e vimos como eles podem ser usados para personalizar os parâmetros de entrada ou cancelar a operação. Neste tutorial, vamos focar nos eventos que são acionados após a finalização da operação. Com esses manipuladores de eventos de nível de postagem, podemos, entre outras coisas, determinar se ocorreu uma exceção durante a operação e lidar com ela de forma adequada, exibindo uma mensagem de erro amigável e informativa na tela, em vez de utilizar a página de exceção padrão do ASP.NET.

Para ilustrar o trabalho com esses eventos de pós-nível, vamos criar uma página que lista os produtos em um GridView editável. Ao atualizar um produto, se uma exceção for gerada, nossa página ASP.NET exibirá uma mensagem curta acima do GridView explicando que ocorreu um problema. Vamos começar!

Etapa 1: Criando um GridView editável de produtos

No tutorial anterior, criamos um GridView editável com apenas dois campos, ProductName e UnitPrice. Isso exigia a criação de uma sobrecarga adicional para o ProductsBLL método da UpdateProduct classe, que aceitava apenas três parâmetros de entrada (o nome do produto, o preço unitário e a ID) em vez de um parâmetro para cada campo de produto. Para este tutorial, vamos praticar essa técnica novamente, criando um GridView editável que exibe o nome do produto, a quantidade por unidade, o preço unitário e as unidades em estoque, mas só permite que o nome, o preço unitário e as unidades em estoque sejam editados.

Para acomodar esse cenário, precisaremos de outra sobrecarga do UpdateProduct método, uma que aceite quatro parâmetros: o nome do produto, o preço unitário, as unidades em estoque e a ID. Adicione o seguinte método à classe ProductsBLL:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct _
    (ByVal productName As String, ByVal unitPrice As Nullable(Of Decimal), _
ByVal unitsInStock As Nullable(Of Short), ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

Com esse método concluído, estamos prontos para criar a página ASP.NET que permite editar esses quatro campos de produto específicos. Abra a ErrorHandling.aspx página na EditInsertDelete pasta e adicione um GridView à página por meio do Designer. Associe o GridView a um novo ObjectDataSource, mapeando o Select() método para o ProductsBLL método da GetProducts() classe e o Update() método para a UpdateProduct sobrecarga recém-criada.

Usar a sobrecarga do método UpdateProduct que aceita quatro parâmetros de entrada

Figura 1: Use a sobrecarga do método UpdateProduct que aceita quatro parâmetros de entrada (clique para exibir a imagem em tamanho real)

Isso criará um ObjectDataSource com uma UpdateParameters coleção com quatro parâmetros e um GridView com um campo para cada um dos campos do produto. A marcação declarativa do ObjectDataSource atribui à propriedade OldValuesParameterFormatString o valor original_{0}, o que causará uma exceção, pois nossa classe BLL não espera receber um parâmetro de entrada denominado original_productID. Não se esqueça de remover essa configuração completamente da sintaxe declarativa (ou defina-a como o valor {0}padrão).

Em seguida, reduza o GridView para incluir apenas os ProductName, QuantityPerUnit, UnitPrice e UnitsInStock BoundFields. Também fique à vontade para aplicar qualquer formatação no nível do campo que você considerar necessária (como alterar as HeaderText propriedades).

No tutorial anterior, analisamos como formatar o UnitPrice BoundField como uma moeda no modo somente leitura e no modo de edição. Vamos fazer o mesmo aqui. Lembre-se de que isso exigiu definir a propriedade BoundField DataFormatString como {0:c}, sua propriedade HtmlEncode como false, e sua propriedade ApplyFormatInEditMode como true, conforme mostrado na Figura 2.

Configurar o UnitPrice BoundField para exibição como uma moeda

Figura 2: Configurar o UnitPrice BoundField para exibir como uma moeda (clique para exibir a imagem em tamanho real)

Formatar o UnitPrice como moeda na interface de edição requer a criação de um manipulador de eventos para o evento GridView RowUpdating que analisa a cadeia de caracteres formatada como moeda em um valor decimal. Lembre-se de que o RowUpdating manipulador de eventos do último tutorial também foi verificado para garantir que o usuário forneceu um UnitPrice valor. No entanto, para este tutorial, vamos permitir que o usuário omita o preço.

Protected Sub GridView1_RowUpdating(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) _
    Handles GridView1.RowUpdating
    If e.NewValues("UnitPrice") IsNot Nothing Then
        e.NewValues("UnitPrice") = _
            Decimal.Parse(e.NewValues("UnitPrice").ToString(), _
            System.Globalization.NumberStyles.Currency)
    End If

Nosso GridView inclui um QuantityPerUnit BoundField, mas esse BoundField deve ser apenas para fins de exibição e não deve ser editável pelo usuário. Para organizar isso, basta definir a propriedade BoundFields ReadOnly como true.

Tornar o QuantityPerUnit BoundField somente leitura

Figura 3: Configure o QuantityPerUnit BoundField Read-Only (Clique para exibir a imagem em tamanho real)

Por fim, marque a caixa de seleção Enable Editing na tag inteligente do GridView. Depois de concluir essas etapas, o Designer da página ErrorHandling.aspx deve estar semelhante à Figura 4.

Remover todos, exceto os BoundFields necessários, e marcar a caixa de seleção Habilitar Edição

Figura 4: Remova todos exceto os BoundFields necessários e verifique a caixa de seleção habilitar edição (clique para exibir a imagem em tamanho real)

Neste ponto, temos uma lista de todos os campos dos produtos: ProductName, QuantityPerUnit, UnitPrice e UnitsInStock; no entanto, somente os campos ProductName, UnitPrice e UnitsInStock podem ser editados.

Os usuários agora podem editar facilmente os nomes, os preços e as unidades dos produtos em campos de estoque

Figura 5: Os usuários agora podem editar facilmente os nomes, os preços e as unidades dos produtos em campos de estoque (clique para exibir a imagem em tamanho real)

Etapa 2: Manipulando elegantemente exceções de DAL-Level

Embora nosso GridView editável funcione maravilhosamente quando os usuários inserem valores legais para o nome, o preço e as unidades do produto editado em estoque, inserir valores ilegais resulta em uma exceção. Por exemplo, omitir o ProductName valor faz com que um NoNullAllowedException seja gerado, uma vez que a ProductName propriedade na ProductsRow classe tem sua AllowDBNull propriedade definida falsecomo ; se o banco de dados estiver inoperante, um SqlException será gerado pelo TableAdapter ao tentar se conectar ao banco de dados. Sem tomar nenhuma ação, essas exceções se propagam da Camada de Acesso a Dados para a Camada de Lógica de Negócios, depois para a página ASP.NET e, finalmente, para o tempo de execução do ASP.NET.

Dependendo de como seu aplicativo Web está configurado e se você está visitando ou não o aplicativo localhost, uma exceção sem tratamento pode resultar em uma página de erro de servidor genérico, um relatório de erro detalhado ou uma página da Web amigável. Consulte o Tratamento de Erros de Aplicativo Web em ASP.NET e o Elemento CustomErrors para obter mais informações sobre como o runtime ASP.NET responde a uma exceção não tratada.

A Figura 6 mostra a tela encontrada ao tentar atualizar um produto sem especificar o ProductName valor. Este é o relatório detalhado de erros padrão exibido ao acessar através de localhost.

Omitir o nome do produto exibirá detalhes da exceção

Figura 6: Omitir o nome do produto exibirá detalhes da exceção (clique para exibir a imagem em tamanho real)

Embora esses detalhes de exceção sejam úteis ao testar um aplicativo, exibir essa tela para um usuário final diante de uma exceção é menos que o ideal. Um usuário final provavelmente não sabe o que é um NoNullAllowedException ou por que foi causado. Uma abordagem melhor é apresentar ao usuário uma mensagem mais amigável explicando que houve problemas ao tentar atualizar o produto.

Se ocorrer uma exceção ao executar a operação, os eventos de nível pós-processamento no ObjectDataSource e no controle de dados da Web oferecem um meio para detectá-la e interromper a exceção de propagar até o tempo de execução do ASP.NET. Para nosso exemplo, vamos criar um manipulador de eventos para o evento do RowUpdated GridView que determina se uma exceção foi acionada e, em caso afirmativo, exibe os detalhes da exceção em um controle Web de rótulo.

Comece adicionando um Rótulo à página ASP.NET, definindo sua propriedade ID para ExceptionDetails e limpando sua propriedade Text. Para chamar a atenção do usuário para essa mensagem, defina sua CssClass propriedade como Warning, que é uma classe CSS que adicionamos ao Styles.css arquivo no tutorial anterior. Lembre-se de que essa classe CSS faz com que o texto do Rótulo seja exibido em uma fonte vermelha, itálica, negrito, extra grande.

Adicionar um controle Web de rótulo à página

Figura 7: Adicionar um controle Web de rótulo à página (clique para exibir a imagem em tamanho real)

Como queremos que esse controle Web de rótulo fique visível somente imediatamente após a ocorrência de uma exceção, defina sua Visible propriedade como false no Page_Load manipulador de eventos:

Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    ExceptionDetails.Visible = False
End Sub

Com esse código, na primeira visita à página e nos postbacks subsequentes, o controle ExceptionDetails terá a propriedade Visible definida como false. Diante de uma exceção no nível DAL ou BLL, que podemos detectar no manipulador de eventos do GridView RowUpdated, definiremos a propriedade ExceptionDetails do controle Visible como verdadeira. Como os manipuladores de eventos de controle da Web ocorrem após o Page_Load manipulador de eventos no ciclo de vida da página, o Rótulo será mostrado. No entanto, no próximo postback, o Page_Load manipulador de eventos reverterá a propriedade Visible para false, ocultando-a da exibição novamente.

Observação

Como alternativa, poderíamos remover a necessidade de definir a propriedade ExceptionDetails do controle Visible em Page_Load atribuindo sua propriedade Visible como false na sintaxe declarativa e desabilitando seu estado de exibição (configurando sua propriedade EnableViewState como false). Usaremos essa abordagem alternativa em um tutorial futuro.

Com o controle Label adicionado, nossa próxima etapa é criar o manipulador de eventos para o evento do RowUpdated GridView. Selecione o GridView no Designer, vá para a janela Propriedades e clique no ícone de raio, listando os eventos do GridView. Já deve haver uma entrada para o evento do RowUpdating GridView, pois criamos um manipulador de eventos para esse evento anteriormente neste tutorial. Crie um manipulador de eventos para o RowUpdated evento também.

Criar um manipulador de eventos para o evento RowUpdated do GridView

Figura 8: Criar um manipulador de eventos para o evento do RowUpdated GridView

Observação

Você também pode criar o manipulador de eventos por meio das listas suspensas na parte superior do arquivo de classe code-behind. Selecione o GridView na lista suspensa à esquerda e o evento RowUpdated na lista à direita.

A criação desse manipulador de eventos adicionará o seguinte código à classe code-behind da página ASP.NET:

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
End Sub

O segundo parâmetro de entrada desse manipulador de eventos é um objeto do tipo GridViewUpdatedEventArgs, que tem três propriedades de interesse para lidar com exceções:

  • Exception uma referência à exceção gerada; se nenhuma exceção tiver sido gerada, essa propriedade terá um valor de null
  • ExceptionHandled um valor Booleano que indica se a exceção foi manipulada ou não no RowUpdated manipulador de eventos; se false (o padrão), a exceção é gerada novamente, atingindo o runtime ASP.NET
  • KeepInEditMode se definido como true a linha GridView editada permanece no modo de edição; se false (o padrão), a linha GridView retorna ao seu modo somente leitura

Nosso código, então, deve verificar se Exception não for null, significando que uma exceção foi gerada durante a realização da operação. Se esse for o caso, queremos:

  • Exibir uma mensagem amigável no ExceptionDetails Rótulo
  • Indique que a exceção foi tratada
  • Manter a linha GridView no modo de edição

Este código a seguir atinge estes objetivos:

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
    If e.Exception IsNot Nothing Then
        ExceptionDetails.Visible = True
        ExceptionDetails.Text = "There was a problem updating the product. "
        If e.Exception.InnerException IsNot Nothing Then
            Dim inner As Exception = e.Exception.InnerException
            If TypeOf inner Is System.Data.Common.DbException Then
                ExceptionDetails.Text &= _
                "Our database is currently experiencing problems." & _
                "Please try again later."
            ElseIf TypeOf inner _
             Is System.Data.NoNullAllowedException Then
                ExceptionDetails.Text += _
                    "There are one or more required fields that are missing."
            ElseIf TypeOf inner Is ArgumentException Then
                Dim paramName As String = CType(inner, ArgumentException).ParamName
                ExceptionDetails.Text &= _
                    String.Concat("The ", paramName, " value is illegal.")
            ElseIf TypeOf inner Is ApplicationException Then
                ExceptionDetails.Text += inner.Message
            End If
        End If
        e.ExceptionHandled = True
        e.KeepInEditMode = True
    End If
End Sub

Esse manipulador de eventos começa verificando se e.Exception é null. Se não for, a propriedade ExceptionDetails do Visible Rótulo é definida como true e a sua propriedade Text é definida como "Houve um problema ao atualizar o produto". Os detalhes da exceção real que foi gerada estão na propriedade e.Exception do objeto InnerException. Essa exceção interna é examinada e, se for de um tipo específico, uma mensagem adicional e útil será acrescentada à ExceptionDetails propriedade do Text Rótulo. Por fim, ambas as propriedades ExceptionHandled e KeepInEditMode são definidas como true.

A Figura 9 mostra uma captura de tela desta página ao omitir o nome do produto; A Figura 10 mostra os resultados ao inserir um valor ilegal UnitPrice (-50).

O ProductName BoundField deve conter um valor

Figura 9: O ProductName BoundField deve conter um valor (clique para exibir a imagem em tamanho real)

Valores de preço unitário negativos não são permitidos

Figura 10: Valores negativos UnitPrice não são permitidos (clique para exibir imagem em tamanho real)

Ao definir a e.ExceptionHandled propriedade como true, o RowUpdated manipulador de eventos indicou que ele lidou com a exceção. Portanto, a exceção não será propagada para o runtime ASP.NET.

Observação

As figuras 9 e 10 mostram uma maneira elegante de lidar com exceções geradas devido à entrada do usuário inválida. Idealmente, no entanto, tal entrada inválida nunca deve alcançar a Camada de Lógica de Negócios, pois a página ASP.NET deve garantir que as entradas do usuário sejam válidas antes de invocar o método da classe ProductsBLLUpdateProduct. Em nosso próximo tutorial, veremos como adicionar controles de validação às interfaces de edição e inserção para garantir que os dados enviados à Camada Lógica de Negócios estejam em conformidade com as regras de negócios. Os controles de validação não só impedem a invocação do UpdateProduct método até que os dados fornecidos pelo usuário sejam válidos, mas também fornecem uma experiência de usuário mais informativa para identificar problemas de entrada de dados.

Etapa 3: Gerenciando elegantemente exceções de BLL-Level

Ao inserir, atualizar ou excluir dados, a Camada de Acesso a Dados pode gerar uma exceção diante de um erro relacionado a dados. O banco de dados pode estar offline, uma coluna de tabela de banco de dados necessária pode não ter um valor especificado ou uma restrição no nível da tabela pode ter sido violada. Além de exceções estritamente relacionadas a dados, a Camada lógica de negócios pode usar exceções para indicar quando as regras de negócios foram violadas. No tutorial Criando uma Camada Lógica de Negócios, por exemplo, adicionamos uma verificação de regra de negócios à sobrecarga original UpdateProduct. Especificamente, se o usuário estava marcando um produto como descontinuado, exigimos que o produto não fosse o único fornecido por seu fornecedor. Se essa condição foi violada, uma ApplicationException foi lançada.

Para a UpdateProduct sobrecarga criada neste tutorial, vamos adicionar uma regra de negócios que proíbe que o campo UnitPrice seja definido como um novo valor que seja mais que o dobro do valor original UnitPrice. Para fazer isso, ajuste a UpdateProduct sobrecarga para que ela execute essa verificação e gere um ApplicationException caso de violação da regra. O método atualizado segue:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(ByVal productName As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
    ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    If unitPrice.HasValue AndAlso Not product.IsUnitPriceNull() Then
        If unitPrice > product.UnitPrice * 2 Then
            Throw New ApplicationException( _
                "When updating a product price," & _
                " the new price cannot exceed twice the original price.")
        End If
    End If
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

Com essa alteração, qualquer atualização de preço que seja mais do que o dobro do preço existente fará com que um ApplicationException seja gerado. Assim como a exceção gerada pelo DAL, a exceção gerada pela BLL ApplicationException pode ser detectada e manipulada no manipulador de eventos do GridView RowUpdated. Na verdade, o código do RowUpdated manipulador de eventos, conforme escrito, detectará corretamente essa exceção e exibirá o valor da propriedade ApplicationExceptionMessage. A Figura 11 mostra uma captura de tela quando um usuário tenta atualizar o preço de Chai para US$ 50,00, o que é mais que o dobro do preço atual de US$ 19,95.

As regras de negócios não permitem aumentos de preços que mais que o dobro do preço de um produto

Figura 11: As regras de negócios não permitem aumentos de preço que mais do que o dobro do preço de um produto (clique para exibir imagem em tamanho real)

Observação

Idealmente, nossas regras lógicas empresariais deveriam ser refatoradas para sair das sobrecargas do método UpdateProduct e serem movidas para um método comum. Isso é deixado como um exercício para o leitor.

Resumo

Durante as operações de inserção, atualização e exclusão, tanto o controle da Web de dados quanto o ObjectDataSource disparam eventos pré e pós-operação que envolvem a operação real. Como vimos neste tutorial e no anterior, ao trabalhar com um GridView editável, o evento RowUpdating do GridView é acionado, seguido pelo evento Updating do ObjectDataSource, momento em que o comando de atualização é feito para o objeto subjacente do ObjectDataSource. Após a conclusão da operação, o evento objectDataSource Updated é acionado, seguido pelo evento do RowUpdated GridView.

Podemos criar manipuladores de eventos para os eventos de pré-nível a fim de personalizar os parâmetros de entrada ou para os eventos pós-nível, a fim de inspecionar e responder aos resultados da operação. Os manipuladores de eventos pós-nível são mais comumente usados para detectar se ocorreu uma exceção durante a operação. Diante de uma exceção, esses manipuladores de eventos pós-nível podem, opcionalmente, lidar com a exceção por conta própria. Neste tutorial, vimos como lidar com essa exceção exibindo uma mensagem de erro amigável.

No próximo tutorial, veremos como diminuir a probabilidade de exceções decorrentes de problemas de formatação de dados (como inserir um negativo UnitPrice). Especificamente, veremos como adicionar controles de validação às interfaces de edição e inserção.

Divirta-se programando!

Sobre o autor

Scott Mitchell, autor de sete livros asp/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser alcançado em mitchell@4GuysFromRolla.com.

Agradecimentos Especiais a

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