Partager via


Gestion des exceptions de niveau BLL et DAL dans une page ASP.NET (C#)

par Scott Mitchell

Télécharger le PDF

Dans ce tutoriel, nous allons voir comment afficher un message d’erreur convivial et informatif si une exception se produit lors d’une opération d’insertion, de mise à jour ou de suppression d’un contrôle web de données ASP.NET.

Présentation

L’utilisation de données à partir d’une application web ASP.NET à l’aide d’une architecture d’application hiérarchisée implique les trois étapes générales suivantes :

  1. Déterminez la méthode de la couche logique métier à appeler et les valeurs de paramètre à passer. Les valeurs des paramètres peuvent être codées en dur, affectées par programmation ou entrées entrées par l’utilisateur.
  2. Appelez la méthode.
  3. Traitez les résultats. Lors de l’appel d’une méthode BLL qui retourne des données, cela peut impliquer la liaison des données à un contrôle Web de données. Pour les méthodes BLL qui modifient des données, cela peut inclure l’exécution d’une action basée sur une valeur de retour ou la gestion normale de toute exception qui s’est produite à l’étape 2.

Comme nous l’avons vu dans le didacticiel précédent, les contrôles ObjectDataSource et web de données fournissent des points d’extensibilité pour les étapes 1 et 3. GridView, par exemple, déclenche son RowUpdating événement avant d’affecter ses valeurs de UpdateParameters champ à sa collection ObjectDataSource ; son RowUpdated événement est déclenché une fois l’opération terminée.

Nous avons déjà examiné les événements qui se déclenchent à l’étape 1 et nous avons vu comment ils peuvent être utilisés pour personnaliser les paramètres d’entrée ou annuler l’opération. Dans ce tutoriel, nous allons attirer l’attention sur les événements qui se déclenchent une fois l’opération terminée. Avec ces gestionnaires d’événements de post-niveau, nous pouvons, entre autres, déterminer si une exception s’est produite pendant l’opération et la gérer correctement, affichant un message d’erreur convivial et informatif à l’écran plutôt que de passer par défaut à la page d’exception ASP.NET standard.

Pour illustrer l’utilisation de ces événements de post-niveau, nous allons créer une page qui répertorie les produits dans un GridView modifiable. Lors de la mise à jour d’un produit, si une exception est levée, notre page ASP.NET affiche un message court au-dessus de GridView expliquant qu’un problème s’est produit. Commençons !

Étape 1 : Création d’un GridView modifiable des produits

Dans le tutoriel précédent, nous avons créé un GridView modifiable avec seulement deux champs et ProductNameUnitPrice. Cela nécessite la création d'une surcharge supplémentaire pour la méthode de la classe ProductsBLL, une qui accepte seulement trois paramètres d'entrée (le nom du produit, le prix unitaire et l'identifiant) au lieu d'un paramètre pour chaque champ du produit. Pour ce tutoriel, nous allons à nouveau pratiquer cette technique, en créant un GridView modifiable qui affiche le nom du produit, la quantité par unité, le prix unitaire et les unités en stock, mais autorise uniquement le nom, le prix unitaire et les unités en stock à modifier.

Pour prendre en charge ce scénario, nous aurons besoin d’une autre surcharge de la UpdateProduct méthode, une qui accepte quatre paramètres : le nom du produit, le prix unitaire, les unités en stock et l’ID. Ajoutez la méthode suivante à la 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;
}

Avec cette méthode terminée, nous sommes prêts à créer la page ASP.NET qui permet de modifier ces quatre champs de produit particuliers. Ouvrez la ErrorHandling.aspx page dans le EditInsertDelete dossier et ajoutez un GridView à la page via le Concepteur. Liez le GridView à un nouvel ObjectDataSource, en mappant la méthode Select() à la méthode ProductsBLL de la classe GetProducts() et la méthode Update() à la surcharge UpdateProduct qui vient d'être créée.

Utiliser la surcharge de méthode UpdateProduct qui accepte quatre paramètres d’entrée

Figure 1 : Utiliser la surcharge de méthode UpdateProduct qui accepte quatre paramètres d’entrée (cliquez pour afficher l’image de taille complète)

Cela crée un ObjectDataSource avec une UpdateParameters collection avec quatre paramètres et un GridView avec un champ pour chacun des champs de produit. Le balisage déclaratif de l’ObjectDataSource attribue à la propriété OldValuesParameterFormatString la valeur original_{0}, ce qui entraînera une exception, car les classes BLL ne s'attendent pas à recevoir un paramètre d’entrée nommé original_productID. N’oubliez pas de supprimer complètement ce paramètre de la syntaxe déclarative (ou définissez-le sur la valeur par défaut, {0}).

Ensuite, réduisez le GridView pour inclure uniquement les ProductName champs, QuantityPerUnit champs, UnitPrice champs, et UnitsInStock BoundFields. N’hésitez pas non plus à appliquer toute mise en forme au niveau du champ que vous jugez nécessaire (par exemple, la modification des HeaderText propriétés).

Dans le tutoriel précédent, nous avons examiné comment mettre en forme le UnitPrice BoundField en tant que devise, à la fois en mode lecture seule et en mode édition. Faisons de même ici. Rappelez-vous que cela nécessitait de définir la propriété du BoundField en DataFormatString à {0:c}, sa propriété HtmlEncode à false, et sa propriété ApplyFormatInEditMode à true, comme illustré dans la Figure 2.

Configurer UnitPrice BoundField pour afficher en tant que devise

Figure 2 : Configurer UnitPrice BoundField pour qu’il s’affiche sous forme de devise (cliquez pour afficher l’image de taille complète)

La mise en forme de UnitPrice en devise dans l'interface d'édition nécessite la création d'un gestionnaire d'événements pour l'événement RowUpdating qui convertit la chaîne formatée en devise en une valeur decimal. Rappelez-vous que le RowUpdating gestionnaire d’événements du dernier didacticiel a également vérifié afin de s'assurer que l’utilisateur a fourni une UnitPrice valeur. Toutefois, pour ce tutoriel, nous allons autoriser l’utilisateur à omettre le prix.

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);
}

