Architectural Considerations


If you embark on a large ASP.NET project, you'll quickly discover that you spend more time writing code for components than writing code for your pages. This is not a bad thing. Placing as much of your application logic as possible in components makes it easier to maintain and extend your application.

However, the process of organizing the components itself can become time consuming. In other words, you start to run into architectural issues concerning the best way to design your web application.

The topic of architecture, like the topics of politics and religion, should not be discussed in polite company. People have passionate opinions about architecture and discussions on this topic quickly devolve into people throwing things. Be aware that any and all statements about proper architecture are controversial.

With these disclaimers out of the way, in this section I provide you with an overview of one of the most common architectures for ASP.NET applications. In this section, you learn how to build a three-tiered ASP.NET application.

Building Multi-tier Applications

One very common architecture for an application follows an n-tier design model. When using an n-tier architecture, you encapsulate your application logic into separate layers.

In particular, it is recommended that an application should be divided into the following three application layers:

  • User Interface Layer

  • Business Logic Layer

  • Data Access Layer

The idea is that the User Interface layer should contain nothing but user interface elements such as HTML and ASP.NET controls. The User Interface layer should not contain any business logic or data access code.

The Business Logic layer contains all your business rules and validation code. It manages all data access for the User Interface Layer.

Finally, the Data Access Layer contains all the code for interacting with a database. For example, all the code for interacting with Microsoft SQL Server should be encapsulated in this layer.

The advantage of encapsulating your application logic into different layers is that it makes it easier to modify your application without requiring you to rewrite your entire application. Changes in one layer can be completely isolated from the other layers.

For example, imagine that (one fine day) your company decides to switch from using Microsoft SQL Server to using Oracle as their database server. If you have been careful to create an isolated Data Access Layer, then you would need to rewrite only your Data Access Layer. It might be a major project, but you would not need to start from scratch.

Or, imagine that your company decides to create a Windows Forms version of an existing ASP.NET application. Again, if you have been careful to isolate your User Interface Layer from your Business Logic Layer, then you can extend your application to support a Windows Forms Interface without rewriting your entire application. The Windows Forms application can use your existing Business Logic and Data Access layers.

Note

I spend my working life training companies on implementing ASP.NET applications. Typically, a company is migrating a web application written in some other language such as Java or ASP Classic to the ASP.NET Framework. It always breaks my heart to see how much code is wasted in these transitions (thousands of man hours of work lost). If you are careful in the way that you design your ASP.NET application now, you can avoid this sorry fate in the future.


I realize that this is all very abstract, so let's examine a particular sample. We'll create a simple product management system that enables you to select, insert, update, and delete products. However, we'll do it the right way by dividing the application into distinct User Interface, Business Logic, and Data Access layers.

Creating the User Interface Layer

The User Interface layer is contained in Listing 14.25. Notice that the User Interface layer consists of a single ASP.NET page. This page contains no code whatsoever.

Listing 14.25. Products.aspx

<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <style type="text/css">     html     {         background-color:silver;     }     .content     {         padding:10px;         background-color:white;     }     .products     {         margin-bottom:20px;     }     .products td,.products th     {         padding:5px;         border-bottom:solid 1px blue;     }     a     {         color:blue;     }     </style>     <title>Products</title> </head> <body>     <form  runat="server">     <div >     <asp:GridView                  DataSource         DataKeyNames="Id"         AutoGenerateEditButton="true"         AutoGenerateDeleteButton="true"         AutoGenerateColumns="false"         Css         GridLines="none"         Runat="server">         <Columns>         <asp:BoundField             DataField="Id"             ReadOnly="true"             HeaderText="Id" />         <asp:BoundField             DataField="Name"             HeaderText="Name" />         <asp:BoundField             DataField="Price"             DataFormatString="{0:c}"             HeaderText="Price" />         <asp:BoundField             DataField="Description"             HeaderText="Description" />         </Columns>     </asp:GridView>     <fieldset>     <legend>Add Product</legend>     <asp:DetailsView                  DataSource         DefaultMode="Insert"         AutoGenerateInsertButton="true"         AutoGenerateRows="false"         Runat="server">         <Fields>         <asp:BoundField             DataField="Name"             HeaderText="Name:" />         <asp:BoundField             DataField="Price"             HeaderText="Price:"/>         <asp:BoundField             DataField="Description"             HeaderText="Description:" />         </Fields>     </asp:DetailsView>     </fieldset>     <asp:ObjectDataSource                  TypeName="AcmeStore.BusinessLogicLayer.Product"         SelectMethod="SelectAll"         UpdateMethod="Update"         InsertMethod="Insert"         DeleteMethod="Delete"         Runat="server" />     </div>     </form> </body> </html>

