Partager via


Création d’une couche de logique métier (C#)

par Scott Mitchell

Télécharger le PDF

Dans ce tutoriel, nous allons voir comment centraliser vos règles métier dans une couche logique métier (BLL) qui sert d’intermédiaire pour l’échange de données entre la couche de présentation et le DAL.

Présentation

La couche d’accès aux données (DAL) créée dans le premier tutoriel sépare correctement la logique d’accès aux données de la logique de présentation. Toutefois, bien que le DAL sépare correctement les détails de l’accès aux données de la couche de présentation, il n’applique aucune règle métier qui pourraient s’appliquer. Par exemple, pour notre application, nous pouvons vouloir interdire la modification des champs CategoryID ou SupplierID de la table Products lorsque le champ Discontinued est défini sur 1, ou nous pourrions vouloir appliquer des règles d’ancienneté, interdisant les situations dans lesquelles un employé est géré par une personne embauchée après lui. Un autre scénario courant est l’autorisation peut-être que seuls les utilisateurs d’un rôle particulier peuvent supprimer des produits ou modifier la UnitPrice valeur.

Dans ce tutoriel, nous allons voir comment centraliser ces règles métier dans une couche logique métier (BLL) qui sert d’intermédiaire pour l’échange de données entre la couche de présentation et le DAL. Dans une application réelle, la BLL doit être implémentée en tant que projet de bibliothèque de classes distinct ; Toutefois, pour ces didacticiels, nous allons implémenter la BLL en tant que série de classes dans notre App_Code dossier afin de simplifier la structure du projet. La figure 1 illustre les relations architecturales entre la couche de présentation, BLL et DAL.

BLL sépare la couche présentation de la couche d’accès aux données et impose des règles d’entreprise

Figure 1 : La BLL sépare la couche Présentation de la couche d’accès aux données et impose des règles d’entreprise

Étape 1 : Création des classes BLL

Notre BLL sera composée de quatre classes, une pour chaque TableAdapter dans le DAL ; chacune de ces classes BLL aura des méthodes pour récupérer, insérer, mettre à jour et supprimer du TableAdapter correspondant dans le DAL, en appliquant les règles métier appropriées.

Pour séparer plus clairement les classes DAL et BLL, nous allons créer deux sous-dossiers dans le dossier App_Code : DAL et BLL. Cliquez simplement avec le bouton droit sur le App_Code dossier dans l’Explorateur de solutions et choisissez Nouveau dossier. Après avoir créé ces deux dossiers, déplacez le Jeu de données typé créé dans le premier tutoriel dans le DAL sous-dossier.

Ensuite, créez les quatre fichiers de classe BLL dans le BLL sous-dossier. Pour ce faire, cliquez avec le bouton droit sur le BLL sous-dossier, choisissez Ajouter un nouvel élément, puis choisissez le modèle De classe. Nommez les quatre classes ProductsBLL, CategoriesBLL, SuppliersBLLet EmployeesBLL.

Ajouter quatre nouvelles classes au dossier App_Code

Figure 2 : Ajouter quatre nouvelles classes au App_Code dossier

Ensuite, ajoutons des méthodes à chacune des classes pour encapsuler simplement les méthodes définies pour les TableAdapters à partir du premier didacticiel. Pour l’instant, ces méthodes appellent simplement directement dans le DAL ; Nous retournerons ultérieurement pour ajouter toute logique métier nécessaire.

Remarque

Si vous utilisez Visual Studio Standard Edition ou version ultérieure (autrement dit, vous n’utilisez pas Visual Web Developer), vous pouvez éventuellement concevoir vos classes visuellement à l’aide du Concepteur de classes. Pour plus d’informations sur cette nouvelle fonctionnalité dans Visual Studio, consultez le blog du Concepteur de classes.

Pour la ProductsBLL classe, nous devons ajouter un total de sept méthodes :

  • GetProducts() retourne tous les produits
  • GetProductByProductID(productID) retourne le produit avec l’ID de produit spécifié
  • GetProductsByCategoryID(categoryID) retourne tous les produits de la catégorie spécifiée
  • GetProductsBySupplier(supplierID) retourne tous les produits du fournisseur spécifié
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) insère un nouveau produit dans la base de données à l’aide des valeurs transmises ; retourne la ProductID valeur de l’enregistrement nouvellement inséré
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) met à jour un produit existant dans la base de données à l’aide des valeurs transmises ; retourne true si une ligne a été mise à jour précisément, false sinon
  • DeleteProduct(productID) supprime le produit spécifié de la base de données

