Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
por Scott Mitchell
Neste tutorial, veremos como exibir uma mensagem de erro amigável e informativa caso ocorra uma exceção durante uma operação de inserção, atualização ou exclusão de um controle da Web de dados ASP.NET.
Introdução
Trabalhar com dados de um aplicativo Web ASP.NET usando uma arquitetura de aplicativo hierárquico 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âmetro devem ser passados. Os valores dos parâmetros podem ser codificados, atribuídos programaticamente ou entradas inseridas pelo usuário.
- Invoque o método.
- Processar os resultados. Ao chamar um método BLL que retorna dados, isso pode envolver a vinculaçã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 de retorno ou o tratamento normal de qualquer exceção que surgiu na Etapa 2.
Como vimos no tutorial anterior, os controles ObjectDataSource e da Web de dados fornecem pontos de extensibilidade para as Etapas 1 e 3. O GridView, por exemplo, aciona o seu RowUpdating
evento antes de atribuir os seus valores de campo à coleção do seu ObjectDataSource UpdateParameters
. O seu RowUpdated
evento é gerado após o ObjectDataSource ter concluído 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, iremos centrar a nossa atenção nos eventos que ocorrem após a conclusão da operação. Com esses manipuladores de eventos de pós-nível, podemos, entre outras coisas, determinar se uma exceção ocorreu durante a operação e tratá-la normalmente, exibindo uma mensagem de erro amigável e informativa na tela, em vez de usar como padrão a página de exceção ASP.NET padrão.
Para ilustrar o trabalho com esses eventos de pós-nível, vamos criar uma página que liste 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, criámos um GridView editável com apenas dois campos, ProductName
e UnitPrice
. Isso exigiu a criação de uma sobrecarga adicional para o ProductsBLL
método da classe, que aceitava apenas três parâmetros de entrada (nome do produto, preço unitário e ID) em oposição a um parâmetro para cada campo do UpdateProduct
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, que aceite quatro parâmetros: nome do produto, preço unitário, unidades em estoque e ID. Adicione o seguinte método à classe ProductsBLL
:
[System.ComponentModel.DataObjectMethodAttribute(
System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull();
else product.UnitsInStock = unitsInStock.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Com esse método concluído, estamos prontos para criar a página de ASP.NET que permite editar esses quatro campos específicos do produto. Abra a página ErrorHandling.aspx
na pasta EditInsertDelete
e adicione um GridView à página através do Designer. Vincule 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 de método que aceita quatro parâmetros de entrada (UpdateProduct
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 à OldValuesParameterFormatString
propriedade o valor original_{0}
, o que causará uma exceção, já que nossa classe BLL não espera que um parâmetro de entrada nomeado original_productID
seja passado. Não se esqueça de remover essa configuração completamente da sintaxe declarativa (ou defini-la com o valor padrão, {0}
).
Em seguida, reduza o GridView para incluir apenas os ProductName
, QuantityPerUnit
, UnitPrice
, e UnitsInStock
BoundFields. Sinta-se também à vontade para aplicar qualquer formatação no nível do campo que julgar necessária (como alterar as HeaderText
propriedades).
No tutorial anterior, analisamos como formatar o BoundField como uma moeda, UnitPrice
tanto no modo somente leitura quanto no modo de edição. Vamos fazer o mesmo aqui. Lembre-se de que isso exigia definir a propriedade DataFormatString
do BoundField como {0:c}
, a propriedade HtmlEncode
como false
, e a propriedade ApplyFormatInEditMode
como true
, como mostra a Figura 2.
Figura 2: Configurar o BoundField para exibição como moeda (UnitPrice
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 RowUpdating
do GridView que analisa a cadeia de caracteres formatada como moeda em um valor decimal
. Lembre-se de que o RowUpdating
manipulador de eventos do tutorial anterior também verificou se o usuário forneceu um valor UnitPrice
. No entanto, para este tutorial, vamos permitir que o usuário omita o preço.
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] =decimal.Parse(e.NewValues["UnitPrice"].ToString(),
System.Globalization.NumberStyles.Currency);
}
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 como ReadOnly
true
.
Figura 3: Tornar o BoundField Read-Only (QuantityPerUnit
imagem em tamanho real)
Por fim, marque a caixa de seleção Ativar edição na etiqueta inteligente do GridView. Depois de concluir essas etapas, o Designer da página ErrorHandling.aspx
deve ser semelhante à Figura 4.
Figura 4: Remova todos os BoundFields, exceto os necessários, e marque a caixa de seleção Ativar edição (Clique para visualizar a imagem em tamanho real)
Neste ponto, temos uma lista de todos os campos dos produtos: ProductName
, QuantityPerUnit
, UnitPrice
e UnitsInStock
; no entanto, apenas os campos ProductName
, UnitPrice
e UnitsInStock
podem ser editados.
Figura 5: Os usuários agora podem editar facilmente nomes, preços e unidades de produtos em campos de estoque (Clique para visualizar a imagem em tamanho real)
Etapa 2: Tratamento Elegante de Exceções DAL-Level
Embora o nosso GridView editável funcione perfeitamente quando os usuários inserem valores válidos para o nome, preço e unidades em estoque do produto editado, inserir valores inválidos resulta em uma exceção. Por exemplo, omitir o ProductName
valor faz com que um NoNullAllowedException seja lançado, uma vez que a ProductName
propriedade na ProductsRow
classe tem sua AllowDBNull
propriedade definida como false
; se o banco de dados estiver inativo, um SqlException
será lançado pelo TableAdapter ao tentar se conectar ao banco de dados. Sem realizar nenhuma ação, essas exceções propagam-se 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 ASP.NET.
Dependendo de como a sua aplicação web está configurada e se você está ou não visitando o aplicativo a partir de localhost
, uma exceção não tratada pode resultar numa página de erro de servidor genérica, num relatório de erro detalhado ou numa página da web amigável para o utilizador. Consulte Tratamento de erros de aplicativos Web em ASP.NET e o elemento customErrors para obter mais informações sobre como o tempo de execução do ASP.NET responde a uma exceção não detetada.
A Figura 6 mostra a tela encontrada ao tentar atualizar um produto sem especificar o ProductName
valor. Este é o relatório de erros detalhado padrão exibido ao passar pelo localhost
.
Figura 6: Omitir o nome do produto exibirá detalhes da exceção (Clique para visualizar a imagem em tamanho real)
Embora esses detalhes de exceção sejam úteis ao testar um aplicativo, apresentar a um usuário final essa tela em face de uma exceção não é o ideal. Um usuário final provavelmente não sabe o que é um NoNullAllowedException
ou por que ele 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 pós-nível no ObjectDataSource e no controlador web de dados fornecem um meio de detectá-la e impedir a exceção de propagar-se até o tempo de execução ASP.NET. Para nosso exemplo, vamos criar um manipulador de eventos para o evento do GridView que determina se uma exceção foi acionada RowUpdated
e, em caso afirmativo, exibe os detalhes da exceção em um controle Web Label.
Comece por adicionar um Label à 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 e extra grande.
Figura 7: Adicionar um controle Web de rótulo à página (Clique para visualizar a imagem em tamanho real)
Como queremos que esse controle da Web Label fique visível somente imediatamente após a ocorrência de uma exceção, defina sua Visible
propriedade como false no manipulador de Page_Load
eventos:
protected void Page_Load(object sender, EventArgs e)
{
ExceptionDetails.Visible = false;
}
Com este código, na primeira visita à página e os postbacks subsequentes o ExceptionDetails
controle terá sua Visible
propriedade definida como false
. Perante uma exceção de nível DAL ou BLL, que podemos detetar no manipulador de eventos do RowUpdated
GridView, definiremos a propriedade ExceptionDetails
do controle Visible
como verdadeira. Como os manipuladores de eventos de controle Web ocorrem após o manipulador de eventos Page_Load
no ciclo de vida da página, o Label será exibido. No entanto, no próximo postback, o Page_Load
manipulador de eventos reverterá a Visible
propriedade de volta para false
, ocultando-a da vista novamente.
Observação
Como alternativa, poderíamos remover a necessidade de definir a propriedade ExceptionDetails
do controlo Visible
atribuindo à sua propriedade Page_Load
o valor Visible
diretamente na sintaxe declarativa e desabilitando o seu estado de vista (configurando a sua propriedade false
para EnableViewState
). 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 void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}
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 lançada; Se nenhuma exceção tiver sido lançada, esta propriedade terá um valor denull
-
ExceptionHandled
um valor booleano que indica se a exceção foi ou não manipulada noRowUpdated
manipulador de eventos; sefalse
(o padrão), a exceção é relançada, percolando até ao tempo de execução ASP.NET -
KeepInEditMode
se definido comotrue
a linha GridView editada permanece no modo de edição; sefalse
(o padrão), a linha GridView reverte para seu modo somente leitura
Nosso código, então, deve verificar se Exception
não é null
, o que significa que uma exceção foi gerada durante a execução da operação. Se for esse o caso, queremos:
- Exibir uma mensagem amigável no
ExceptionDetails
rótulo - Indicar que a exceção foi tratada
- Manter a linha GridView no modo de edição
Este código a seguir atinge esses objetivos:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.Exception != null)
{
// Display a user-friendly message
ExceptionDetails.Visible = true;
ExceptionDetails.Text = "There was a problem updating the product. ";
if (e.Exception.InnerException != null)
{
Exception inner = e.Exception.InnerException;
if (inner is System.Data.Common.DbException)
ExceptionDetails.Text +=
"Our database is currently experiencing problems." +
"Please try again later.";
else if (inner is NoNullAllowedException)
ExceptionDetails.Text +=
"There are one or more required fields that are missing.";
else if (inner is ArgumentException)
{
string paramName = ((ArgumentException)inner).ParamName;
ExceptionDetails.Text +=
string.Concat("The ", paramName, " value is illegal.");
}
else if (inner is ApplicationException)
ExceptionDetails.Text += inner.Message;
}
// Indicate that the exception has been handled
e.ExceptionHandled = true;
// Keep the row in edit mode
e.KeepInEditMode = true;
}
}
Este manipulador de eventos começa verificando se e.Exception
é null
. Se não for o caso, a propriedade ExceptionDetails
do Rótulo é configurada para Visible
e a sua propriedade true
é configurada para "Houve um problema ao atualizar o produto". Os detalhes sobre a exceção que efetivamente ocorreu encontram-se na propriedade Text
do objeto e.Exception
. Essa exceção interna é examinada e, se for de um tipo específico, uma mensagem adicional e útil é anexada à ExceptionDetails
propriedade do Text
Label. Por fim, as ExceptionHandled
propriedades 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 visualizar a imagem em tamanho real)
Figura 10: Valores negativos UnitPrice
não são permitidos (Clique para visualizar a imagem em tamanho real)
Ao definir a e.ExceptionHandled
propriedade como true
, o manipulador de RowUpdated
eventos indicou que manipulou a exceção. Portanto, a exceção não se propagará até ao ambiente de execução do ASP.NET.
Observação
As Figuras 9 e 10 mostram uma maneira graciosa de lidar com exceções geradas devido à entrada inválida do usuário. Idealmente, porém, essa entrada inválida nunca chegará à Camada de Lógica de Negócios em primeiro lugar, pois a página de ASP.NET deve garantir que as entradas do usuário sejam válidas antes de invocar o ProductsBLL
método da UpdateProduct
classe. 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 para a camada de lógica de negócios estejam em conformidade com as regras de negócios. Os controles de validação não só impedem a UpdateProduct
invocação do 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: Lidando graciosamente com BLL-Level exceções
Ao inserir, atualizar ou excluir dados, a Camada de Acesso a Dados pode lançar uma exceção em face 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 de Lógica de Negócios pode usar exceções para indicar quando as regras de negócios foram violadas. No tutorial Criando uma camada de 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 esta condição for violada, um ApplicationException
será lançado.
Para a UpdateProduct
sobrecarga criada neste tutorial, vamos adicionar uma regra de negócios que proíbe que o campo UnitPrice
seja definido com um novo valor que seja mais do que o dobro do valor original UnitPrice
. Para fazer isso, ajuste a UpdateProduct
sobrecarga para que ele execute essa verificação e lance um ApplicationException
se a regra for violada. O método atualizado é o seguinte:
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
// Make sure the price has not more than doubled
if (unitPrice != null && !product.IsUnitPriceNull())
if (unitPrice > product.UnitPrice * 2)
throw new ApplicationException(
"When updating a product price," +
" the new price cannot exceed twice the original price.");
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull();
else product.UnitsInStock = unitsInStock.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Com esta 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 lançado. Assim como a exceção gerada pelo DAL, esta exceção gerada pelo BLL ApplicationException
pode ser detetada e tratada 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 do Chai para US$ 50,00, o que é mais do que o dobro de seu 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 dobram o preço de um produto (Clique para visualizar a imagem em tamanho real)
Observação
Idealmente, as nossas regras de lógica empresarial seriam refatoradas das sobrecargas do método UpdateProduct
para um método comum. Isto fica como um exercício para o leitor.
Resumo
Durante as operações de inserção, atualização e exclusão, tanto o controlo de dados Web quanto o ObjectDataSource disparam eventos de pré e pós-nível que enquadram a operação real. Como vimos neste tutorial e no anterior, quando se trabalha 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 é executado para o objeto subjacente do ObjectDataSource. Após a conclusão da operação, o evento do Updated
ObjectDataSource é 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 de 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 detetar se ocorreu uma exceção durante a operação. Diante de uma exceção, esses manipuladores de eventos de nível superior 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.
Feliz Programação!
Sobre o Autor
Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.
Um agradecimento especial a
Esta série de tutoriais foi revisada por muitos revisores úteis. A principal revisora deste tutorial foi Liz Shulok. Interessado em rever meus próximos artigos do MSDN? Se for o caso, envie-me uma mensagem para mitchell@4GuysFromRolla.com.