The page in Listing 14.25 contains a GridView, DetailsView, and ObjectDataSource control. The GridView control enables you to view, update, and delete the products contained in the Products database table (see Figure 14.13). The DetailsView enables you to add new products to the database. Both controls use the ObjectDataSource as their data source.

Figure 14.13. The Products.aspx page.


Note

The next chapter is entirely devoted to the ObjectDataSource control.


The page in Listing 14.25 does not interact with a database directly. Instead, the ObjectDataSource control is used to bind the GridView and DetailsView controls to a component named AcmeStore.BusinessLogicLayer.Product. The Product component is contained in the Business Logic layer.

Note

The page in Listing 14.25 does not contain any validation controls. I omitted adding validation controls for reasons of space. In a real application, you would want to toss some RequiredFieldValidator and CompareValidator controls into the page.


Creating the Business Logic Layer

The ASP.NET pages in your application should contain a minimum amount of code. All your application logic should be pushed into separate components contained in either the Business Logic or Data Access layers.

Your ASP.NET pages should not communicate directly with the Data Access layer. Instead, the pages should call the methods contained in the Business Logic layer.

The Business Logic layer consists of a single component named Product, which is contained in Listing 14.26. (A real-world application might contain dozens or even hundreds of components in its Business Logic layer.)

Listing 14.26. BLL/Product.vb

[View full width]

