Partager via


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

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.

Introduction

La couche d’accès aux données (DAL) créée dans le premier tutoriel sépare proprement la logique d’accès aux données de la logique de présentation. Toutefois, bien que le DAL sépare proprement les détails d’accès aux données de la couche de présentation, il n’applique pas de règles métier qui peuvent s’appliquer. Par exemple, pour notre application, nous pouvons interdire la CategoryID modification des champs ou SupplierID de la Products table lorsque le Discontinued champ est défini sur 1, ou nous pourrions vouloir appliquer des règles d’ancienneté, en interdisant les situations dans lesquelles un employé est géré par une personne qui a été 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, le BLL doit être implémenté en tant que projet de bibliothèque de classes distinct ; Toutefois, pour ces tutoriels, nous allons implémenter le BLL sous forme de 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.

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

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

Plutôt que de créer des classes distinctes pour implémenter notre logique métier, nous pourrions également placer cette logique directement dans le DataSet typé avec des classes partielles. Pour obtenir un exemple de création et d’extension d’un DataSet typé, reportez-vous au premier tutoriel.

Étape 1 : Création des classes BLL

Notre BLL sera composé 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 respectif dans le DAL, en appliquant les règles métier appropriées.

Pour séparer plus proprement les classes DAL et BLL, nous allons créer deux sous-dossiers dans le App_Code dossier, DAL et BLL. Cliquez simplement avec le bouton droit sur le App_Code dossier dans le Explorateur de solutions et choisissez Nouveau dossier. Après avoir créé ces deux dossiers, déplacez le DataSet 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 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, nous allons ajouter des méthodes à chacune des classes pour simplement encapsuler les méthodes définies pour les TableAdapters du premier tutoriel. Pour l’instant, ces méthodes appellent simplement directement dans le DAL ; nous reviendrons plus tard pour ajouter toute logique métier nécessaire.

Notes

Si vous utilisez Visual Studio Standard Edition ou une version ultérieure (autrement dit, vous n’utilisez pas Visual Web Developer), vous pouvez éventuellement concevoir vos classes visuellement à l’aide de l’Designer de classe. Pour plus d’informations sur cette nouvelle fonctionnalité dans Visual Studio, reportez-vous au blog class Designer.

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 précisément une ligne a été mise à jour, False sinon
  • DeleteProduct(productID) supprime le produit spécifié de la base de données

ProductsBLL.vb

Imports NorthwindTableAdapters

