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

por Scott Mitchell

Baixar PDF

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 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âmetro passar. Os valores de parâmetro podem ser embutidos em código, 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 o tratamento normal de qualquer exceção que surgiu na Etapa 2.

Como vimos no tutorial anterior, os controles ObjectDataSource e 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, voltaremos nossa atenção para os eventos que são acionados após a conclusão da operação. Com esses manipuladores de eventos pós-nível, podemos, entre outras coisas, determinar se ocorreu uma exceção 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 padrão 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, 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 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 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: Usar a sobrecarga de UpdateProduct método 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 o OldValuesParameterFormatString valor original_{0}, o que causará uma exceção, pois a classe BLL não espera que um parâmetro de entrada chamado original_productID seja passado. Não se esqueça de remover essa configuração completamente da sintaxe declarativa (ou defina-a como o valor padrão, {0}).

Em seguida, pare o GridView para incluir apenas os ProductNameBoundFields , QuantityPerUnitUnitPrice, e UnitsInStock . Também fique à vontade para aplicar qualquer formatação de nível de campo que você considere 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 exigia a configuração da propriedade BoundField DataFormatString como , sua HtmlEncode propriedade como falsee como trueApplyFormatInEditMode , conforme mostrado na Figura {0:c}2.

Configurar UnitPrice BoundField para exibir como um Conversor de Moedas

Figura 2: Configurar o UnitPrice BoundField para exibir como um Conversor de Moedas (clique para exibir a imagem em tamanho real)

Formatar o UnitPrice como uma 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 em moeda em um decimal valor. Lembre-se de que o RowUpdating manipulador de eventos do último tutorial também verificou 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 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 ReadOnly como true.

Tornar o QuantityPerUnit BoundField somente leitura

Figura 3: Tornar a QuantityPerUnit Read-Only BoundField (clique para exibir a imagem em tamanho real)

Por fim, marcar a caixa de seleção Habilitar Edição da marca inteligente gridView. Depois de concluir essas etapas, o ErrorHandling.aspx Designer da página deve ser semelhante à Figura 4.

Remova All But the Needed BoundFields e marque a caixa de seleção Habilitar Edição

Figura 4: Remover Todos, exceto os BoundFields necessários, e marque 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 , , e dos produtosProductName; no entanto, somente os ProductNamecampos , UnitPricee UnitsInStock podem ser editados.UnitsInStockUnitPriceQuantityPerUnit

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

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

Etapa 2: lidar normalmente com 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, pois a ProductName propriedade na ProductsRow classe tem sua AllowDBNull propriedade definida falsecomo ; se o banco de dados estiver inativo, um SqlException será gerado pelo TableAdapter ao tentar se conectar ao banco de dados. Sem executar nenhuma ação, essas exceções surgirão da Camada de Acesso a Dados para a Camada de Lógica de Negócios, para a página ASP.NET e, por fim, para o runtime do ASP.NET.

Dependendo de como seu aplicativo Web está configurado e se você está visitando ou não o aplicativo de 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 Tratamento de erros de aplicativo Web em ASP.NET e o elemento customErrors para obter mais informações sobre como o runtime do ASP.NET responde a uma exceção não capturada.

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 por 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, apresentar um usuário final com essa tela diante de uma exceção é menor que o ideal. Um usuário final provavelmente não sabe o que é ou NoNullAllowedException 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 pós-nível no ObjectDataSource e no controle da Web de dados fornecerão um meio de detectá-lo e cancelar a exceção de propagar até o runtime do ASP.NET. Para nosso exemplo, vamos criar um manipulador de eventos para o evento gridView RowUpdated que determina se uma exceção foi disparada e, em caso afirmativo, exibe os detalhes da exceção em um controle Web Label.

Comece adicionando um Rótulo à página ASP.NET, definindo sua ID propriedade ExceptionDetails como e limpando sua Text propriedade. 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.

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 void Page_Load(object sender, EventArgs e)
{
    ExceptionDetails.Visible = false;
}

