Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
por Scott Mitchell
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:
- 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.
- Invoque o método.
- 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.
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.
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
.
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.
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.
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 false
como ; 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
.
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.
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.
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 denull
-
ExceptionHandled
um valor Booleano que indica se a exceção foi manipulada ou não noRowUpdated
manipulador de eventos; sefalse
(o padrão), a exceção é gerada novamente, atingindo o runtime ASP.NET -
KeepInEditMode
se definido comotrue
a linha GridView editada permanece no modo de edição; sefalse
(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).
Figura 9: O ProductName
BoundField deve conter um valor (clique para exibir a imagem em tamanho real)
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 ProductsBLL
UpdateProduct
. 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 ApplicationException
Message
. 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.
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.