Notre GridView inclut un QuantityPerUnit BoundField, mais ce BoundField doit être uniquement à des fins d’affichage et ne doit pas être modifiable par l’utilisateur. Pour organiser cela, définissez simplement la propriété BoundFields ReadOnly sur true.

Rendre le champ BoundField QuantityPerUnit en lecture seule

Figure 3 : Créez le BoundField Read-Only (QuantityPerUnit)

Enfin, cochez la case Activer la modification depuis la balise intelligente de GridView. Une fois ces étapes effectuées, le Designer de la ErrorHandling.aspx page devrait ressembler à la Figure 4.

Supprimer tous les éléments BoundFields sauf ceux nécessaires et cochez la case Activer l'édition

Figure 4 : Supprimer toutes les limites requises et cochez la case Activer la modification (cliquez pour afficher l’image de taille complète)

À ce stade, nous avons une liste des champs ProductName, QuantityPerUnit, UnitPrice et UnitsInStock de tous les produits ; toutefois, seuls les champs ProductName, UnitPrice et UnitsInStock peuvent être modifiés.

Les utilisateurs peuvent désormais modifier facilement les noms, les prix et les unités des produits dans les champs Stock

Figure 5 : Les utilisateurs peuvent désormais modifier facilement les noms, les prix et les unités des produits dans les champs Stock (cliquez pour afficher l’image de taille complète)

Étape 2 : Gestion normale des exceptions DAL-Level

Bien que notre GridView modifiable fonctionne merveilleusement lorsque les utilisateurs entrent des valeurs légales pour le nom, le prix et les unités du produit modifiés en stock, l’entrée de valeurs illégales entraîne une exception. Par exemple, omettre la valeur ProductName entraîne la levée d’une NoNullAllowedException parce que la propriété ProductName dans la classe ProductsRow a sa propriété AllowDBNull définie à false ; si la base de données est en panne, une SqlException sera levée par le TableAdapter lorsqu'on tente de se connecter à la base de données. Sans effectuer d’action, ces exceptions s’affichent de la couche d’accès aux données vers la couche logique métier, puis vers la page ASP.NET et enfin vers le runtime ASP.NET.

