Création d’une couche de logique métier (C#)
par Scott Mitchell
Dans ce tutoriel, nous allons voir comment centraliser vos règles métier dans une couche de 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é, 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 de 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 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.
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é 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 d’entreprise appropriées.
Pour séparer plus proprement les classes dal- et BLL, nous allons créer deux sous-dossiers dans le App_Code
dossier et DAL
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 didacticiel 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
, SuppliersBLL
et EmployeesBLL
.
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 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 (c’est-à-dire que 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, consultez le blog class Designer.
Pour la ProductsBLL
classe , nous devons ajouter un total de sept méthodes :
GetProducts()
retourne tous les produitsGetProductByProductID(productID)
retourne le produit avec l’ID de produit spécifiéGetProductsByCategoryID(categoryID)
retourne tous les produits de la catégorie spécifiéeGetProductsBySupplier(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 passées ; retourne laProductID
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 passées ; retournetrue
si précisément une ligne a été mise à jour ; sinon,false
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 simplement des données GetProducts
, GetProductByProductID
, GetProductsByCategoryID
et GetProductBySuppliersID
sont assez simples, car elles appellent simplement le dal. Alors que dans certains scénarios, il peut y avoir des règles d’entreprise 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 alors 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 en tant que 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 nouveaux dans .NET 2.0 et fournissent une technique permettant d’indiquer si un type valeur doit être null
. En C#, vous pouvez marquer un type valeur comme un type nullable en ajoutant ?
après le type (comme int? x;
). Pour plus d’informations, consultez la section Types nullables du 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 une ligne affectée. Par exemple, si le développeur de la page appelle DeleteProduct
le passage d’un ProductID
pour un produit inexistant, l’instruction DELETE
émise pour la base de données n’aura aucun effet et, par conséquent, la DeleteProduct
méthode retournera 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 produit nouveau ou modifié comme une liste de scalaires au lieu d’accepter un 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 se présente lorsque nous nous tournons vers l’insertion et la mise à jour de produits à l’aide d’ObjectDataSource. En bref, ObjectDataSource essaiera de créer une instance des paramètres d’entrée. Si la méthode BLL 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 AddProduct
et UpdateProduct
, le code crée un ProductsRow
instance et le remplit avec les valeurs que vous venez de passer. 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 passées à 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 le produit à mettre à jour à l’aide de GetProductByProductID(productID)
. Bien que cela puisse sembler un voyage inutile vers la base de données, ce voyage supplémentaire s’avérera utile dans les prochains tutoriels qui explorent l’accès concurrentiel optimiste. L’accès concurrentiel 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 la BLL qui ne modifient qu’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 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, l’objet ObjectDataSource de ASP.NET 2.0 facilite l’accès déclaratif aux données à partir d’une classe. Pour faciliter le filtrage de la liste des classes possibles à lier dans l’Assistant ObjectDataSource, par défaut, seules les classes marquées comme DataObjects
sont affiché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 méthode de la classe est la méthode de UpdateSupplierAddress
la SuppliersBLL
classe . 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 pour le SupplierDataRow
spécifié supplierID
(à l’aide GetSupplierBySupplierID
de ), définit ses propriétés liées à l’adresse, puis appelle la SupplierDataTable
méthode de Update
. La UpdateSupplierAddress
méthode est la suivante :
[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 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 présentation doit fonctionner par rapport au fichier BLL à la place. 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 :
ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();
Pour utiliser les nouvelles classes BLL, il suffit de modifier la première ligne de code pour remplacer simplement 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 (tout comme le DataSet typé) à l’aide de ObjectDataSource. Nous allons aborder l’ObjetDataSource plus en détail dans les didacticiels suivants.
Figure 3 : La liste des produits est affichée 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 :
- La longueur du
ProductName
champ doit être inférieure ou inférieure à 40 caractères - La longueur du
QuantityPerUnit
champ doit être inférieure ou inférieure à 20 caractères - Les
ProductID
champs ,ProductName
etDiscontinued
sont obligatoires, mais tous les autres champs sont facultatifs - Les
UnitPrice
champs ,UnitsInStock
,UnitsOnOrder
etReorderLevel
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 UnitPrice
colonnes , UnitsInStock
, UnitsOnOrder
ou 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 ProductsDataRow
propriété sur une valeur de QuantityPerUnit
chaîne supérieure à 20 caractères, un ArgumentException
sera levée.
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.cs
.
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 UnitPrice
valeurs de colonne , UnitsInStock
, UnitsOnOrder
et ReorderLevel
(si ce n’est pas NULL
le cas ) sont supérieures ou égales à zéro. Si l’une de ces colonnes est hors limites, lèvez 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 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
.
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 à 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 HttpApplication
de Error
). Pour gérer une exception lors de l’utilisation du BLL par programmation, nous pouvons utiliser un try... catch block, comme le montre 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 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.