This article will focus on how to abstract business rules to the business logic layer, which serves as a bridge between the display layer and the data access layer.
I. Getting Started
The Dal in the first article clearly separates the data access logic from the display layer. However, it is clear that the dal is separated from the display layer, but it does not execute any commercial logic. For example, if the discontinued field in the products table is identified as 1, you are not allowed to modify the values of the categoryid and supplierid fields, or you want to make some restrictions, such: A manager can only manage his employees. Another common scenario is authorization. For example, only a specific person can delete or modify a product.
This article describes how to implement these business rules. In actual applications, BLL usually appears as a class library project. However, to simplify the project structure, we add a series of class files to the app_code folder in our series. Shows the architectural relationship between the three layers.
Step 1: Create a BLL class
The entire BLL consists of four classes, one-to-one correspondence with tableadapter In Dal. Each class includes data acquisition, insertion, update, and deletion according to its business rules.
To better distinguish the classes in Dal from those in Bll, we create two subfolders named dal and BLL respectively in the app_code folder. Step: Right-click the "app_code" folder and select "create folder. Start the strongly-typed dataset created above to the Dal.
Next, create a class file in the BLL folder. Step: Right-click the "BLL" folder, select "Add new item", and select the "class" template. Add the productsbll, categoriesbll, suppliersbll, and employeesbll classes.
Next, add a method for the class to encapsulate the methods defined in the tableadapter created above. Currently, these methods can only be accessed through the Dal, and the required business logic will be gradually added later.
For the productsbll class, you must add the following seven methods:
Getproducts (), returns all items
Getproductbyproductid (productid), returns the product of a specific ID
Getproductsbycategoryid (categoryid), returns a category of items
Getproductsbysupplierid (supplierid), returns the product of a provider
Addproduct (...), add product
Updateproduct (...), update Product
Deleteproduct (productid) to delete a product
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;
}
}
These methods with returned values are the same as those in the Dal call. However, we can implement some special business logic (such as user authorization) at this layer. For these methods, bll simply acts as a proxy for the display layer to access data through the Dal.
The addproduct and updateproduct methods are used to insert and update data respectively through the input parameters (one-to-one correspondence with the fields in the table. Because many fields (categoryid and supplierid) in the products table can be empty, the corresponding parameters in the two methods also use data types that are allowed to be empty. The data types that allow NULL data are newly added by. net2.0. In C #, you can add a question mark (?) after the data type. To indicate that this type accepts null values (nullable), such as Int? X;
The Return Value of the insert, delete, and update methods is of the bool type to identify whether the operation is successful. For example, if an invalid ID is input in the deleteproduct method, no deletion is performed and false is returned.
Note: When we add a product or update an existing product, an instance with a list instead of productsrow is used because it inherits from ADO. net datarow class productsrow does not have the default no-argument constructor. We can create a productdatatable instance and use its newproductrow method to create a new productsrow instance. When we look back and use objectdatasource to insert and update data, the disadvantages of this method are obvious. That is to say, objectdatasource will try to create an input parameter instance, but it will fail because of the lack of a default parameter-free constructor.
In the addproducts and updateproducts methods below, a productrow instance is created and operated on by the input value. Field-level verification is performed when a column of a row is assigned a value. Therefore, manually entering the value of each row will help verify the validity of the data of the passed BLL method. However, the strong datarow automatically generated by vs does not support the nullable type. Therefore, you must use the setcolumnnamenull method to accept null values for a special column in the Data row.
In updateproducts, get the data to be updated through getproductbyproductid. This action seems redundant, but it makes sense to process concurrent access. "Effective concurrent access" means that if two users simultaneously access the same data in the database, there is no mutual impact. At the same time, it is convenient to retrieve the whole row record to modify only some fields in the row. When we develop a suppliersbll class, we will see this example.
Finally, note that the productsbll class applies the dataobject attribute and the method also applies the dataobjectmethodattribute feature. The dataobject feature specifies that this class can be used as an object to bind to an objectdatasource control. The same is true for dataobjectmethodattribute. In future articles, we will see that objectdatasource in ASP. net2.0 will make it very easy to access data through classes. In the objectdatasource wizard, to facilitate filtering, only the classes that identify the dataobject attribute are displayed in the drop-down list by default. Although adding or not these attributes does not affect the execution of productsbll, adding them makes it easier to use in the objectdatasource wizard.
1.1 add other classes
After the first class is complete, add the categories, suppliers, and employees classes. Simulate the creation of the first class above and create the following class:
Categoriesbll. CS
Getcategories ()
Getcategorybycategoryid (categoryid)
Suppliersbll. CS
Getsuppliers ()
Getsupplierbysupplierid (supplierid)
Getsuppliersbycountry (country)
Updatesupplieraddress (supplierid, address, city, country)
Employeesbll. CS
Getemployees ()
Getemployeebyemployeeid (employeeid)
Getemployeesbymanager (managerid)
Note that the updatesuppliersaddress method in suppliersbll provides an interface for updating only the supplier address information. Specifically, this method uses supplierdatarow to obtain the supplierid, obtain the address information, and then call the update method of supplierdatatable. The Code is as follows:
[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;
}
}
Step 2: access a strongly-Typed Dataset through The bll class
In the preceding section, we directly program a strongly Typed Dataset. After adding Bll, the display layer will access BLL instead of directly accessing Dal. In the allproducts. aspx File above, bind the data to the gridview using the following code:
Productstableadapter productsadapter = new productstableadapter ();
Gridview1.datasource = productsadapter. getproducts ();
Gridview1.databind ();
Now with Bll, the first line of code has changed. As follows:
Productsbll productlogic = new productsbll ();
Gridview1.datasource = productlogic. getproducts ();
Gridview1.databind ();
Classes in BLL can be called directly through objectdatasource. We will discuss the details of objectdatasource in future articles.
Step 3: add field-level verification for the datarow class
When inserting and updating data, field-level verification checks the value of the service object attribute. Field-level verification for the products table includes:
Productname cannot exceed 40 characters.
The quantityperunit field cannot contain more than 20 characters.
The productid, productname, and discontinued fields cannot be empty.
The value of the unitprice, unitsinstock, and unitonorder fields must be greater than or equal to 0.
These rules can and should be defined at the database level. The character limit of a field can be set by type in the database (nvarchar (40). It is optional when a field is required. It can be defined by whether the field is null; the existing constraints of the database ensure that the column value is greater than or equal to 0.
In addition to the database level, it can also be implemented at the dataset level. In fact, the length of a field and whether a field is null have been captured by the constraints of the table. You can check whether the field-level verification of this class exists through the attribute of a column in the Dataset Designer. It shows that the maximum length of the quantityperunit data column is 20 characters and can be null. If we try to assign a value of more than 20 characters to it, argumentexception will be generated.
Then, the boundary constraints cannot be defined through the attribute window, for example, unitprice cannot be defined to be greater than or equal to 0. To provide the field-level constraints of this type. You need to create an event processor for the columnchanging event of the datatable. You can create a partial class to expand dataset, able, and datarow. This technology allows you to create a columnchanging event processor for the productdatatable table. Add a class named productdatatable. columnchanging. CS to the app_code folder.
Next, create an event processor to ensure that the value of the unitprice and other columns is greater than or equal to 0. If the value of any column exceeds the boundary, an exception is thrown.
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 );
}
}
}
}
}
Step 4: add custom business logic to the BLL class
In addition to field-level verification, there are also some advanced custom business rules used to process entities and concepts that exceed a certain column level. For example:
If a product is discounted, the unit price cannot be updated.
The place of residence of a worker is the same as that of the manager.
If you only purchase one type of product from the provider, you cannot get a discount.
To implement these business rules, add constraints in BLL. These constraints can be directly written in the method.
Take "if you only buy one type of goods from the provider, you cannot get a discount" as an example. That is, if product A is the only product of provider B, you cannot get a discount. In addition, if the provider provides three types of products X, Y, and Z, the discount is acceptable. However, note that some business rules do not appear in pairs.
You can check whether discontinued is set to true in the updateproducts method to implement business rules. If yes, call getproductsbysupplierid to check the number of items purchased by the provider. If only one item is bought, throw an exception.
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;
}
4.1 respond to verification errors in the presentation layer
When BLL is called through the display layer, we will decide whether to directly handle the exception or directly throw the exception to ASP. NET. We can use try_catch block in BLL to handle exceptions. As follows:
Productsbll productlogic = new productsbll ();
// Update information for productid 1
Try
{
// This will fail since we are attempting to use
// Unitprice value less than 0.
Productlogic. updateproduct (
"Scott s tea", 1, 1, null,-14 m, 10, null, null, false, 1 );
}
Catch (argumentexception AE)
{
Response. Write ("there was a problem:" + AE. Message );
}
In subsequent content, when you use a web control for crud, you can use the event processor instead of the Code encapsulated by the try_catch block to process exceptions in BLL.
Ii. Summary
An application with an excellent architecture is usually divided into different layers and each layer is responsible for it. In the preceding section, we created a dal using a strongly Typed Dataset. In this article, we created a BLL to access the Dal by adding a class to the app_code folder. at the same time, BLL implements the field-level and business-Level Logic. In addition to creating an independent Bll, this article also used the partial class to extend the tableadapter method. However, this technology does not allow rewriting of existing methods, nor can it achieve good separation of dal and BLL. Next, we will complete the display layer. In the next article, we will perform simple data access and create a consistent style interface to serve the entire course.
Happy programming !!!