Com esse código, na primeira página, visite e postbacks subsequentes, o ExceptionDetails controle terá sua Visible propriedade definida falsecomo . Diante de uma exceção no nível de DAL ou BLL, que podemos detectar no manipulador de eventos do RowUpdated GridView, definiremos a ExceptionDetails propriedade do Visible controle como true. 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 de volta para , ocultando-a Visiblefalseda exibição novamente.

Observação

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

Com o controle Rótulo adicionado, nossa próxima etapa é criar o manipulador de eventos para o evento gridView RowUpdated . Selecione GridView no Designer, vá para o janela Propriedades e clique no ícone de raio, listando os eventos do GridView. Já deve haver uma entrada para o evento gridView RowUpdating , 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 gridView RowUpdated

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 GridView na lista suspensa à esquerda e o RowUpdated evento do à 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 gerada; se nenhuma exceção tiver sido gerada, essa propriedade terá um valor de null
  • ExceptionHandled um valor booliano que indica se a exceção foi tratada ou não no RowUpdated manipulador de eventos; se false (o padrão), a exceção será gerada novamente, percolando até o runtime do ASP.NET
  • KeepInEditMode se definido como true a linha GridView editada permanecer no modo de edição; se false (o padrão), a linha GridView será revertida para seu modo somente leitura

Nosso código, então, deve marcar para ver se Exception não nullé , o que significa que uma exceção foi gerada durante a execução da operação. Se esse for 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 estes 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;
    }
}

Esse manipulador de eventos começa verificando se e.Exception é null. Se não estiver, a ExceptionDetails propriedade label Visible será definida true como e sua Text propriedade como "Houve um problema ao atualizar o produto". Os detalhes da exceção real que foi gerada residem na e.Exception propriedade do InnerException objeto. 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, as ExceptionHandled propriedades e KeepInEditMode são definidas truecomo .

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 inválido UnitPrice (-50).

O BoundField ProductName deve conter um valor

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

Valores unitprice negativos não são permitidos

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

Ao definir a e.ExceptionHandled propriedade truecomo , o RowUpdated manipulador de eventos indicou que ele lidou com a exceção. Portanto, a exceção não se propaga até o runtime do ASP.NET.

Observação

Os números 9 e 10 mostram uma maneira normal de lidar com exceções geradas devido à entrada inválida do usuário. No entanto, o ideal é que essa entrada inválida nunca chegue à Camada 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 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 à 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 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: lidar normalmente com 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 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 regra de negócios marcar à sobrecarga originalUpdateProduct. Especificamente, se o usuário estava marcando um produto como descontinuado, exigimos que o produto não fosse o único fornecido pelo 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 UnitPrice campo seja definido como um novo valor que seja mais do que o dobro do valor original UnitPrice . Para fazer isso, ajuste a UpdateProduct sobrecarga para que ela execute essa marcar e gere uma ApplicationException se a regra for violada. O método atualizado segue:

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 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 lançado. Assim como a exceção gerada do DAL, esse BLL gerado ApplicationException pode ser detectado e manipulado no manipulador de eventos do RowUpdated GridView. Na verdade, o RowUpdated código do manipulador de eventos, conforme escrito, detectará corretamente essa exceção e exibirá o ApplicationExceptionvalor da propriedade do 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.

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

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 exibir a imagem em tamanho real)

Observação

Idealmente, nossas regras de lógica de negócios seriam refatoradas das sobrecargas de UpdateProduct método e em um método comum. Isso é deixado como um exercício para o leitor.

Resumo

Durante a inserção, atualização e exclusão de operações, o controle da Web de dados e o ObjectDataSource envolveram eventos de pré e pós-nível que reservam a operação real. Como vimos neste tutorial e no anterior, ao trabalhar com um GridView editável, o evento de RowUpdating GridView é acionado, seguido pelo evento objectDataSource Updating , 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 gridView RowUpdated .

Podemos criar manipuladores de eventos para os eventos de pré-nível para 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. Manipuladores de eventos de 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 de pós-nível podem, opcionalmente, manipular 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.

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 Liz Shulok. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.