ProductsBLL.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;

[System.ComponentModel.DataObject]
public class ProductsBLL
{
    private ProductsTableAdapter _productsAdapter = null;
    protected ProductsTableAdapter Adapter
    {
        get {
            if (_productsAdapter == null)
                _productsAdapter = new ProductsTableAdapter();

            return _productsAdapter;
        }
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
    public Northwind.ProductsDataTable GetProducts()
    {
        return Adapter.GetProducts();
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductByProductID(int productID)
    {
        return Adapter.GetProductByProductID(productID);
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
    {
        return Adapter.GetProductsByCategoryID(categoryID);
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsBySupplierID(int supplierID)
    {
        return Adapter.GetProductsBySupplierID(supplierID);
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Insert, true)]
    public bool AddProduct(string productName, int? supplierID, int? categoryID,
        string quantityPerUnit, decimal? unitPrice,  short? unitsInStock,
        short? unitsOnOrder, short? reorderLevel, bool discontinued)
    {
        // Create a new ProductRow instance
        Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
        Northwind.ProductsRow product = products.NewProductsRow();

        product.ProductName = productName;
        if (supplierID == null) product.SetSupplierIDNull();
          else product.SupplierID = supplierID.Value;
        if (categoryID == null) product.SetCategoryIDNull();
          else product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
          else product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) product.SetUnitPriceNull();
          else product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) product.SetUnitsInStockNull();
          else product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
          else product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) product.SetReorderLevelNull();
          else product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;

        // Add the new product
        products.AddProductsRow(product);
        int rowsAffected = Adapter.Update(products);

        // Return true if precisely one row was inserted,
        // otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Update, true)]
    public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
        string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
        short? unitsOnOrder, short? reorderLevel, bool discontinued, 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 (supplierID == null) product.SetSupplierIDNull();
          else product.SupplierID = supplierID.Value;
        if (categoryID == null) product.SetCategoryIDNull();
          else product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
          else product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) product.SetUnitPriceNull();
          else product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) product.SetUnitsInStockNull();
          else product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
          else product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) product.SetReorderLevelNull();
          else product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;

        // Update the product record
        int rowsAffected = Adapter.Update(product);

        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Delete, true)]
    public bool DeleteProduct(int productID)
    {
        int rowsAffected = Adapter.Delete(productID);

        // Return true if precisely one row was deleted,
        // otherwise false
        return rowsAffected == 1;
    }
}

Les méthodes qui retournent directement des données GetProducts, GetProductByProductID, GetProductsByCategoryID et GetProductBySuppliersID sont assez simples car elles invoquent directement le DAL. Dans certains scénarios, il peut y avoir des règles métier qui doivent être implémentées à ce niveau (telles que des règles d’autorisation basées sur l’utilisateur actuellement connecté ou le rôle auquel appartient l’utilisateur), nous allons simplement laisser ces méthodes as-is. Pour ces méthodes, la BLL sert simplement de proxy par le biais duquel la couche de présentation accède aux données sous-jacentes à partir de la couche d’accès aux données.

Les méthodes AddProduct et UpdateProduct prennent toutes deux en paramètres les valeurs des différents champs de produit et ajoutent respectivement un nouveau produit ou mettent à jour un produit existant. Étant donné que la plupart des colonnes de la Product table peuvent accepter NULL des valeurs (CategoryID, SupplierIDet UnitPrice, pour nommer quelques-uns), ces paramètres d’entrée pour AddProduct et UpdateProduct qui mappent à de telles colonnes utilisent des types nullables. Les types nullables sont nouveaux pour .NET 2.0 et fournissent une technique pour indiquer si un type valeur doit, à la place, être null. En C#, vous pouvez marquer un type valeur en tant que type Nullable en ajoutant ? après le type (par exemple int? x;). Pour plus d’informations, consultez la section Types nullables dans le Guide de programmation C#.

Les trois méthodes retournent une valeur booléenne indiquant si une ligne a été insérée, mise à jour ou supprimée, car l’opération peut ne pas entraîner de ligne affectée. Par exemple, si le développeur de pages appelle DeleteProduct en passant un ProductID pour un produit inexistant, l’instruction DELETE émise dans la base de données n’a aucun effet et par conséquent, la méthode DeleteProduct revient false.

