In this tutorial
Section 1The Data Access Layer (DAL) described clearly separates the presentation logic from the Data Access logic. However, even if DAL separates the details of data access from the presentation layer, it cannot process any business rules. For example, we may not want the "Category Number" or "supplier number" of products marked as "Deactivated" in the product table to be updated; we may also need to apply some qualification rules, for example, we do not want to be managed by people who are less qualified than ourselves. Another common situation is authorization. For example, only users with special permissions can delete the product or change the unit price.
In fact, we can regard the Business Logic Layer (BLL) as a bridge between the data access Layer and the presentation Layer for data exchange. In this chapter, we will discuss how to integrate these business rules into a BLL. It should be noted that in an actual application, BLL is implemented in the form of Class Library, but to simplify the project structure, in this tutorial, we implement BLL as a series of classes in the App_Code folder. Figure 1 shows the structural relationship among presentation layer, BLL, and DAL.
Figure 1: BLL separates the presentation layer from the DAL and adds Business Rules
Step 1: Create a BLL class
Our BLL consists of four classes. Each BLL class corresponds to a TableAdapter In the DAL, they all obtain read, insert, modify, and delete methods from their TableAdapter to apply appropriate business rules.
To clearly distinguish the DAL and BLL classes, we create two subfolders named DAL and BLL respectively in the App_Code folder. You just need to right-click the App_Code Folder in Solution Explorer and select New Folder to create a New sub-Folder. After the two folders are created, move the Typed DataSet (Typed DataSet) created in section 1 to the DAL folder.
Then, create four class files in the BLL folder. Similarly, you just need to right-click the BLL folder in Solution Explorer and select New Item ), in the displayed dialog box, select a Class template to create a new Class file. Name the four files ProductsBLL, CategoriesBLL, SuppliersBLL, and EmployeesBLL.
Figure 2: add four new classes to The BLL folder
Next, let's add some methods to these newly created classes and simply wrap the methods in TableAdapter in section 1. Now, these methods can only directly use those methods in the DAL, And we will add some business logic to them later.
Note: If you are using Visual Studio standard or later (that is, you are not using Visual Web Developer), you can also use Class Designer to visually design your Class. You can get detailed information about this new function of Visual Studio on the Class Designer Blog.
In the ProductsBLL class, we need to add seven methods to it:
· GetProducts ()-returns all products
· GetProductByProductID (productID)-returns the product of the specified ProductID.
· GetProductsByCategoryID (categoryID)-return the product of the specified category
· GetProductsBySupplier (supplierID)-return the product of the specified supplier
· AddProduct (productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued)-adds a product information to the database and returns the ProductID of the newly added product.
· UpdateProduct (productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID)-updates an existing product in a database. If a record is updated, returns true; otherwise, returns false.
· DeleteProduct (productID)-delete the product of the specified ProductID
ProductsBLL. cs
1 using System;
2 using System. Data;
3 using System. Configuration;
4 using System. Web;
5 using System. Web. Security;
6 using System. Web. UI;
7 using System. Web. UI. WebControls;
8 using System. Web. UI. WebControls. WebParts;
9 using System. Web. UI. HtmlControls;
10 using NorthwindTableAdapters;
11
12 [System. ComponentModel. DataObject]
13 public class ProductsBLL
14 {
15 private ProductsTableAdapter _ productsAdapter = null;
16 protected ProductsTableAdapter Adapter
17 {
18 get {
19 if (_ productsAdapter = null)
20 _ productsAdapter = new ProductsTableAdapter ();
21
22 return _ productsAdapter;
23}
24}
25
26
27 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Select, true)]
28 public Northwind. ProductsDataTable GetProducts ()
29 {
30 return Adapter. GetProducts ();
31}
32
33 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Select, false)]
34 public Northwind. ProductsDataTable GetProductByProductID (int productID)
35 {
36 return Adapter. GetProductByProductID (productID );
37}
38
39 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Select, false)]
40 public Northwind. ProductsDataTable GetProductsByCategoryID (int categoryID)
41 {
42 return Adapter. GetProductsByCategoryID (categoryID );
43}
44
45 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Select, false)]
46 public Northwind. ProductsDataTable GetProductsBySupplierID (int supplierID)
47 {
48 return Adapter. GetProductsBySupplierID (supplierID );
49}
50 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Insert, true)]
51 public bool AddProduct (string productName, int? SupplierID, int? CategoryID, string quantityPerUnit,
52 decimal? UnitPrice, short? UnitsInStock, short? UnitsOnOrder, short? ReorderLevel,
53 bool discontinued)
54 {
55 // create a ProductRow instance
56 Northwind. ProductsDataTable products = new Northwind. ProductsDataTable ();
57 Northwind. ProductsRow product = products. NewProductsRow ();
58
59 product. ProductName = productName;
60 if (supplierID = null) product. SetSupplierIDNull (); else product. SupplierID = supplierID. Value;
61 if (categoryID = null) product. SetCategoryIDNull (); else product. CategoryID = categoryID. Value;
62 if (quantityPerUnit = null) product. SetQuantityPerUnitNull (); else product. QuantityPerUnit = quantityPerUnit;
63 if (unitPrice = null) product. SetUnitPriceNull (); else product. UnitPrice = unitPrice. Value;
64 if (unitsInStock = null) product. SetUnitsInStockNull (); else product. UnitsInStock = unitsInStock. Value;
65 if (unitsOnOrder = null) product. SetUnitsOnOrderNull (); else product. UnitsOnOrder = unitsOnOrder. Value;
66 if (reorderLevel = null) product. SetReorderLevelNull (); else product. ReorderLevel = reorderLevel. Value;
67 product. Discontinued = discontinued;
68
69 // Add new product
70 products. AddProductsRow (product );
71 int rowsAffected = Adapter. Update (products );
72
73 // if a new record is added, true is returned; otherwise, false is returned.
74 return rowsAffected = 1;
75}
76
77 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Update, true)]
78 public bool UpdateProduct (string productName, int? SupplierID, int? CategoryID, string quantityPerUnit,
79 decimal? UnitPrice, short? UnitsInStock, short? UnitsOnOrder, short? ReorderLevel,
80 bool discontinued, int productID)
81 {
82 Northwind. ProductsDataTable products = Adapter. GetProductByProductID (productID );
83 if (products. Count = 0)
84 // if no matching record is found, false is returned.
85 return false;
86
87 Northwind. ProductsRow product = products [0];
88
89 product. ProductName = productName;
90 if (supplierID = null) product. SetSupplierIDNull (); else product. SupplierID = supplierID. Value;
91 if (categoryID = null) product. SetCategoryIDNull (); else product. CategoryID = categoryID. Value;
92 if (quantityPerUnit = null) product. SetQuantityPerUnitNull (); else product. QuantityPerUnit = quantityPerUnit;
93 if (unitPrice = null) product. SetUnitPriceNull (); else product. UnitPrice = unitPrice. Value;
94 if (unitsInStock = null) product. SetUnitsInStockNull (); else product. UnitsInStock = unitsInStock. Value;
95 if (unitsOnOrder = null) product. SetUnitsOnOrderNull (); else product. UnitsOnOrder = unitsOnOrder. Value;
96 if (reorderLevel = null) product. SetReorderLevelNull (); else product. ReorderLevel = reorderLevel. Value;
97 product. Discontinued = discontinued;
98
99 // update product records
100 int rowsAffected = Adapter. Update (product );
101
102 // If a record is updated, true is returned; otherwise, false is returned.
103 return rowsAffected = 1;
104}
105
106 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Delete, true)]
107 public bool DeleteProduct (int productID)
108 {
109 int rowsAffected = Adapter. Delete (productID );
110
111 // If a record is deleted, true is returned; otherwise, false is returned.
112 return rowsAffected = 1;
113}
114}
115
The GetProducts, GetProductByProductID, GetProductsByCategoryID, and GetProductBySuppliersID methods simply call the methods in the DAL to return data. However, in some cases, we may need to implement some business rules for them (for example, authorization rules, different users or roles should be able to see different data ), now we can simply make them like this. For these methods, BLL serves only as a proxy between the presentation layer and the DAL.
AddProduct and UpdateProduct both use the product information in the parameter to add or update a product record. Because many fields in the Product table allow null values (CategoryID, SupplierID, UnitPrice ...... So the parameters in AddProduct and UpdateProduct use nullable types. Nullable types is a new technique provided in. NET 2.0 to indicate whether a value type can be empty. In C #, you can add a question mark (for example, int x;) after a value type that can be null ;). For more information about Nullable Types, see C # Programming Guide.
Because insertion, modification, and deletion may not affect any rows, A bool value is returned to indicate whether the operation is successful. For example, a page developer uses an existing ProductID to call DeleteProduct. Obviously, the DELETE statement submitted to the database does not have any effect, so DeleteProduct returns false.
Note: When we add or update details of a product, we accept a scalar list composed of product information, rather than directly accepting a ProductsRow instance. Because ProductsRow inherits from ADO. NET DataRow, and DataRow does not have a default no-argument constructor. To create a ProductsRow instance, we must first create a ProductsDataTable instance, then call its NewProductRow method (as we did in the AddProduct method ). However, when I use ObjectDataSource to insert or update data, the disadvantages of this method will be exposed. In short, ObjectDataSource will try to create an instance for the input parameter. If the BLL method wants to get a ProductsRow, ObjectDataSource will try to create one, but obviously, this operation will certainly fail, because there is no default no-argument constructor. For details about this issue, you can find it in the following two posts of the ASP. NET Forum: Updating ObjectDataSources with stronugly-Typed DataSets, Problem With ObjectDataSource and stronugly-Typed DataSet.
Then, in AddProduct and UpdateProduct, we create a ProductsRow instance and assign the passed parameters to it. When a value is assigned to the DataColumns of a DataRow, validation of various fields may be triggered. Therefore, we should manually verify the passed parameters to ensure that the data passed to the BLL method is valid. Unfortunately, Visual Studio does not use nullable values for a strongly-typed Dataset (stronugly-typed DataRow. To indicate that a DataColumn in DataRow can accept null values, we must use the SetColumnNameNull method.
In UpdateProduct, we first use the GetProductByProductID (productID) method to read the product information to be updated. There seems to be no need to do this, but we will prove that this additional operation works in later Optimistic concurrency courses. Concurrency optimization is a technology that ensures that two users operate on one data at a time without conflict. Obtaining the entire record also makes it easier to create a method that updates only some columns of DataRow. We can find this example in the SuppliersBLL class.
Finally, note that the DataObject tag ([System. ComponentModel. DataObject] on the top of the class declaration statement) is added to the ProductsBLL class. The DataObjectMethodAttribute tag is also added to each method. The DataObject tag marks this class as binding to an ObjectDataSource control, while DataObjectMethodAttribute describes the purpose of this method. We will see in later Tutorials that the ObjectDataSource of ASP. NET 2.0 makes it easier to access data from a class. To properly filter existing classes in the ObjectDataSource wizard, only classes marked as DataObject are displayed in the class list by default. Of course, the ProductsBLL class can work without this label, but adding it makes operations in the ObjectDataSource wizard easier and more pleasant.
Add other classes
After the ProductsBLL class is completed, we also add some classes for the categories, suppliers, and employees services. Let's take some time to create the following class, which can be done according to the above example:
· CategoriesBLL. cs
O GetCategories ()
O GetCategoryByCategoryID (categoryID)
· SuppliersBLL. cs
O GetSuppliers ()
O GetSupplierBySupplierID (supplierID)
O GetSuppliersByCountry (country)
O UpdateSupplierAddress (supplierID, address, city, country)
· EmployeesBLL. cs
O GetEmployees ()
O GetEmployeeByEmployeeID (employeeID)
O GetEmployeesByManager (managerID)
The UpdateSupplierAddress method in the SuppliersBLL class is worth noting. This method provides an interface for updating only the supplier address information. It first reads a SupplierDataRow Based on the specified SupplierID (using the GetSupplierBySupplierID method), sets all its attributes about the address, and then calls the Update method of SupplierDataTable. The UpdateSupplierAddress method code is as follows:
UpdateSupplierAddress
1 [System. ComponentModel. DataObjectMethodAttribute (System. ComponentModel. DataObjectMethodType. Update, true)]
2 public bool UpdateSupplierAddress (int supplierID, string address, string city, string country)
3 {
4 Northwind. SuppliersDataTable suppliers = Adapter. GetSupplierBySupplierID (supplierID );
5 if (suppliers. Count = 0)
6 // if no matching item is found, false is returned.
7 return false;
8 else
9 {
10 Northwind. SuppliersRow supplier = suppliers [0];
11
12 if (address = null) supplier. SetAddressNull (); else supplier. Address = address;
13 if (city = null) supplier. SetCityNull (); else supplier. City = city;
14 if (country = null) supplier. SetCountryNull (); else supplier. Country = country;
15
16 // update the supplier's address information
17 int rowsAffected = Adapter. Update (supplier );
18
19 // If a record is updated, true is returned; otherwise, false is returned.
20 return rowsAffected = 1;
21}
22}
23
You can download the complete BLL class code from the link at the top of the page.
Step 2: access the typed dataset through The BLL class
In the first section of this tutorial, we provide an example of using a typed dataset directly. However, after we add the BLL class, the presentation layer can work through BLL. In the example of AllProducts. aspx in Section 1 of this tutorial, ProductsTableAdapter is used to bind the product list to the GridView. The Code is as follows:
1 ProductsTableAdapter productsAdapter = new ProductsTableAdapter ();
2 GridView1.DataSource = productsAdapter. GetProducts ();
3 GridView1.DataBind ();
To use the new BLL class, all we need to do is simply modify the first line of code. Replace ProductsTableAdapter with a ProductBLL object:
1 ProductsBLL productLogic = new ProductsBLL ();
2 GridView1.DataSource = productLogic. GetProducts ();
3 GridView1.DataBind ();
The BLL class can also be clearly accessed by using ObjectDataSource (just like a typed dataset ). We will discuss ObjectDataSource in detail in the next tutorial.
Figure 3: product list displayed in the GridView
Step 3: add field-level verification to DataRow
Field-level verification is to check all attribute values involved in the business object when inserting or updating. For example, some field-level verification rules are as follows:
· The ProductName field cannot exceed 40 characters
· The QuantityPerUnit field cannot exceed 20 characters
· The ProductID, ProductName, and Discontinued fields are required, while other fields are optional.
· The UnitPrice, UnitsInStock, UnitsOnOrder, and ReorderLevel fields must not be less than 0.
These rules can or should be described at the database layer. To limit the number of characters in the ProductName and QuantityPerUnit fields, you can use the data type of the corresponding columns in the Products table (nvarchar (40) and nvarchar (20) respectively )). You can set the corresponding column of the table in the database to "allowed to be NULL" for the field "required. To ensure that the values of the UnitPrice, UnitsInStock, UnitsOnOrder, and ReorderLevel fields are not less than 0, you can add a constraint to the corresponding columns respectively.
In addition to applying these rules to databases, these rules will also be applied to DataSet. In fact, the field length and whether to allow null information have been applied to the DataColumn set of each able. We can see the existing field-level verification in the DataSet Designer. Select a field from a DataTable and then find it in the Properties window. 4. The QuantityPerUnit field in ProductDataTable allows null values and the maximum length is 20 characters. If we try to set a string with a length greater than 20 characters for the QuantityPerUnit attribute of a ProductsDataRow, an ArgumentException will be thrown.
Figure 4: DataColumn provides basic field-level verification
Unfortunately, we cannot specify a boundary check through the attribute window. For example, the value of UnitPrice cannot be less than 0. To provide such field-level verification, we need to create an Event Handler for the ColumnChanging Event of the DataTable. As mentioned in the previous tutorial, DataSet, able, and DataRow objects created from typed datasets can be extended using the partial class. With this technology, we can create a ColumnChanging Event Handler for ProductDataTable. Create a new class file named ProductsDataTable. ColumnChanging. cs in the App_Code folder, as shown in.
Figure 5: Add a new class in the App_Code folder
Create an Event handler for the ColumnChanging Event to ensure that the values of the UnitPrice, UnitsInStock, UnitsOnOrder, and ReorderLevel fields are not less than 0. If the values of these columns are out of the range, an ArgumentException is thrown.
ProductsDataTable. ColumnChanging. cs
1 public partial class Northwind
2 {
3 public partial class ProductsDataTable
4 {
5 public override void BeginInit ()
6 {
7 this. ColumnChanging + = ValidateColumn;
8}
9
10 void ValidateColumn (object sender, DataColumnChangeEventArgs e)
11 {
12 if (e. Column. Equals (this. UnitPriceColumn ))
13 {
14 if (! Convert. IsDBNull (e. ProposedValue) & (decimal) e. ProposedValue <0)
15 {
16 throw new ArgumentException ("UnitPrice cannot be less than zero", "UnitPrice ");
17}
18}
19 else if (e. Column. Equals (this. UnitsInStockColumn) |
20 e. Column. Equals (this. UnitsOnOrderColumn) |
21 e. Column. Equals (this. ReorderLevelColumn ))
22 {
23 if (! Convert. IsDBNull (e. ProposedValue) & (short) e. ProposedValue <0)
24 {
25 throw new ArgumentException (string. Format ("{0} cannot be less than zero", e. Column. ColumnName), e. Column. ColumnName );
26}
27}
28}
29}
30}
Step 4: add business rules to the BLL class
In addition to field-level verification, there may be more advanced business rules that cannot be expressed in a single column that contain different entities or concepts, such:
· If a product is marked as "disabled", its unit price cannot be modified.
· An employee's place of residence must be the same as that of his/her supervisor
· If a product is the only product provided by a supplier, it cannot be marked as "disabled"
The BLL class should always verify the business rules of the application. These verifications can be directly added to their methods.
Imagine that our business rules indicate that if a product is the only product of a given supplier, it cannot be marked as "disabled ". That is to say, if product X is the only product we purchased from supplier Y, we cannot mark it as disabled. However, if the vendor Y provides us with A total of three products, namely A, B, and C, we can mark any or all of them as "disabled ". Strange business rules, right? But the commercial rules are usually different from what we usually feel.
To apply this business rule in the UpdateProducts method, we should first check whether Discontinued is set to true. If so, call GetProductsBySupplierID to see how many products we have purchased from this supplier. If we only purchased this product from this supplier, we will throw an ApplicationException.
UpdateProduct
1 public bool UpdateProduct (string productName, int? SupplierID, int? CategoryID, string quantityPerUnit,
2 decimal unitPrice, short? UnitsInStock, short? UnitsOnOrder, short? ReorderLevel,
3 bool discontinued, int productID)
4 {
5 Northwind. ProductsDataTable products = Adapter. GetProductByProductID (productID );
6 if (products. Count = 0)
7 // if no match is found, false is returned.
8 return false;
9
10 Northwind. ProductsRow product = products [0];
11
12 // Business Rule Check-the only product provided by a supplier cannot be deactivated
13 if (discontinued)
14 {
15 // obtain all the products we obtain from this supplier
16 Northwind. ProductsDataTable productsBySupplier = Adapter. GetProductsBySupplierID (product. SupplierID );
17
18 if (productsBySupplier. Count = 1)
19 // This is the only product we have obtained from this supplier.
20 throw new ApplicationException ("You cannot mark a product as discontinued if its only product purchased from a supplier ");
21}
22
23 product. ProductName = productName;
24 if (supplierID = null) product. SetSupplierIDNull (); else product. SupplierID = supplierID. Value;
25 if (categoryID = null) product. SetCategoryIDNull (); else product. CategoryID = categoryID. Value;
26 if (quantityPerUnit = null) product. SetQuantityPerUnitNull (); else product. QuantityPerUnit = quantityPerUnit;
27 if (unitPrice = null) product. SetUnitPriceNull (); else product. UnitPrice = unitPrice. Value;
28 if (unitsInStock = null) product. SetUnitsInStockNull (); else product. UnitsInStock = unitsInStock. Value;
29 if (unitsOnOrder = null) product. SetUnitsOnOrderNull (); else product. UnitsOnOrder = unitsOnOrder. Value;
30 if (reorderLevel = null) product. SetReorderLevelNull (); else product. ReorderLevel = reorderLevel. Value;
31 product. Discontinued = discontinued;
32
33 // update product records
34 int rowsAffected = Adapter. Update (product );
35
36 // If a record is updated, true is returned; otherwise, false is returned.
37 return rowsAffected = 1;
38}
39
Respond to verification errors in the presentation layer
When we call BLL from the presentation layer, we can decide whether to handle an exception that may be thrown or let it be directly thrown to ASP. NET (this will cause an error in HttpApplication ). When using BLL, if you want to handle an exception programmatically, we can use try... catch Block, as shown in the following example:
1 ProductsBLL productLogic = new ProductsBLL ();
2
3 // update the product information with ProductID 1
4 try
5 {
6 // This operation will fail, because we try to use a UnitPrice smaller than 0
7 productLogic. UpdateProduct ("Scott's Tea", 1, 1, null,-14 m, 10, null, null, false, 1 );
8}
9 catch (ArgumentException AE)
10 {
11 Response. Write ("There was a problem:" + AE. Message );
12}
We will see in the following tutorial that when a data Web Control is used to insert, modify, or delete operation data, the exception thrown from BLL can be handled directly in an Event Handler without using try... Catch Block to wrap the code.
Summary
An application with a good architecture has a clear hierarchy, and each layer encapsulates a specific role. In the first article of this tutorial, we created a data access layer using a Typed Dataset. In this article, we created a business logic layer, it consists of a series of classes in App_Code and calls the corresponding methods in DAL. BLL implements the field-level and business-Level Logic for our applications. Apart from creating an independent BLL, as we have done in this section, another option is to use the partial class to extend the methods in TableAdapter. However, using this technology does not allow us to rewrite existing methods, nor separate Our DAL and BLL clearly enough.
After completing the DAL and BLL, we are ready to start processing the presentation layer. In the next tutorial, we will briefly introduce some data access topics and define a consistent page presentation for the entire tutorial.