Selon la façon dont votre application web est configurée et si vous visitez l’application localhostou non, une exception non gérée peut entraîner une page d’erreur de serveur générique, un rapport d’erreurs détaillé ou une page web conviviale. Consultez gestion des erreurs d’application web dans ASP.NET et l’élément customErrors pour plus d’informations sur la façon dont le runtime ASP.NET répond à une exception non interceptée.

La figure 6 montre l’écran rencontré lors de la tentative de mise à jour d’un produit sans spécifier la ProductName valeur. Il s’agit du rapport d’erreurs détaillé par défaut affiché lors de l’exécution localhost.

L’omission du nom du produit affiche les détails de l’exception

Figure 6 : L’omission du nom du produit affiche les détails de l’exception (cliquez pour afficher l’image de taille complète)

Bien que ces détails d’exception soient utiles lors du test d’une application, la présentation d’un utilisateur final avec un tel écran face à une exception est inférieure à l’idéal. Un utilisateur final ne sait probablement pas ce qu’est NoNullAllowedException ou pourquoi il a été provoqué. Une meilleure approche consiste à présenter à l’utilisateur un message plus convivial expliquant qu’il y avait des problèmes lors de la tentative de mise à jour du produit.

Si une exception se produit lors de l’exécution de l’opération, les événements de niveau post dans ObjectDataSource et le contrôle Web de données fournissent un moyen de la détecter et d’annuler l’exception avant qu'elle ne remonte jusqu'à l'exécution ASP.NET. Pour notre exemple, nous allons créer un gestionnaire d’événements pour l’événement RowUpdated gridView qui détermine si une exception a été déclenchée et, le cas échéant, affiche les détails de l’exception dans un contrôle Web Label.

Commencez par ajouter une étiquette à la page ASP.NET, en définissant sa ID propriété ExceptionDetails et en supprimant sa Text propriété. Pour attirer l'attention de l'utilisateur sur ce message, définissez sa propriété CssClass sur Warning, qui est une classe CSS que nous avons ajoutée dans le fichier Styles.css lors du didacticiel précédent. Rappelez-vous que cette classe CSS entraîne l'affichage du texte de l'étiquette en rouge, italique, gras et très grand.

Ajouter un contrôle Web d’étiquette à la page

Figure 7 : Ajouter un contrôle Web d’étiquette à la page (cliquez pour afficher l’image de taille complète)

Étant donné que nous voulons que ce contrôle Label Web soit visible uniquement immédiatement après qu’une exception s’est produite, définissez sa Visible propriété sur false dans le Page_Load gestionnaire d’événements :

protected void Page_Load(object sender, EventArgs e)
{
    ExceptionDetails.Visible = false;
}

Avec ce code, lors de la première visite de la page et des postbacks ultérieurs, le contrôle ExceptionDetails aura sa propriété Visible définie sur false. Face à une exception de niveau DAL ou BLL, que nous pouvons détecter dans le gestionnaire d’événements RowUpdated de GridView, nous allons définir la propriété ExceptionDetails du contrôle Visible sur true. Étant donné que les gestionnaires d’événements de contrôle Web se produisent après le Page_Load gestionnaire d’événements dans le cycle de vie de la page, le Label s’affiche. Toutefois, lors de la prochaine publication, le gestionnaire d'événements Page_Load rétablira la propriété Visible à false, la masquant à nouveau de l'affichage.

Remarque

Nous pourrions également supprimer la nécessité de définir la propriété ExceptionDetails du contrôle Visible en Page_Load en affectant sa propriété Visible à false dans la syntaxe déclarative et en désactivant son état d’affichage (en définissant sa propriété EnableViewState sur false). Nous allons utiliser cette approche alternative dans un prochain tutoriel.

Avec le contrôle Label ajouté, notre étape suivante consiste à créer le gestionnaire d’événements pour l’événement RowUpdated gridView. Sélectionnez GridView dans le Concepteur, accédez à la fenêtre Propriétés, puis cliquez sur l’icône éclair, répertoriant les événements de GridView. Il doit déjà y avoir une entrée pour l’événement GridView, car nous avons créé un gestionnaire d’événements RowUpdating pour cet événement précédemment dans ce tutoriel. Créez également un gestionnaire d’événements pour l’événement RowUpdated .

Créer un gestionnaire d’événements pour l’événement RowUpdated de GridView

