Partilhar via


Manipulando exceções de BLL e DAL-Level em uma página de ASP.NET (C#)

por Scott Mitchell

Descarregar 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 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:

  1. 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.
  2. Invoque o método.
  3. 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.

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

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.

Configurar o UnitPrice BoundField para exibir como uma moeda

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 ReadOnlytrue.

Tornar o QuantityPerUnit BoundField somente leitura

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.

Remova todos os BoundFields exceto os necessários e marque a caixa de seleção Ativar edição

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.

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 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.

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

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.

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

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.

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 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 de null
  • ExceptionHandled um valor booleano que indica se a exceção foi ou não manipulada no RowUpdated manipulador de eventos; se false (o padrão), a exceção é relançada, percolando até ao tempo de execução 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 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).

O ProductName BoundField deve conter um valor

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

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

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 ApplicationExceptionMessage. 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.

As regras comerciais não permitem aumentos de preços que mais do que duplicem 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 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.