Notez que lors de l’ajout d’un nouveau produit ou de la mise à jour d’un produit existant, nous prenons les valeurs de champ du nouveau produit ou du produit modifié sous forme de liste de scalaires, plutôt que d'accepter une instance ProductsRow. Cette approche a été choisie, car la ProductsRow classe dérive de la classe ADO.NET DataRow , qui n’a pas de constructeur sans paramètre par défaut. Pour créer une ProductsRow instance, nous devons d’abord créer une ProductsDataTable instance, puis appeler sa NewProductRow() méthode (que nous faisons dans AddProduct). Cette lacune se manifeste lorsque nous nous tournons vers l’insertion et la mise à jour de produits à l’aide de l’ObjectDataSource. En bref, ObjectDataSource tente de créer une instance des paramètres d’entrée. Si la méthode BLL attend une ProductsRow instance, ObjectDataSource tente de en créer une, mais échoue en raison de l’absence d’un constructeur sans paramètre par défaut. Pour plus d’informations sur ce problème, reportez-vous aux deux billets de forums ASP.NET suivants : Mise à jour d’ObjectDataSources avec Strongly-Typed DataSets et Problème avec ObjectDataSource et Strongly-Typed DataSet.

Ensuite, dans les deux AddProduct et UpdateProduct, le code crée une instance de ProductsRow et la remplit avec les valeurs qui viennent d’être passées. Lorsque vous affectez des valeurs aux DataColumns d'une DataRow, divers contrôles de validation au niveau du champ peuvent se produire. Par conséquent, le fait de remettre manuellement les valeurs passées dans un DataRow permet de garantir la validité des données transmises à la méthode BLL. Malheureusement, les classes DataRow fortement typées générées par Visual Studio n’utilisent pas de types nullables. Plutôt, pour indiquer qu’un DataColumn particulier dans un DataRow doit correspondre à une NULL valeur de base de données, nous devons utiliser la SetColumnNameNull() méthode.

Dans UpdateProduct, nous chargeons d'abord le produit à mettre à jour en utilisant GetProductByProductID(productID). Bien que cela puisse sembler un voyage inutile vers la base de données, ce voyage supplémentaire sera utile dans les didacticiels futurs qui explorent la concurrence optimiste. L’accès concurrentiel optimiste est une technique permettant de s’assurer que deux utilisateurs qui travaillent simultanément sur les mêmes données ne remplacent pas accidentellement les modifications des autres. Saisir l’intégralité de l’enregistrement facilite également la création de méthodes de mise à jour dans la BLL qui modifient uniquement un sous-ensemble des colonnes de DataRow. Lorsque nous explorerons la SuppliersBLL classe, nous verrons un tel exemple.

Enfin, notez que l’attribut ProductsBLLDataObject est appliqué à la classe (la [System.ComponentModel.DataObject] syntaxe juste avant l’instruction de classe située en haut du fichier) et que les méthodes ont des attributs DataObjectMethodAttribute. L’attribut DataObject marque la classe comme étant un objet adapté à la liaison à un contrôle ObjectDataSource, tandis que l’objet DataObjectMethodAttribute indique l’objectif de la méthode. Comme nous le verrons dans les prochains didacticiels, ASP.NET ObjectDataSource de la version 2.0 facilite l’accès déclaratif aux données à partir d’une classe. Pour filtrer la liste des classes possibles à lier dans l’Assistant ObjectDataSource, par défaut, seules ces classes marquées comme DataObjects sont affichées dans la liste déroulante de l’Assistant. La ProductsBLL classe fonctionne tout aussi bien sans ces attributs, mais les ajouter facilite l'utilisation dans l'assistant ObjectDataSource.

Ajout des autres classes

Une fois la ProductsBLL classe terminée, nous devons encore ajouter les classes pour travailler avec les catégories, les fournisseurs et les employés. Prenez un moment pour créer les classes et méthodes suivantes à l’aide des concepts de l’exemple ci-dessus :

  • CategoriesBLL.cs

    • GetCategories()
    • GetCategoryByCategoryID(categoryID)
  • SuppliersBLL.cs

    • GetSuppliers()
    • GetSupplierBySupplierID(supplierID)
    • GetSuppliersByCountry(country)
    • UpdateSupplierAddress(supplierID, address, city, country)
  • EmployeesBLL.cs

    • GetEmployees()
    • GetEmployeeByEmployeeID(employeeID)
    • GetEmployeesByManager(managerID)