Figure 8 : Créer un gestionnaire d’événements pour l’événement RowUpdated GridView

Remarque

Vous pouvez également créer le gestionnaire d’événements via les listes déroulantes en haut du fichier de classe code-behind. Sélectionnez GridView dans la liste déroulante à gauche et l’événement RowUpdated à partir de la liste de droite.

La création de ce gestionnaire d’événements ajoute le code suivant à la classe code-behind de la page ASP.NET :

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}

Le deuxième paramètre d’entrée de ce gestionnaire d’événements est un objet de type GridViewUpdatedEventArgs, qui a trois propriétés intéressantes pour la gestion des exceptions :

  • Exception référence à l’exception levée ; si aucune exception n’a été levée, cette propriété aura une valeur de null
  • ExceptionHandled valeur booléenne qui indique si l’exception a été gérée ou non dans le RowUpdated gestionnaire d’événements ; si false (la valeur par défaut), l’exception est levée à nouveau, encolant jusqu’au runtime ASP.NET
  • KeepInEditMode si elle est définie sur true la ligne GridView modifiée reste en mode édition ; si false (valeur par défaut), la ligne GridView revient à son mode lecture seule

Notre code doit alors vérifier si Exception n'est pas null, ce qui signifie qu'une exception a été levée lors de l'exécution de l'opération. Si c’est le cas, nous voulons :

  • Afficher un message convivial dans l’étiquette ExceptionDetails
  • Indiquer que l’exception a été gérée
  • Conserver la ligne GridView en mode édition

Ce code suivant accomplit ces objectifs :

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;
    }
}

Le gestionnaire d’événements commence par vérifier si e.Exception est null. Si ce n’est pas le cas, la propriété ExceptionDetails de l'étiquette Visible est définie sur true, et sa propriété Text sur « Il y a eu un problème de mise à jour du produit ». Les détails de l’exception réelle levée résident dans la propriété e.Exception de l’objet InnerException. Cette exception interne est examinée et, s’il s’agit d’un type particulier, un message supplémentaire et utile est ajouté à la propriété ExceptionDetails de l’étiquette Text. Enfin, les propriétés ExceptionHandled et KeepInEditMode sont toutes deux définies sur true.

La figure 9 montre une capture d’écran de cette page lors de l’omission du nom du produit ; La figure 10 montre les résultats lors de l’entrée d’une valeur illégale UnitPrice (-50).

ProductName BoundField doit contenir une valeur

Figure 9 : BoundField ProductName doit contenir une valeur (cliquez pour afficher l’image de taille complète)

Les valeurs UnitPrice négatives ne sont pas autorisées

Figure 10 : Les valeurs négatives UnitPrice ne sont pas autorisées (cliquez pour afficher l’image de taille complète)

En définissant la propriété e.ExceptionHandled à true, le gestionnaire d’événements RowUpdated a indiqué qu’il a géré l’exception. Par conséquent, l’exception ne se propage pas au runtime ASP.NET.

Remarque

Les figures 9 et 10 montrent un moyen approprié de gérer les exceptions levées en raison d’une entrée utilisateur non valide. Dans l’idéal, toutefois, une telle entrée non valide n’atteint jamais la couche logique métier en premier lieu, car la page ASP.NET doit s’assurer que les entrées de l’utilisateur sont valides avant d’appeler la méthode de ProductsBLL la UpdateProduct classe. Dans notre prochain tutoriel, nous allons voir comment ajouter des contrôles de validation aux interfaces d’édition et d’insertion pour vous assurer que les données soumises à la couche logique métier sont conformes aux règles métier. Les contrôles de validation empêchent non seulement l’appel de la UpdateProduct méthode jusqu’à ce que les données fournies par l’utilisateur soient valides, mais fournissent également une expérience utilisateur plus informative pour identifier les problèmes d’entrée de données.

Étape 3 : Gestion normale des exceptions BLL-Level