Imports System Imports System.Collections.Generic Imports AcmeStore.DataAccessLayer Namespace AcmeStore.BusinessLogicLayer     ''' <summary>     ''' Represents a store product and all the methods     ''' for selecting, inserting, and updating a product     ''' </summary>     Public Class Product         Private _id As Integer = 0         Private _name As String = String.Empty         Private _price As Decimal = 0         Private _description As String = String.Empty         ''' <summary>         ''' Product Unique Identifier         ''' </summary>         Public ReadOnly Property Id() As Integer             Get                 Return _id             End Get         End Property         ''' <summary>         ''' Product Name         ''' </summary>         Public ReadOnly Property Name() As String             Get                 Return _name             End Get         End Property         ''' <summary>         ''' Product Price         ''' </summary>         Public ReadOnly Property Price() As Decimal             Get                 Return _price             End Get         End Property         ''' <summary>         ''' Product Description         ''' </summary>         Public ReadOnly Property Description() As String             Get                 Return _description             End Get         End Property         ''' <summary>         ''' Retrieves all products         ''' </summary>         Public Shared Function SelectAll() As List(Of Product)             Dim dataAccessLayer As SqlDataAccessLayer = New SqlDataAccessLayer()             Return dataAccessLayer.ProductSelectAll()         End Function         ''' <summary>         ''' Updates a particular product         ''' </summary>         ''' <param name="id">Product Id</param>         ''' <param name="name">Product Name</param>         ''' <param name="price">Product Price</param>         ''' <param name="description">Product Description</param>         Public Shared Sub Update(ByVal id As Integer, ByVal name As String, ByVal price As  Decimal, ByVal description As String)             If id < 1 Then                 Throw New ArgumentException("Product Id must be greater than 0", "id")             End If             Dim productToUpdate As Product = New Product(id, name, price, description)             productToUpdate.Save()         End Sub         ''' <summary>         ''' Inserts a new product         ''' </summary>         ''' <param name="name">Product Name</param>         ''' <param name="price">Product Price</param>         ''' <param name="description">Product Description</param>         Public Shared Sub Insert(ByVal name As String, ByVal price As Decimal, ByVal  description As String)             Dim NewProduct As Product = New Product(name, price, description)             NewProduct.Save()         End Sub         ''' <summary>         ''' Deletes an existing product         ''' </summary>         ''' <param name="id">Product Id</param>         Public Shared Sub Delete(ByVal id As Integer)             If id < 1 Then                 Throw New ArgumentException("Product Id must be greater than 0", "id")             End If             Dim dataAccessLayer As SqlDataAccessLayer = New SqlDataAccessLayer()             dataAccessLayer.ProductDelete(id)         End Sub         ''' <summary>         ''' Validates product information before saving product         ''' properties to the database         ''' </summary>         Private Sub Save()             If String.IsNullOrEmpty(_name) Then                 Throw New ArgumentException("Product Name not supplied", "name")             End If             If _name.Length > 50 Then                 Throw New ArgumentException("Product Name must be less than 50 characters" , "name")             End If             If String.IsNullOrEmpty(_description) Then                 Throw New ArgumentException("Product Description not supplied", "description")             End If             Dim dataAccessLayer As SqlDataAccessLayer = New SqlDataAccessLayer()             If _id > 0 Then                 dataAccessLayer.ProductUpdate(Me)             Else                 dataAccessLayer.ProductInsert(Me)             End If         End Sub         ''' <summary>         ''' Initializes Product         ''' </summary>         ''' <param name="name">Product Name</param>         ''' <param name="price">Product Price</param>         ''' <param name="description">Product Description</param>         Public Sub New(ByVal name As String, ByVal price As Decimal, ByVal description As  String)             Me.New(0, name, price, description)         End Sub         ''' <summary>         ''' Initializes Product         ''' </summary>         ''' <param name="id">Product Id</param>         ''' <param name="name">Product Name</param>         ''' <param name="price">Product Price</param>         ''' <param name="description">Product Description</param>         Public Sub New(ByVal id As Integer, ByVal name As String, ByVal price As Decimal,  ByVal description As String)             _id = id             _name = name             _price = price             _description = description         End Sub     End Class End namespace

The Product component contains four public methods named SelectAll(), Update(), Insert(), and Delete(). All four of these methods use the SqlDataAccessLayer component to interact with the Products database table. The SqlDataAccessLayer is contained in the Data Access Layer.

For example, the SelectAll() method returns a collection of Product objects. This collection is retrieved from the SqlDataAccessLayer component.

The Insert(), Update(), and Delete() methods validate their parameters before passing the parameters to the Data Access layer. For example, when you call the Insert() method, the length of the Name parameter is checked to verify that it is less than 50 characters.

Notice that the Business Logic layer does not contain any data access logic. All this logic is contained in the Data Access layer.

Creating the Data Access Layer

The Data Access layer contains all the specialized code for interacting with a database. The Data Access layer consists of the single component in Listing 14.27. (A real-world application might contain dozens or even hundreds of components in its Data Access Layer.)

Listing 14.27. SqlDataAccessLayer.vb

[View full width]

Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Web.Configuration Imports System.Collections.Generic Imports AcmeStore.BusinessLogicLayer Namespace AcmeStore.DataAccessLayer     ''' <summary>     ''' Data Access Layer for interacting with Microsoft     ''' SQL Server 2005     ''' </summary>     Public Class SqlDataAccessLayer         Private Shared ReadOnly _connectionString As String = String.Empty         ''' <summary>         ''' Selects all products from the database         ''' </summary>         Public Function ProductSelectAll() As List(Of Product)             ' Create Product collection             Dim colProducts As New List(Of Product)()             ' Create connection             Dim con As SqlConnection = New SqlConnection(_connectionString)             ' Create command             Dim cmd As SqlCommand = New SqlCommand()             cmd.Connection = con             cmd.CommandText = "SELECT Id,Name,Price,Description FROM Products"             ' Execute command             Using con                 con.Open()                 Dim reader As SqlDataReader = cmd.ExecuteReader()                 While reader.Read()                     colProducts.Add(New Product( _                         CType(reader("Id"), Integer), _                         CType(reader("Name"), String), _                         CType(reader("Price"), Decimal),_                         CType(reader("Description"), String)))                 End While             End Using             Return colProducts         End Function         ''' <summary>         ''' Inserts a new product into the database         ''' </summary>         ''' <param name="newProduct">Product</param>         Public Sub ProductInsert(ByVal NewProduct As Product)             ' Create connection             Dim con As SqlConnection = New SqlConnection(_connectionString)             ' Create command             Dim cmd As SqlCommand = New SqlCommand()             cmd.Connection = con             cmd.CommandText = "INSERT Products (Name,Price,Description) VALUES (@Name, @Price,@Description)"             ' Add parameters             cmd.Parameters.AddWithValue("@Name", NewProduct.Name)             cmd.Parameters.AddWithValue("@Price", NewProduct.Price)             cmd.Parameters.AddWithValue("@Description", NewProduct.Description)             ' Execute command             Using con                 con.Open()                 cmd.ExecuteNonQuery()             End Using         End Sub         ''' <summary>         ''' Updates an existing product into the database         ''' </summary>         ''' <param name="productToUpdate">Product</param>         Public Sub ProductUpdate(ByVal productToUpdate As Product)             ' Create connection             Dim con As SqlConnection = New SqlConnection(_connectionString)             ' Create command             Dim cmd As SqlCommand = New SqlCommand()             cmd.Connection = con             cmd.CommandText = "UPDATE Products SET Name=@Name,Price=@Price ,Description=@Description WHERE Id=@Id"             ' Add parameters             cmd.Parameters.AddWithValue("@Name", productToUpdate.Name)             cmd.Parameters.AddWithValue("@Price", productToUpdate.Price)             cmd.Parameters.AddWithValue("@Description", productToUpdate.Description)             cmd.Parameters.AddWithValue("@Id", productToUpdate.Id)             ' Execute command             Using con                 con.Open()                 cmd.ExecuteNonQuery()             End Using         End Sub         ''' <summary>         ''' Deletes an existing product in the database         ''' </summary>         ''' <param name="id">Product Id</param>         Public Sub ProductDelete(ByVal Id As Integer)             ' Create connection             Dim con As SqlConnection = New SqlConnection(_connectionString)             ' Create command             Dim cmd As SqlCommand = New SqlCommand()             cmd.Connection = con             cmd.CommandText = "DELETE Products WHERE Id=@Id"             ' Add parameters             cmd.Parameters.AddWithValue("@Id", Id)             ' Execute command             Using con                 con.Open()                 cmd.ExecuteNonQuery()             End Using         End Sub         ''' <summary>         ''' Initialize the data access layer by         ''' loading the database connection string from         ''' the Web.Config file         ''' </summary>         Shared Sub New()             _connectionString = WebConfigurationManager.ConnectionStrings("Store") .ConnectionString             If String.IsNullOrEmpty(_connectionString) Then                 Throw New Exception("No connection String configured in Web.Config file")             End If         End Sub     End Class End Namespace

The SqlDataAccessLayer component in Listing 14.27 grabs the database connection string that it uses when communicating with Microsoft SQL Server in its constructor. The connection string is assigned to a private field so that it can be used by all the component's methods.

The SqlDataAccessLayer component has four public methods: ProductSelectAll(), ProductInsert(), ProductUpdate(), and ProductDelete(). These methods use the ADO.NET classes from the System.Data.SqlClient namespace to communicate with Microsoft SQL Server.

Note

We discuss ADO.NET in Chapter 16, "Building Data Access Components."


Notice that the SqlDataAccessLayer component is not completely isolated from the components in the Business Logic Layer. The ProductSelectAll() method builds a collection of Product objects, which the method returns to the Business Logic layer. You should strive to isolate each layer as much as possible. However, in some cases, you cannot completely avoid mixing objects from different layers.




ASP. NET 2.0 Unleashed
ASP.NET 2.0 Unleashed
ISBN: 0672328232
EAN: 2147483647
Year: 2006
Pages: 276

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net