La méthode de la classe SuppliersBLL qui mérite d'être notée est la méthode UpdateSupplierAddress. Cette méthode fournit une interface permettant de mettre à jour uniquement les informations d’adresse du fournisseur. En interne, cette méthode lit l’objet SupplierDataRow correspondant à supplierID spécifié (à l’aide de GetSupplierBySupplierID), définit ses propriétés liées à l’adresse, puis appelle la méthode SupplierDataTable de Update. La méthode UpdateSupplierAddress suit :

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateSupplierAddress
    (int supplierID, string address, string city, string country)
{
    Northwind.SuppliersDataTable suppliers =
        Adapter.GetSupplierBySupplierID(supplierID);
    if (suppliers.Count == 0)
        // no matching record found, return false
        return false;
    else
    {
        Northwind.SuppliersRow supplier = suppliers[0];

        if (address == null) supplier.SetAddressNull();
          else supplier.Address = address;
        if (city == null) supplier.SetCityNull();
          else supplier.City = city;
        if (country == null) supplier.SetCountryNull();
          else supplier.Country = country;

        // Update the supplier Address-related information
        int rowsAffected = Adapter.Update(supplier);

        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }
}

Reportez-vous au téléchargement de cet article pour mon implémentation complète des classes BLL.

Étape 2 : Accès aux jeux de données typés via les classes BLL

Dans le premier tutoriel, nous avons vu des exemples de travail directement avec le DataSet typé au niveau de la programmation, mais avec l'ajout de nos classes BLL, la couche de présentation devrait interagir avec le BLL à la place. Dans l’exemple AllProducts.aspx du premier didacticiel, il ProductsTableAdapter a été utilisé pour lier la liste des produits à un GridView, comme illustré dans le code suivant :

ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();

Pour utiliser les nouvelles classes BLL, tout ce qui doit être modifié est la première ligne de code simplement remplacer l’objet ProductsTableAdapter par un ProductBLL objet :

ProductsBLL productLogic = new ProductsBLL();
GridView1.DataSource = productLogic.GetProducts();
GridView1.DataBind();

Les classes BLL sont également accessibles de manière déclarative (comme pour l’ensemble de données typé) à l’aide de ObjectDataSource. Nous aborderons plus en détail ObjectDataSource dans les didacticiels suivants.

La liste des produits s’affiche dans un GridView

Figure 3 : La liste des produits s’affiche dans un GridView (Cliquez pour afficher l’image de taille complète)

Étape 3 : Ajout de la validation Field-Level aux classes de type DataRow

La validation au niveau du champ est des vérifications qui se rapportent aux valeurs de propriété des objets métier lors de l’insertion ou de la mise à jour. Voici quelques règles de validation au niveau du champ pour les produits :

  • Le ProductName champ doit comporter 40 caractères ou moins
  • Le QuantityPerUnit champ doit comporter 20 caractères ou moins
  • Les champs ProductID, ProductName, et Discontinued sont obligatoires, mais tous les autres champs sont facultatifs.
  • Les champs UnitPrice, UnitsInStock, UnitsOnOrder et ReorderLevel doivent être supérieurs ou égaux à zéro.

Ces règles peuvent et doivent être exprimées au niveau de la base de données. La limite de caractères sur les champs ProductName et QuantityPerUnit est définie par les types de données de ces colonnes dans la table Products (nvarchar(40) et nvarchar(20), respectivement). La nature obligatoire ou facultative des champs est déterminée par l'autorisation des NULL par les colonnes de la table de base de données. Quatre contraintes de vérification existent qui garantissent que seules les valeurs supérieures ou égales à zéro peuvent être insérées dans les colonnes UnitPrice, UnitsInStock, UnitsOnOrder ou ReorderLevel.

Outre l’application de ces règles à la base de données, elles doivent également être appliquées au niveau du DataSet. En fait, la longueur du champ et le fait qu'une valeur soit requise ou facultative sont déjà déterminés pour chaque ensemble de DataColumns de chaque DataTable. Pour afficher automatiquement la validation au niveau du champ, accédez au Concepteur DataSet, sélectionnez un champ dans l’un des DataTables, puis accédez à la fenêtre Propriétés. Comme le montre la figure 4, le QuantityPerUnit DataColumn dans le ProductsDataTable a une longueur maximale de 20 caractères et permet des valeurs NULL. Si nous essayons de définir la propriété ProductsDataRow de QuantityPerUnit sur une valeur de chaîne supérieure à 20 caractères, une ArgumentException sera levée.

