https://msdn.microsoft.com/zh-cn/dd255899
Brief introduction
The data Access layer (DAL) created in tutorial one clearly separates the data access logic from the presentation logic. However, although the DAL clearly separates the details of the data access layer from the presentation layer, it does not implement any business rules that might be adopted. For example, we want our application to disable modification of the CategoryID or SupplierID fields of the Products table in the Discontinued field to 1 o'clock, and We may wish to implement some qualifications rules to prohibit the occurrence of an employee being managed by another employee who is subsequently hired. Another common scenario is authorization – it is possible that only users who are in a specific position can delete products or change UnitPrice values.
In this tutorial, we can learn how to centralize business rules into the business Logic layer (BLL) that acts as a mediator between the presentation layer and the DAL. In real-world applications, the BLL should be implemented as a separate class library project. However, in order to simplify the project structure, in these tutorials we implement the BLL in a series of classes under the App_Code folder. Figure 1 shows the structural relationship between the presentation layer, the BLL, and the DAL.
Figure 1: The BLL separates the presentation layer from the data access layer and enforces business rules.
Step 1: Create the BLL class
Our BLL will consist of four classes, each corresponding to the different TableAdapter in the DAL. Each BLL class has methods that retrieve, insert, update, or delete data from the TableAdapter of the class in the DAL and apply the appropriate business rules.
In order to distinguish the related classes of the DAL more clearly from the related classes of the BLL, we create two subfolders under the App_Code folder: DAL and BLL. When created, simply right-click the App_Code folder in the Solution Explorer and select New folder. After you create both folders, move the Typed DataSet that you created in tutorial one to the DAL subfolder.
Then, create four BLL class files in the BLL subfolder. To do this, right-click the BLL subfolder, select Add a New Item, and then select the Class template. The four classes are named Productsbll, CATEGORIESBLL, SUPPLIERSBLL, and EMPLOYEESBLL respectively.
Figure 2: add four new classes in the App_Code folder
Let's then add some methods to each class that simply encapsulate the method defined for TableAdapters in tutorial one. Currently, these methods are just a direct call to the content in the DAL, and we will return to these methods later to add any required business logic.
Note: If you are currently using Visual Studio standard Edition or later (that is, you are not currently using visual Web Developer), you can use the class Designer to freely set it in a visual way To count their own classes. For more information about this new feature in Visual Studio, see class Designer Blog.
For the PRODUCTSBLL class, you need to add a total of seven methods:
- GetProducts () – Returns all products.
- Getproductbyproductid (ProductID) – returns the product with the specified product ID.
- Getproductsbycategoryid (CategoryID) – returns all products in the specified category.
- Getproductsbysupplier (SupplierID) – returns all products from the specified vendor.
- Addproduct (productName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, discontinued) – Inserts a new product into the database by passing in the value, and returns the ProductID value of the newly inserted record.
- UpdateProduct (productName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, discontinued, ProductID) – Updates an existing product in the database by passing in the value, or False if a row is updated exactly.
- Deleteproduct (ProductID) – Deletes the specified product from the database.
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) _products Adapter = 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.getproductsbycateg Oryid (CategoryID); } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)] Public northwind.productsdatatable getproductsbysupplierid (int supplierID) {return ADAPTER.GETPRODUCTSBYSUPPL Ierid (SupplierID); } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Insert, True)] public bool Addproduct (string productName, int? supplierID, int? CategoryID, string quantityperunit, decimal unit Price, 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? u Nitprice, short? UnitsInStock, short? UnitsOnOrder, short? ReorderLevel, BOOL discontinued, int ProductID) {Northwind.productsdatatable products = Adapter.getproductbypro Ductid (ProductID); if (products. Count = = 0)//No matching record found, return false return; 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-getproducts, Getproductbyproductid, Getproductsbycategoryid, and Getproductbysuppliersid, just return the data, they are fairly straightforward and simple, Because they're just lowering the content in the DAL. In some cases, there may be some business rules that need to be implemented at this level (such as access to different data based on the authorization rules of the currently logged-on user or the user's position), but here we just keep these methods intact. Therefore, for these methods, the BLL acts as a proxy, representing the layer through which the proxy accesses the underlying data in the data access layer.
The Addproduct and UpdateProduct methods pass the values of each field in the product as parameters, and they are: add a new product and update an existing one. Because many columns of the Product table, such as CategoryID, SupplierID, and UnitPrice, can accept NULL values, the input parameters corresponding to such columns in addproduct and updateproduct use the Nullable class Type. The Nullable type is a new type for. NET 2.0来, and with the technology provided by that type, we can indicate whether a value type can be an empty type. In C #, you can add a question mark after the type? Mark a value type as a nullable type (for example, int?). X )。 For more information, see the section Nullable types in the C # Programming Guide.
All three methods return a Boolean value that indicates whether a row was successfully inserted, updated, or deleted. This value is returned because the operation of the method does not necessarily affect one row. For example, if the page developer calls Deleteproduct when the incoming ProductID is not an ID for an existing product, the DELETE statement to the database has no effect, so the Deleteproduct method returns false.
Note that when you add a new product or update an existing product, we pass in a set of values to the field value of the new or changed product instead of accepting an Productsrow instance for this purpose. The reason for this choice is that the Productsrow class is derived from the ADO. DataRow class, which does not have a default parameterless constructor. To create a new Productsrow instance, first create a productsdatatable instance and then call its Newproductrow () method (as we did in the Addproduct method). When we use ObjectDataSource to insert or update new products, their flaws are exposed. In short, ObjectDataSource will attempt to create an instance of the input parameters. If the BLL method expects a Productsrow instance, ObjectDataSource attempts to create one, but the attempt fails because the default parameterless constructor is missing. For more information about this issue, see the following two asp: Update Objectdatasources, ObjectDataSource, and strongly typed datasets with a strongly typed DataSet.
In addition, the code in both Addproduct and UpdateProduct creates an Productsrow instance and assigns the instance a value that was just passed in. Various field-level validation checks can occur when assigning values to some DataColumn of a DataRow. Therefore, a manual validation of the incoming values helps ensure the validity of the data passed to the BLL method. Unfortunately, the strongly typed DataRow class generated by Visual Studio does not use the nullable type. In order to assign a database null value to a specific DataColumn in a DataRow, we must use the SetColumnNameNull () method.
In UpdateProduct, we first load the product to be updated with Getproductbyproductid (ProductID). Although this appears to be an unnecessary operation on the database, this round trip will prove worthwhile in future tutorials that introduce concurrency optimizations. Concurrency optimization technology ensures that two of users who work simultaneously on the same data do not inadvertently overwrite changes made to each other. Getting the entire record also makes it easy: Create an Update method in the BLL so that the method modifies only a subset of all the columns of the DataRow. When we look at the Suppliersbll class, we see an example of this.
Finally, note that the DataObject attribute is used for the Productsbll class (near the beginning of the file, the [System.ComponentModel.DataObject] label in front of the class declaration statement), and its methods are Dataobjectmethoda The Ttribute property. The DataObject property marks the class as a suitable object to bind to the ObjectDataSource control, and the Dataobjectmethodattribute property indicates the purpose of the method. As you can see in a future tutorial, the ObjectDataSource of ASP. NET 2.0 makes it easy to access data in a declarative way from a class. By default, only those classes marked as DataObject are displayed in the drop-down list of the ObjectDataSource Wizard, which helps filter out those classes that can be bound in the wizard. The PRODUCTSBLL class does not work well without these attributes, but adding these attributes makes it easier to work under the ObjectDataSource Wizard.
Add other Classes
After we have finished writing the PRODUCTSBLL class, we need to add some classes that deal with categories, vendors, and employee data. Let's take some time to create the following classes and methods using the concepts in the example above:
- CategoriesBLL.cs
- GetCategories ()
- Getcategorybycategoryid (CategoryID)
- SuppliersBLL.cs
- Getsuppliers ()
- Getsupplierbysupplierid (SupplierID)
- Getsuppliersbycountry (Country)
- Updatesupplieraddress (SupplierID, Address, city, country)
- EmployeesBLL.cs
- GetEmployees ()
- Getemployeebyemployeeid (EmployeeID)
- Getemployeesbymanager (ManagerID)
One notable method is the Updatesupplieraddress method of the Suppliersbll class. This method provides an interface to update only the vendor's address information. On an internal implementation, the method reads the Supplierdatarow object of the specified SupplierID (read with Getsupplierbysupplierid), sets its associated address property, and then uses the supplierdatatable method of updating. The Updatesupplieraddress method is as follows:
[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update, True)] public bool Updatesupplieraddress (int SupplierID, string address, String city, String country) {Northwind.suppliers DataTable suppliers = Adapter.getsupplierbysupplierid (SupplierID); if (suppliers. Count = = 0)//No matching record found, return false return; 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 Typed DataSets through the BLL class
In tutorial one we see a programming example that uses the Typed DataSet directly. Now that we have added some BLL classes, the presentation layer should instead work on the BLL. The allproducts.aspx Example of tutorial one uses productstableadapter to bind the product list to a GridView, as shown in the following code:
ProductsTableAdapter productsAdapter = new ProductsTableAdapter();GridView1.DataSource = productsAdapter.GetProducts();GridView1.DataBind();
To use the BLL class, simply change the first line of code – simply replace the productstableadapter object with the productbll object:
ProductsBLL productLogic = new ProductsBLL();GridView1.DataSource = productLogic.GetProducts();GridView1.DataBind();
You can also use ObjectDataSource to access the BLL class declaratively (like the Typed DataSet). We'll discuss ObjectDataSource in more detail in the next tutorial.
Figure 3: The Product list is displayed in the GridView
Step 3: Add field-level validation to the DataRow class
Field-level validation is a check for the property values of a business object when an INSERT or update operation occurs. Here are some field-level validation rules for the product:
- The ProductName field cannot exceed 40 characters in length.
- The QuantityPerUnit field cannot exceed 20 characters in length.
- The ProductID, ProductName, and discontinued fields are required, but all other fields are optional.
- The UnitPrice, UnitsInStock, UnitsOnOrder, and ReorderLevel fields must be greater than or equal to zero.
These rules can and should be expressed at the database level. The data type of the corresponding column of the Products table reflects the limit on the number of characters for the ProductName and QuantityPerUnit fields (nvarchar (40) and nvarchar (20) respectively). The expression that is optional or required for a field is this: The database table column allows or does not allow null. The existence of four check constraints ensures that only values greater than or equal to zero can be assigned to the UnitPrice, UnitsInStock, UnitsOnOrder, and ReorderLevel columns.
These rules should be implemented at the dataset level, in addition to being implemented at the database level. In fact, the length of the field and whether a value is required or optional is defined by the DataColumn set of the DataTable. To view existing automated field-level validation, go to the DataSet Designer, select a domain from one of the DataTable, and go to the Properties window. As shown in 4, the maximum length allowed for QuantityPerUnit DataColumn in Productsdatatable is 20 characters and a NULL value is allowed. If we try to set the QuantityPerUnit property of Productsdatarow to a string value that is longer than 20 characters, the system throws a ArgumentException exception.
Figure 4: DataColumn provides basic domain level validation
Unfortunately, we cannot specify bounds checking through the Properties window, for example, UnitPrice must be greater than or equal to zero for such checks. To provide this type of field-level validation, you need to create an event Handler for columnchanging events for the DataTable. As described in the previous tutorial, the dataset, DataTables, and DataRow objects created by the Typed dataset can be extended by using partial classes. With this technique we can create an event Handler for the Productsdatatable class for the columnchanging events. First, create a class named ProductsDataTable.ColumnChanging.cs under the App_Code folder.
Figure 5: Adding a new class in the App_Code folder
Second, create an event handler for the ColumnChanging event to ensure that the values of the UnitPrice, UnitsInStock, UnitsOnOrder, and ReorderLevel columns (if not NULL) are greater than or equal to zero. Any one of these columns is out of range and the system will give 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.equal S (this. Unitpricecolumn)) {if (! Convert.isdbnull (e.ProposedValue) && (decimal) e.ProposedValue < 0) { throw new ArgumentException ("UnitPrice cannot is 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 is less than zero", e.column.columnname), E.column.colum Nname); } } } }}
Step 4: Add custom Business rules to the BLL class
In addition to field-level validation, there may be advanced custom business rules that involve different entities or that involve concepts that cannot be expressed in a single column, such as:
- If a product is a discontinued product, its UnitPrice cannot be updated.
- The country of residence of the employee must be the same as the country where the manager resides.
- If a product is the only product provided by its supplier, the product cannot be a broken product.
The BLL class should contain checks to ensure compliance with the application's business rules. You can add these checks directly to the method that they are applied to.
Suppose our business rules stipulate that if a product is the sole product of a specified vendor, the product cannot be marked as discontinued. That is, if product x is the only product we have purchased from supplier y , we cannot mark x as discontinued, but if supplier y provides us with three products:A ,B and C , we can mark any one or all of them as discontinued. This is a strange business rule, but business rules don't always conform to common sense!
In order to implement this business rule for the Updateproducts method, we first check whether discontinued is set to true, if so, we will call Getproductsbysupplierid to determine how many products we have purchased from the vendor of the product. If you purchase only one product from this vendor, we throw a ApplicationException exception.
public bool UpdateProduct (string productName, int? supplierID, int? CategoryID, string QuantityPerUnit, decimal? unitpr Ice, 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; Northwind.productsrow product = Products[0]; Business Rule Check-cannot discontinue//a product, is supplied by only//one supplier if (discontinue d) {//Get the products we buy from this supplier northwind.productsdatatable Productsbysupplier = Adapter.getproductsbysupplierid (product. SupplierID); if (Productsbysupplier.count = = 1)//This was the only product we buy from this supplier throw new A Pplicationexception ("You cannot mark a product as discontinued if it's 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;}
Responding to validation errors at the presentation layer
When you call the BLL from the presentation layer, we can decide whether to try to process any exceptions that might occur, or to throw those exceptions directly to ASP. NET (they raise HttpApplication error events). To handle an exception when programming with the BLL, we can use the Try...catch block as follows:
ProductsBLL productLogic = new ProductsBLL();// Update information for ProductID 1try{ // 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);}
In a later tutorial we will see that when using a Web Data control to insert, update, or delete data, an event handler can be used to process exceptions thrown from the BLL without encapsulating the processing code in the Try...catch block.
Summary
A well-structured application has a clear hierarchy, with each layer encapsulating specific tasks. In the first tutorial in this series, we created a data access layer with the typed dataset, and in this tutorial we built a business logic layer that includes a series of classes in our application's App_Code folder that invoke the contents of the Dal down. Our application implements field-level and business-level logic through the BLL. In this tutorial, we created a separate BLL, but another option is to use partial classes to extend the TableAdapters approach. However, using this technique, we cannot rewrite the existing methods, nor can we separate our Dal and BLL as clearly as we used in this article.
Once the DAL and BLL code is written, we can start writing our presentation layer code. In the next tutorial, we will briefly deviate from the data Access topic and instead define a consistent page layout that will be used for all tutorials.
Happy programming!
Microsoft-Create a business logic layer