Chapter 6: Separating Code from Presentation
Creating Multitiered Web Applications
You can use components to divide your Web application into multiple layers, thereby creating a multitiered application. For example, in a two-tiered application, you might have a user interface layer built from ASP.NET pages and a data layer built from components. In a three-tiered application, you might introduce a third layer, representing your application's business logic, between the user interface and data layers.
Why would you want to create multiple layers? The advantage of dividing an application into multiple layers is that you can more easily manage and change the application.
In the preceding section, for example, you created a component that enables you to save form information to a file. In essence, you created a single component for the data layer. Now suppose that your needs change, and you decide to save the data to a database table instead of a file. In that case, you need to modify only the component. You don't have to touch a line of code in the ASP.NET page itself. In other words, you can isolate changes in the data layer from changes in the user interface layer.
An additional benefit of dividing an application into multiple layers is that doing so makes it easier for multiple teams of programmers to work on the same application at the same time. If all the application logic is contained in a single page, you face the problem of multiple programmers stumbling over each other while performing modifications.
To illustrate these points, you can construct a simple three-tiered Web application consisting of user interface, business, and data layers to create a simple order entry application.
The application consists of the following three objects:
orderform.aspxThis ASP.NET page represents the user interface layer.
BizObjectThis component represents the business layer.
DataObjectThis component represents the data layer.
First, build the user interface layer. The ASP.NET page for the user interface is contained in Listing 6.8. The page requests five pieces of information: the customer name, product, unit price of the product, quantity, and customer's state of residence (see Figure 6.3).
Listing 6.8 orderform.aspx
<%@ Import Namespace="myComponents" %> <Script Runat="Server"> Sub PlaceOrder( s As Object, e As EventArgs ) Dim myBizObject As New BizObject If isValid Then Try myBizObject.CheckOrder( _ customer.Text, _ Product.SelectedItem.Text, _ unitPrice.Text.ToDouble, _ Quantity.Text.ToInt32, _ StateOfResidence.SelectedItem.Text ) Catch myEx As Exception errorLabel.Text = myEx.Message End Try End If End Sub </Script> <html> <head><title>orderform.aspx</title></head> <body> <form Runat="Server"> <h2>Enter an Order:</h2> <asp:Label ID="errorLabel" forecolor="Red" font-bold="True" MaintainState="False" Runat="Server" /> <p> Customer Name: <br> <asp:TextBox ID="customer" Runat="Server" /> <asp:RequiredFieldValidator ControlToValidate="customer" Text="You must enter a customer name!" Runat="Server" /> <p> Product: <br> <asp:ListBox ID="Product" Runat="Server"> <asp:ListItem Text="Hair Dryer" /> <asp:ListItem Text="Shaving Cream" /> <asp:ListItem Text="Electric Comb" /> </asp:ListBox> <asp:RequiredFieldValidator ControlToValidate="Product" Text="You must select a product!" Runat="Server" /> <p> Unit Price: <br> <asp:TextBox ID="unitPrice" Runat="Server" /> <asp:RequiredFieldValidator ControlToValidate="unitPrice" Text="You must enter a Unit Price!" Runat="Server" /> <p> Quantity: <br> <asp:TextBox ID="quantity" Runat="Server" /> <asp:RequiredFieldValidator ControlToValidate="quantity" Text="You must enter a quantity!" Runat="Server" /> <asp:CompareValidator ControlToValidate="quantity" Text="Quantity must be a number!" Operator="DataTypeCheck" Type="Integer" Runat="Server" /> <p> Customer State: <br> <asp:DropDownList ID="StateOfResidence" Runat="Server"> <asp:ListItem Text="California" /> <asp:ListItem Text="Nevada" /> <asp:ListItem Text="Washington" /> </asp:DropDownList> <p> <asp:Button Text="Place Order" OnClick="placeOrder" Runat="Server" /> </form> </body> </html>
When all the information is entered into the form, and the Place Order button is clicked, the placeOrder subroutine executes. This subroutine creates an instance of the BizObject component and passes all the form information to the component's CheckOrder() method.
The business component is contained in Listing 6.9.
Listing 6.9 BizObject.vb
Imports System Namespace myComponents Public Class bizObject Sub checkOrder( customer As String, _ product As String, _ unitPrice As Double, _ quantity As Integer, _ state As String ) If quantity <= 0 Or quantity > 100 Then Throw New ArgumentException( "Invalid Quantity!" ) End If If state = "California" And Product="Hair Dryer" Then Throw New ArgumentException( "Californians cannot own Hair Dryers!" ) End IF If state = "Washington" Then unitPrice += unitPrice * .06 End If Dim myDataObject As New DataObject myDataObject.SaveOrder( customer, product, unitPrice, quantity, state ) End Sub End Class End Namespace
The business component contains all the business rules for the application. The component encapsulates the following rules:
The product quantity must be greater than 0 and less than 100.
People who live in California cannot buy hair dryers.
People who live in Washington must pay an additional 6% sales tax.
If an order fails either of the first two requirements, an exception is raised. The error is caught in the orderentry.aspx page and displayed in a Label control. For example, Figure 6.4 displays what happens when you try to order more than 100 products.
Exceptions and Try..Catch blocks are discussed in detail in Chapter 18, "Application Tracing and Error Handling."
If an order satisfies all the business rules, it is passed to the SaveOrder() method of the DataObject component, which saves the order to disk. This component is contained in Listing 6.10.
Listing 6.10 DataObject.vb
Imports System Imports System.IO Namespace myComponents Public Class dataObject Sub saveOrder( customer As String, _ product As String, _ unitPrice As Double, _ quantity As Integer, _ state As String ) Dim myPath As String = "c:\orders.txt" Dim myFile As StreamWriter myFile = File.AppendText( myPath ) myFile.Write( "customer: " & customer & Environment.NewLine ) myFile.Write( "product: " & product & Environment.NewLine ) myFile.Write( "unit price: " & unitPrice.ToString() & Environment.NewLine ) myFile.Write( "quantity: " & quantity.ToString() & Environment.NewLine ) myFile.Write( "state: " & state & Environment.NewLine ) myFile.Write( "=============" & Environment.NewLine ) myFile.Close End Sub End Class End Namespace
Remember that you must compile both the BizObject and DataObject components and copy them to your application's /BIN directory before you can use them. The order of compilation is important. Because the BizObject component refers to the DataObject component, you must create the DataObject component first. You can compile this component by using the following statement (executed from a DOS prompt):
vbc /t:library DataObject.vb
After you copy the DataObject.dll file to your application's /BIN directory, you can compile the BizObject component by using the following statement:
vbc /t:library /r:DataObject.dll bizObject.vb
Notice the /r option used when compiling the BizObject component; it references the DataObject.dll file. If you don't include this reference, you receive the error User-defined type not defined: DataObject.