DataColumn fournit une validation de base Field-Level

Figure 4 : DataColumn fournit une validation de base Field-Level (cliquez pour afficher l’image de taille complète)

Malheureusement, nous ne pouvons pas spécifier de vérifications de limites, telles que la UnitPrice valeur doit être supérieure ou égale à zéro, via la fenêtre Propriétés. Pour fournir ce type de validation au niveau du champ, nous devons créer un gestionnaire d’événements pour l’événement ColumnChanging de DataTable. Comme mentionné dans le tutoriel précédent, les objets DataSet, DataTables et DataRow créés par l’ensemble de données typé peuvent être étendus à l’aide de classes partielles. À l’aide de cette technique, nous pouvons créer un gestionnaire d’événements ColumnChanging pour la ProductsDataTable classe. Commencez par créer une classe dans le App_Code dossier nommé ProductsDataTable.ColumnChanging.cs.

Ajouter une nouvelle classe au dossier App_Code

Figure 5 : Ajouter une nouvelle classe au App_Code dossier (cliquez pour afficher l’image de taille complète)

Ensuite, créez un gestionnaire d’événements pour l’événement ColumnChanging qui garantit que les valeurs des colonnes UnitPrice, UnitsInStock, UnitsOnOrder et ReorderLevel (si ce n’est NULL) sont supérieures ou égales à zéro. Si une telle colonne est hors de portée, lancez un ArgumentException.

ProductsDataTable.ColumnChanging.cs

public partial class Northwind
{
    public partial class ProductsDataTable
    {
        public override void BeginInit()
         {
            this.ColumnChanging += ValidateColumn;
         }

         void ValidateColumn(object sender,
           DataColumnChangeEventArgs e)
         {
            if(e.Column.Equals(this.UnitPriceColumn))
            {
               if(!Convert.IsDBNull(e.ProposedValue) &&
                  (decimal)e.ProposedValue < 0)
               {
                  throw new ArgumentException(
                      "UnitPrice cannot be less than zero", "UnitPrice");
               }
            }
            else if (e.Column.Equals(this.UnitsInStockColumn) ||
                     e.Column.Equals(this.UnitsOnOrderColumn) ||
                     e.Column.Equals(this.ReorderLevelColumn))
            {
                if (!Convert.IsDBNull(e.ProposedValue) &&
                    (short)e.ProposedValue < 0)
                {
                    throw new ArgumentException(string.Format(
                        "{0} cannot be less than zero", e.Column.ColumnName),
                        e.Column.ColumnName);
                }
            }
         }
    }
}

Étape 4 : Ajout de règles d’entreprise personnalisées aux classes de BLL

En plus de la validation au niveau du champ, il peut y avoir des règles d’entreprise personnalisées de haut niveau qui impliquent différentes entités ou concepts non explicites au niveau de la colonne unique, comme :

  • Si un produit est arrêté, il UnitPrice ne peut pas être mis à jour
  • Le pays de résidence d’un employé doit être identique au pays de résidence de son responsable
  • Un produit ne peut pas être interrompu s’il s’agit du seul produit fourni par le fournisseur

Les classes BLL doivent contenir des vérifications pour garantir l’adhésion aux règles métier de l’application. Ces vérifications peuvent être ajoutées directement aux méthodes auxquelles elles s’appliquent.

Imaginez que nos règles commerciales déterminent qu’un produit n’a pas pu être marqué comme étant abandonné s’il s’agissait du seul produit d’un fournisseur donné. Autrement dit, si le produit X était le seul produit que nous avons acheté auprès du fournisseur Y, nous n’avons pas pu marquer X comme supprimé ; si, cependant, le fournisseur Y nous a fourni trois produits, A, B et C, alors nous pourrions marquer n’importe quel et tous ces produits comme abandonnés. Une règle d’entreprise impaire, mais les règles métier et le bon sens ne sont pas toujours alignés !