Lors de l’insertion, de la mise à jour ou de la suppression de données, la couche d’accès aux données peut lever une exception face à une erreur liée aux données. La base de données peut être hors connexion, une colonne de table de base de données requise n’a peut-être pas eu une valeur spécifiée, ou une contrainte au niveau de la table peut avoir été violée. En plus des exceptions strictement liées aux données, la couche logique métier peut utiliser des exceptions pour indiquer quand des règles d’entreprise ont été violées. Dans le didacticiel Création d’une couche logique métier , par exemple, nous avons ajouté une vérification de règle métier à la surcharge d’origine UpdateProduct . Plus précisément, si l’utilisateur marque un produit comme supprimé, nous avons exigé que le produit ne soit pas le seul fourni par son fournisseur. Si cette condition a été violée, une ApplicationException exception a été levée.

Pour la UpdateProduct surcharge créée dans ce tutoriel, nous allons ajouter une règle d’entreprise qui empêche le UnitPrice champ d’être défini sur une nouvelle valeur qui est supérieure à deux fois la valeur d’origine UnitPrice . Pour ce faire, ajustez la UpdateProduct surcharge afin qu’elle effectue cette vérification et lève une ApplicationException si la règle est violée. La méthode mise à jour suit :

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;
}

Avec cette modification, toute mise à jour de prix qui est supérieure à deux fois le prix existant provoquera le déclenchement d'une ApplicationException. Tout comme l’exception levée à partir de la DAL, cette exception levée par le BLL ApplicationException peut être détectée et gérée dans le gestionnaire d’événements RowUpdated de GridView. En fait, le code du gestionnaire d’événements RowUpdated, tel qu'il est écrit, détecte correctement cette exception et affiche la valeur de la propriété ApplicationExceptionMessage. La figure 11 montre une capture d’écran lorsqu’un utilisateur tente de mettre à jour le prix de Chai à 50,00 $, soit plus que le double de son prix actuel de 19,95 $.

Les règles d’entreprise interdisent les hausses de prix qui dépassent le double du prix d’un produit

Figure 11 : Les règles d’entreprise interdisent les augmentations de prix qui dépassent le double du prix d’un produit (cliquez pour afficher l’image pleine taille)

Remarque

Dans l’idéal, nos règles de logique métier seraient refactorisées en dehors des surcharges de méthode UpdateProduct et intégrées dans une méthode commune. Ceci est laissé en tant qu’exercice pour le lecteur.

Résumé

Lors des opérations d'insertion, de mise à jour et de suppression, le contrôle Web de données et l'ObjectDataSource déclenchent des événements préalables et postérieurs qui encadrent l'opération réelle. Comme nous l’avons vu dans ce didacticiel et le précédent, lorsque nous travaillons avec un GridView modifiable, l’événement RowUpdating GridView se déclenche, suivi de Updating l’événement ObjectDataSource, auquel point la commande de mise à jour est effectuée sur l’objet sous-jacent d’ObjectDataSource. Une fois l’opération terminée, l’événement Updated ObjectDataSource se déclenche, suivi de l’événement RowUpdated gridView.

Nous pouvons créer des gestionnaires d’événements pour les événements de pré-niveau afin de personnaliser les paramètres d’entrée ou les événements de post-niveau afin d’inspecter et de répondre aux résultats de l’opération. Les gestionnaires d’événements de post-niveau sont les plus couramment utilisés pour détecter si une exception s’est produite pendant l’opération. Face à une exception, ces gestionnaires d’événements de post-niveau peuvent éventuellement gérer l’exception par eux-mêmes. Dans ce tutoriel, nous avons vu comment gérer une telle exception en affichant un message d’erreur convivial.

Dans le tutoriel suivant, nous allons voir comment réduire la probabilité d’exceptions résultant de problèmes de mise en forme des données (par exemple, entrer un négatif UnitPrice). Plus précisément, nous allons examiner comment ajouter des contrôles de validation aux interfaces d’édition et d’insertion.

Bonne programmation !

À propos de l’auteur

Scott Mitchell, auteur de sept livres ASP/ASP.NET et fondateur de 4GuysFromRolla.com, travaille avec les technologies Web Microsoft depuis 1998. Scott travaille en tant que consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 en 24 heures. On peut le joindre à mitchell@4GuysFromRolla.com.

Merci spécial à

Cette série de tutoriels a été examinée par de nombreux réviseurs utiles. Le réviseur principal de ce tutoriel était Liz Shulok. Vous souhaitez consulter mes prochains articles MSDN ? Si c’est le cas, déposez-moi une ligne à mitchell@4GuysFromRolla.com.