<System.ComponentModel.DataObject()> _
Public Class ProductsBLL

    Private _productsAdapter As ProductsTableAdapter = Nothing
    Protected ReadOnly Property Adapter() As ProductsTableAdapter
        Get
            If _productsAdapter Is Nothing Then
                _productsAdapter = New ProductsTableAdapter()
            End If

            Return _productsAdapter
        End Get
    End Property

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, True)> _
    Public Function GetProducts() As Northwind.ProductsDataTable
        Return Adapter.GetProducts()
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductByProductID(ByVal productID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductByProductID(productID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsByCategoryID(categoryID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsBySupplierID(ByVal supplierID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsBySupplierID(supplierID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Insert, True)> _
    Public Function AddProduct( _
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean) _
        As Boolean

        Dim products As New Northwind.ProductsDataTable()
        Dim product As Northwind.ProductsRow = products.NewProductsRow()

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        products.AddProductsRow(product)
        Dim rowsAffected As Integer = Adapter.Update(products)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(_
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean, productID As Integer) _
        As Boolean

        Dim products As Northwind.ProductsDataTable = _
            Adapter.GetProductByProductID(productID)

        If products.Count = 0 Then
            Return False
        End If

        Dim product as Northwind.ProductsRow = products(0)

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        Dim rowsAffected As Integer = Adapter.Update(product)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Delete, True)> _
    Public Function DeleteProduct(ByVal productID As Integer) As Boolean
        Dim rowsAffected As Integer = Adapter.Delete(productID)

        Return rowsAffected = 1
    End Function
End Class

Les méthodes qui retournent simplement des données GetProducts, GetProductByProductID, GetProductsByCategoryIDet GetProductBySuppliersID sont assez simples, car elles appellent simplement vers le bas dans le DAL. Bien que 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 telles quelles. Pour ces méthodes, le 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 AddProduct méthodes et UpdateProduct prennent toutes deux comme paramètres les valeurs des différents champs de produit et ajoutent un nouveau produit ou mettent à jour un produit existant, respectivement. Étant donné que de nombreuses colonnes de la Product table peuvent accepter NULL des valeurs (CategoryID, SupplierID, et UnitPrice, pour n’en nommer que quelques-unes), ces paramètres d’entrée pour AddProduct et UpdateProduct qui mappent à ces colonnes utilisent des types nullables. Les types nullables sont des nouveautés dans .NET 2.0 et fournissent une technique permettant d’indiquer si un type de valeur doit plutôt être Nothing. Pour plus d’informations, reportez-vous à l’article de blog de Paul VickThe Truth About Nullable Types and VB et à la documentation technique sur la structure Nullable .

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 aboutir à une ligne affectée. Par exemple, si le développeur de page appelle DeleteProduct en passant un ProductID pour un produit inexistant, l’instruction DELETE émise à la base de données n’aura aucun effet et la méthode retournera Falsedonc DeleteProduct .

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 produit nouveau ou modifié comme une liste de scalaires, au lieu d’accepter une ProductsRow instance. 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 un ProductsRow instance, nous devons d’abord créer un ProductsDataTable instance, puis appeler sa NewProductRow() méthode (ce que nous faisons dans AddProduct). Cette lacune est en arrière lorsque nous nous tournons vers l’insertion et la mise à jour de produits à l’aide de ObjectDataSource. En bref, ObjectDataSource essaiera de créer une instance des paramètres d’entrée. Si la méthode BLL s’attend à un ProductsRow instance, ObjectDataSource tente d’en créer un, 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 et AddProductUpdateProduct, le code crée un ProductsRow instance et le remplit avec les valeurs qui viennent d’être transmises. Lors de l’affectation de valeurs à DataColumns d’un DataRow, différentes vérifications de validation au niveau du champ peuvent se produire. Par conséquent, le fait de replacer 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. Au lieu de cela, 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 dans le produit pour mettre à jour à l’aide de GetProductByProductID(productID). Bien que cela puisse sembler être un déplacement inutile dans la base de données, ce voyage supplémentaire s’avérera utile dans les prochains tutoriels qui explorent l’accès concurrentiel optimiste. La concurrence optimiste est une technique qui permet de s’assurer que deux utilisateurs qui travaillent simultanément sur les mêmes données ne remplacent pas accidentellement les modifications de l’autre. L’obtention de l’enregistrement entier facilite également la création de méthodes de mise à jour dans le BLL qui modifient uniquement un sous-ensemble des colonnes de DataRow. Lorsque nous explorons la SuppliersBLL classe, nous voyons un tel exemple.

Enfin, notez que l’attribut DataObject est appliqué à la ProductsBLL classe (syntaxe [System.ComponentModel.DataObject] 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 approprié pour la liaison à un contrôle ObjectDataSource, tandis que le DataObjectMethodAttribute indique l’objectif de la méthode. Comme nous le verrons dans les prochains tutoriels, ObjectDataSource de ASP.NET 2.0 permet d’accéder facilement aux données d’une classe de manière déclarative. Pour vous aider à filtrer la liste des classes possibles à lier dans l’Assistant ObjectDataSource, par défaut, seules les classes marquées comme DataObjects sont indiquées dans la liste déroulante de l’Assistant. La ProductsBLL classe fonctionnera tout aussi bien sans ces attributs, mais leur ajout facilite l’utilisation dans l’Assistant ObjectDataSource.

Ajout des autres classes

Une fois la ProductsBLL classe terminée, nous devons toujours 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 seule méthode à noter est la méthode de la SuppliersBLLUpdateSupplierAddress classe. Cette méthode fournit une interface permettant de mettre à jour uniquement les informations d’adresse du fournisseur. En interne, cette méthode lit dans l’objet SupplierDataRow pour le spécifié supplierID (à l’aide GetSupplierBySupplierIDde ), définit ses propriétés liées à l’adresse, puis appelle la SupplierDataTableméthode de Update . La UpdateSupplierAddress méthode est la suivante :

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateSupplierAddress(ByVal supplierID As Integer, _
    ByVal address As String, ByVal city As String, ByVal country As String) _
    As Boolean

    Dim suppliers As Northwind.SuppliersDataTable = _
        Adapter.GetSupplierBySupplierID(supplierID)

    If suppliers.Count = 0 Then
        Return False
    Else
        Dim supplier As Northwind.SuppliersRow = suppliers(0)

        If address Is Nothing Then
            supplier.SetAddressNull()
        Else
            supplier.Address = address
        End If

        If city Is Nothing Then
            supplier.SetCityNull()
        Else
            supplier.City = city
        End If

        If country Is Nothing Then
            supplier.SetCountryNull()
        Else
            supplier.Country = country
        End If

        Dim rowsAffected As Integer = Adapter.Update(supplier)

        Return rowsAffected = 1
    End If
End Function

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

Étape 2 : Accès aux DataSets typés via les classes BLL

Dans le premier tutoriel, nous avons vu des exemples d’utilisation directe du DataSet typé par programmation, mais avec l’ajout de nos classes BLL, le niveau de présentation doit plutôt fonctionner sur le BLL. Dans l’exemple AllProducts.aspx du premier tutoriel, le ProductsTableAdapter a été utilisé pour lier la liste des produits à un GridView, comme indiqué dans le code suivant :

Dim productsAdapter As New ProductsTableAdapter()
GridView1.DataSource = productsAdapter.GetProducts()
GridView1.DataBind()

Pour utiliser les nouvelles classes BLL, il suffit de modifier la première ligne de code en remplaçant simplement l’objet ProductsTableAdapter par un ProductBLL objet :

Dim productLogic As New ProductsBLL()
GridView1.DataSource = productLogic.GetProducts()
GridView1.DataBind()

Les classes BLL sont également accessibles de manière déclarative (tout comme le DataSet typé) à l’aide de ObjectDataSource. Nous aborderons l’ObjetDataSource plus en détail dans les tutoriels suivants.

La liste des produits s’affiche dans un GridView

Figure 3 : La liste des produits s’affiche dans un GridView (cliquer pour afficher une image en taille réelle)

Étape 3 : Ajout de Field-Level validation aux classes 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 contenir 40 caractères ou moins
  • Le QuantityPerUnit champ doit contenir 20 caractères ou moins
  • Les ProductIDchamps , ProductNameet Discontinued sont obligatoires, mais tous les autres champs sont facultatifs
  • Les UnitPricechamps , UnitsInStock, UnitsOnOrderet 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 ProductName champs et QuantityPerUnit est capturée par les types de données de ces colonnes dans la Products table (nvarchar(40) et nvarchar(20), respectivement). Si les champs sont obligatoires et facultatifs sont exprimés par si la colonne de la table de base de données autorise NULL s. Il existe quatre contraintes case activée qui garantissent que seules les valeurs supérieures ou égales à zéro peuvent être intégrées aux UnitPricecolonnes , UnitsInStock, UnitsOnOrderou ReorderLevel .

Outre l’application de ces règles au niveau de la base de données, elles doivent également être appliquées au niveau du DataSet. En fait, la longueur du champ et si une valeur est requise ou facultative sont déjà capturées pour chaque jeu dataTable de DataColumns. Pour voir la validation au niveau du champ fournie automatiquement, accédez à l’Designer DataSet, sélectionnez un champ dans l’un des DataTables, puis accédez au Fenêtre Propriétés. Comme le montre la figure 4, le QuantityPerUnit DataColumn dans a ProductsDataTable une longueur maximale de 20 caractères et autorise NULL les valeurs. Si nous essayons de définir la ProductsDataRowpropriété sur une valeur de QuantityPerUnit chaîne supérieure à 20 caractères, un ArgumentException sera levée.

DataColumn fournit la validation Field-Level de base

Figure 4 : DataColumn fournit la validation Field-Level de base (cliquez pour afficher l’image en taille réelle)

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 le 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 le DataSet 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.vb.

Ajouter une nouvelle classe au dossier App_Code

Figure 5 : Ajouter une nouvelle classe au App_Code dossier (cliquer pour afficher l’image en taille réelle)

Ensuite, créez un gestionnaire d’événements pour l’événement ColumnChanging qui garantit que les UnitPricevaleurs de colonne , UnitsInStock, UnitsOnOrderet ReorderLevel (si ce n’est pas NULLle cas ) sont supérieures ou égales à zéro. Si l’une de ces colonnes est hors limites, lèvez un ArgumentException.

ProductsDataTable.ColumnChanging.vb

Imports System.data

Partial Public Class Northwind
    Partial Public Class ProductsDataTable
        Public Overrides Sub BeginInit()
            AddHandler Me.ColumnChanging, AddressOf ValidateColumn
        End Sub

        Sub ValidateColumn(sender As Object, e As DataColumnChangeEventArgs)
            If e.Column.Equals(Me.UnitPriceColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Decimal) < 0 Then
                    Throw New ArgumentException( _
                        "UnitPrice cannot be less than zero", "UnitPrice")
                End If
            ElseIf e.Column.Equals(Me.UnitsInStockColumn) OrElse _
                e.Column.Equals(Me.UnitsOnOrderColumn) OrElse _
                e.Column.Equals(Me.ReorderLevelColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Short) < 0 Then
                    Throw New ArgumentException(String.Format( _
                        "{0} cannot be less than zero", e.Column.ColumnName), _
                        e.Column.ColumnName)
                End If
            End If
        End Sub
    End Class
End Class

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

Outre la validation au niveau du champ, il peut exister des règles métier personnalisées de haut niveau qui impliquent différentes entités ou concepts non expressibles au niveau d’une seule colonne, par exemple :

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

Les classes BLL doivent contenir des vérifications pour garantir le respect des 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 d’entreprise imposent qu’un produit ne puisse pas être marqué comme étant abandonné s’il était le 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 ne pourrions pas marquer X comme étant abandonné ; si, cependant, le fournisseur Y nous a fourni trois produits, A, B, et C, alors nous pourrions marquer n’importe lequel et tous ceux-ci comme abandonnés. Une règle d’entreprise étrange, mais les règles d’entreprise et le bon sens ne sont pas toujours alignés !

Pour appliquer cette règle métier dans la UpdateProducts méthode, nous allons commencer par vérifier si Discontinued a été défini True sur et, si c’est le cas, nous appelons GetProductsBySupplierID pour déterminer le nombre de produits que nous avons achetés auprès du fournisseur de ce produit. Si un seul produit est acheté auprès de ce fournisseur, nous lèveons un ApplicationException.

<System.ComponentModel.DataObjectMethodAttribute_
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct( _
    productName As String, supplierID As Nullable(Of Integer), _
    categoryID As Nullable(Of Integer), quantityPerUnit As String, _
    unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
    unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
    discontinued As Boolean, productID As Integer) _
    As Boolean

    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)

    If products.Count = 0 Then
        Return False
    End If

    Dim product As Northwind.ProductsRow = products(0)

    If discontinued Then
        Dim productsBySupplier As Northwind.ProductsDataTable = _
            Adapter.GetProductsBySupplierID(product.SupplierID)

        If productsBySupplier.Count = 1 Then
            Throw New ApplicationException( _
                "You cannot mark a product as discontinued if it is " & _
                "the only product purchased from a supplier")
        End If
    End If

    product.ProductName = productName

    If Not supplierID.HasValue Then
        product.SetSupplierIDNull()
    Else
        product.SupplierID = supplierID.Value
    End If

    If Not categoryID.HasValue Then
        product.SetCategoryIDNull()
    Else
        product.CategoryID = categoryID.Value
    End If

    If quantityPerUnit Is Nothing Then
        product.SetQuantityPerUnitNull()
    Else
        product.QuantityPerUnit = quantityPerUnit
    End If

    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If

    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If

    If Not unitsOnOrder.HasValue Then
        product.SetUnitsOnOrderNull()
    Else
        product.UnitsOnOrder = unitsOnOrder.Value
    End If

    If Not reorderLevel.HasValue Then
        product.SetReorderLevelNull()
    Else
        product.ReorderLevel = reorderLevel.Value
    End If

    product.Discontinued = discontinued

    Dim rowsAffected As Integer = Adapter.Update(product)

    Return rowsAffected = 1
End Function

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

Lors de l’appel de la BLL à partir du niveau de présentation, nous pouvons décider s’il faut tenter de gérer les exceptions qui peuvent être déclenchées ou les laisser buller jusqu’à ASP.NET (ce qui déclenche l’événement HttpApplicationde Error ). Pour gérer une exception lors de l’utilisation du BLL par programmation, nous pouvons utiliser un try... Bloc catch , comme le montre l’exemple suivant :

Dim productLogic As New ProductsBLL()

Try
    productLogic.UpdateProduct("Scotts Tea", 1, 1, Nothing, _
      -14, 10, Nothing, Nothing, False, 1)
Catch ae As ArgumentException
    Response.Write("There was a problem: " & ae.Message)
End Try

Comme nous le verrons dans les prochains tutoriels, la gestion des exceptions qui s’affichent à 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 à inclure du code dans Try...Catch des blocs.

Résumé

Une application bien conçue est conçue en couches distinctes, chacune encapsulant 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 de logique métier sous la forme d’une série de classes dans le dossier de App_Code notre application qui appellent notre dal. Le BLL implémente la logique au niveau du champ et au niveau de l’entreprise pour notre application. En plus de créer une BLL distincte, comme nous l’avons fait dans ce tutoriel, une autre option consiste à étendre les méthodes de TableAdapters en utilisant des classes partielles. Toutefois, l’utilisation de cette technique ne nous permet pas de remplacer les méthodes existantes et ne sépare pas notre DAL et notre BLL aussi proprement que l’approche que nous avons adoptée dans cet article.

Une fois le DAL et le BLL terminés, nous sommes prêts à commencer sur notre couche de présentation. Dans le tutoriel suivant , nous allons faire un bref détour par les rubriques d’accès aux données et définir une mise en page cohérente à utiliser tout au long des 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 comme consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Il est accessible à l’adressemitchell@4GuysFromRolla.com . ou via son blog, qui se trouve à l’adresse http://ScottOnWriting.NET.

Remerciements spéciaux à

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.