Pour appliquer cette règle métier dans la méthode UpdateProducts, nous commencerions par vérifier si Discontinued a été défini true, et dans ce cas-là, nous appellerions GetProductsBySupplierID pour déterminer combien de produits nous avons achetés auprès du fournisseur de ce produit. Si un seul produit est acheté auprès de ce fournisseur, nous lançons un ApplicationException.

public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
    string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
    short? unitsOnOrder, short? reorderLevel, bool discontinued, 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];

    // Business rule check - cannot discontinue
    // a product that is supplied by only
    // one supplier
    if (discontinued)
    {
        // Get the products we buy from this supplier
        Northwind.ProductsDataTable productsBySupplier =
            Adapter.GetProductsBySupplierID(product.SupplierID);

        if (productsBySupplier.Count == 1)
            // this is the only product we buy from this supplier
            throw new ApplicationException(
                "You cannot mark a product as discontinued if it is the only
                  product purchased from a supplier");
    }

    product.ProductName = productName;
    if (supplierID == null) product.SetSupplierIDNull();
      else product.SupplierID = supplierID.Value;
    if (categoryID == null) product.SetCategoryIDNull();
      else product.CategoryID = categoryID.Value;
    if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
      else product.QuantityPerUnit = quantityPerUnit;
    if (unitPrice == null) product.SetUnitPriceNull();
      else product.UnitPrice = unitPrice.Value;
    if (unitsInStock == null) product.SetUnitsInStockNull();
      else product.UnitsInStock = unitsInStock.Value;
    if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
      else product.UnitsOnOrder = unitsOnOrder.Value;
    if (reorderLevel == null) product.SetReorderLevelNull();
      else product.ReorderLevel = reorderLevel.Value;
    product.Discontinued = discontinued;

    // Update the product record
    int rowsAffected = Adapter.Update(product);

    // Return true if precisely one row was updated,
    // otherwise false
    return rowsAffected == 1;
}

Réponse aux erreurs de validation dans le niveau Présentation

Lors de l’appel de la BLL depuis la couche de présentation, nous pouvons décider s’il faut tenter de gérer les exceptions susceptibles d’être levées ou de les laisser remonter jusqu’à ASP.NET (ce qui déclenchera l’événement HttpApplication).Error Pour gérer une exception lors de l’utilisation de BLL par programmation, nous pouvons utiliser une tentative... catch block, comme l’illustre l’exemple suivant :

ProductsBLL productLogic = new ProductsBLL();

// Update information for ProductID 1
try
{
    // This will fail since we are attempting to use a
    // UnitPrice value less than 0.
    productLogic.UpdateProduct(
        "Scott s Tea", 1, 1, null, -14m, 10, null, null, false, 1);
}
catch (ArgumentException ae)
{
    Response.Write("There was a problem: " + ae.Message);
}

Comme nous le verrons dans les prochains didacticiels, la gestion des exceptions qui se déclenchent à partir de la BLL lors de l’utilisation d’un contrôle Web de données pour l’insertion, la mise à jour ou la suppression de données peut être gérée directement dans un gestionnaire d’événements, au lieu d’avoir à encapsuler du code dans des try...catch blocs.

Résumé

Une application bien conçue est conçue en couches distinctes, chacune encapsule un rôle particulier. Dans le premier tutoriel de cette série d’articles, nous avons créé une couche d’accès aux données à l’aide de DataSets typés ; Dans ce tutoriel, nous avons créé une couche logique métier en tant que série de classes dans le dossier de App_Code notre application qui appellent notre DAL. BLL implémente la logique au niveau des champs et la logique métier pour notre application. Outre la création d’une BLL distincte, comme nous l’avons fait dans ce tutoriel, une autre option consiste à étendre les méthodes de TableAdapters à l’aide de classes partielles. Toutefois, l’utilisation de cette technique ne nous permet pas d'outrepasser les méthodes existantes ni de séparer notre DAL et notre BLL aussi proprement que l’approche que nous avons adoptée dans cet article.

Avec la DAL et BLL terminées, nous sommes prêts à commencer notre couche de présentation. Dans le tutoriel suivant , nous allons prendre un bref détour des rubriques d’accès aux données et définir une mise en page cohérente à utiliser dans les didacticiels.

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. Les réviseurs principaux de ce tutoriel étaient Liz Shulok, Dennis Patterson, Carlos Santos et Hilton Giesenow. Vous souhaitez consulter mes prochains articles MSDN ? Si c’est le cas, déposez-moi une ligne à mitchell@4GuysFromRolla.com.