Main ASP .NET Tutorial Index 

Tutorial 2: Creating a Business Logic Layer

 

Scott Mitchell

June 2006

Download the ASPNET_Data_Tutorial_2_CS.exe sample code.

Contents of Tutorial 2 (Visual C#)

Introduction
Step 1: Creating the BLL Classes
Step 2: Accessing the Typed DataSets Through the BLL Classes
Step 3: Adding Field-Level Validation to the DataRow Classes
Step 4: Adding Custom Business Rules to the BLL's Classes
Summary

 

Step 1: Creating the BLL Classes

Our BLL will be composed of four classes, one for each TableAdapter in the DAL; each of these BLL classes will have methods for retrieving, inserting, updating, and deleting from the respective TableAdapter in the DAL, applying the appropriate business rules.

To more cleanly separate the DAL- and BLL-related classes, let's create two subfolders in the App_Code folder, DAL and BLL. Simply right-click on the App_Code folder in the Solution Explorer and choose New Folder. After creating these two folders, move the Typed DataSet created in the first tutorial into the DAL subfolder.

Next, create the four BLL class files in the BLL subfolder. To accomplish this, right-click on the BLL subfolder, choose Add a New Item, and choose the Class template. Name the four classes ProductsBLL, CategoriesBLL, SuppliersBLL, and EmployeesBLL.

Figure 2. Add Four New Classes to the App_Code Folder

Next, let's add methods to each of the classes to simply wrap the methods defined for the TableAdapters from the first tutorial. For now, these methods will just call directly into the DAL; we'll return later to add any needed business logic.

Note   If you are using Visual Studio Standard Edition or above (that is, you're not using Visual Web Developer), you can optionally design your classes visually using the Class Designer. Refer to the Class Designer Blog for more information on this new feature in Visual Studio.

For the ProductsBLL class we 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 from the specified category
  • GetProductsBySupplier(supplierID) – returns all products from the specified supplier
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) – inserts a new product into the database using the values passed-in; 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 using the passed-in values; returns true if precisely one row was updated, false otherwise
  • 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)
                _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;
    }
}

The methods that simply return data – GetProducts, GetProductByProductID, GetProductsByCategoryID, and GetProductBySuppliersID – are fairly straightforward as they simply call down into the DAL. While in some scenarios there may be business rules that need to be implemented at this level (such as authorization rules based on the currently logged on user or the role to which the user belongs), we'll simply leave these methods as-is. For these methods, then, the BLL serves merely as a proxy through which the presentation layer accesses the underlying data from the Data Access Layer.

The AddProduct and UpdateProduct methods both take in as parameters the values for the various product fields and add a new product or update an existing one, respectively. Since many of the Product table's columns can accept NULL values (CategoryID, SupplierID, and UnitPrice, to name a few), those input parameters for AddProduct and UpdateProduct that map to such columns use use nullable types. Nullable types are new to .NET 2.0 and provide a technique for indicating whether a value type should, instead, be null. In C# you can flag a value type as a nullable type by adding ? after the type (like int? x;). Refer to the Nullable Types section in the C# Programming Guide for more information.

All three methods return a Boolean value indicating whether a row was inserted, updated, or deleted since the operation may not result in an affected row. For example, if the page developer calls DeleteProduct passing in a ProductID for a non-existent product, the DELETE statement issued to the database will have no affect and therefore the DeleteProduct method will return false.

Note that when adding a new product or updating an existing one we take in the new or modified product's field values as a list of scalars as opposed to accepting a ProductsRow instance. This approach was chosen because the ProductsRow class derives from the ADO.NET DataRow class, which doesn't have a default parameterless constructor. In order to create a new ProductsRow instance, we must first create a ProductsDataTable instance and then invoke its NewProductRow() method (which we do in AddProduct). This shortcoming rears its head when we turn to inserting and updating products using the ObjectDataSource. In short, the ObjectDataSource will try to create an instance of the input parameters. If the BLL method expects a ProductsRow instance, the ObjectDataSource will try to create one, but fail due to the lack of a default parameterless constructor. For more information on this problem, refer to the following two ASP.NET Forums posts: Updating ObjectDataSources with Strongly-Typed DataSets and Problem With ObjectDataSource and Strongly-Typed DataSet.

Next, in both AddProduct and UpdateProduct, the code creates a ProductsRow instance and populates it with the values just passed in. When assigning values to DataColumns of a DataRow various field-level validation checks can occur. Therefore, manually putting the passed in values back into a DataRow helps ensure the validity of the data being passed to the BLL method. Unfortunately the strongly-typed DataRow classes generated by Visual Studio do not use nullable types. Rather, to indicate that a particular DataColumn in a DataRow should correspond to a NULL database value we must use the SetColumnNameNull() method.

In UpdateProduct we first load in the product to update using GetProductByProductID(productID). While this may seem like an unnecessary trip to the database, this extra trip will prove worthwhile in future tutorials that explore optimistic concurrency. Optimistic concurrency is a technique to ensure that two users who are simultaneously working on the same data don't accidentally overwrite one another's changes. Grabbing the entire record also makes it easier to create update methods in the BLL that only modify a subset of the DataRow's columns. When we explore the SuppliersBLL class we'll see such an example.

Finally, note that the ProductsBLL class has the DataObject attribute applied to it (the [System.ComponentModel.DataObject] syntax right before the class statement near the top of the file) and the methods have DataObjectMethodAttribute attributes. The DataObject attribute marks the class as being an object suitable for binding to an ObjectDataSource control, whereas the DataObjectMethodAttribute indicates the purpose of the method. As we'll see in future tutorials, ASP.NET 2.0's ObjectDataSource makes it easy to declaratively access data from a class. To help filter the list of possible classes to bind to in the ObjectDataSource's wizard, by default only those classes marked as DataObjects are shown in the wizard's drop-down list. The ProductsBLL class will work just as well without these attributes, but adding them makes it easier to work with in the ObjectDataSource's wizard.

Adding the Other Classes

With the ProductsBLL class complete, we still need to add the classes for working with categories, suppliers, and employees. Take a moment to create the following classes and methods using the concepts from 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)

The one method worth noting is the SuppliersBLL class's UpdateSupplierAddress method. This method provides an interface for updating just the supplier's address information. Internally, this method reads in the SupplierDataRow object for the specified supplierID (using GetSupplierBySupplierID), sets its address-related properties, and then calls down into the SupplierDataTable's Update method. The UpdateSupplierAddress method 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;
    }
}

Refer to this article's download for my complete implementation of the BLL classes.

 

 

 

 

Go to top of page or next part of tutorial

 
eXTReMe Tracker