3. Enhancing the Component with Error Handling
One way you could enhance the
component is with better support for error reporting. As it is, any
database errors that occur are immediately returned to the calling code.
In some cases (for example, if there is a legitimate database problem),
this is a reasonable approach, because the component can't handle the
problem.
However, the component fails
to handle one common problem properly. This problem occurs if the
connection string isn't found in the web.config file. Though the
component tries to read the connection string as soon as it's created,
the calling code doesn't realize a problem exists until it tries to use a
database method.
A better approach is to notify the client as soon as the problem is detected, as shown in the following code example:
Public Class DBUtil
Private connectionString As String
Public Sub New()
If WebConfigurationManager.ConnectionStrings("AdBoard") _
Is Nothing Then
Throw New ApplicationException( _
"Missing ConnectionString variable in web.config.")
Else
connectionString = _
WebConfigurationManager.ConnectionStrings( _
"AdBoard").ConnectionString
End If
End Sub
' (Other class code omitted.)
End Class
This code
throws an ApplicationException with a custom error message that
indicates the problem. To provide even better reporting, you could
create your own exception class that inherits from ApplicationException.
Components often catch
the exceptions that occur during low-level tasks (such as reading a file
or interacting with a database) and then throw less detailed exceptions
like ApplicationException to notify the web page. That way, there's no
chance that the user will see the technical error information. This is
important, because detailed error messages can give hackers clues to how
your code works—and how to subvert it.
|
|
4. Enhancing the Component with Aggregate Information
The component doesn't
have to limit the type of information it provides to DataSets. Other
information is also useful. For example, you might provide a read-only
property called ItemFields that returns an array of strings representing
the names for fields in the Items table. Or you might add another
method that retrieves aggregate information about the entire table, such
as the average cost of items in the database or the total number of
currently listed items, as shown here:
Public Class DBUtil
' (Other class code omitted.)
Public Function GetAveragePrice() As Decimal
Dim query As String = "SELECT AVG(Price) FROM Items"
Dim con As New SqlConnection(connectionString)
Dim cmd As New SqlCommand(query, con)
con.Open()
Dim average As Decimal = CType(cmd.ExecuteScalar(), Decimal)
con.Close()
Return average
End Function
Public Function GetTotalItems() As Integer
Dim query As String = "SELECT COUNT(*) FROM Items"
Dim con As New SqlConnection(connectionString)
Dim cmd As New SqlCommand(query, con)
con.Open()
Dim count As Integer = CType(cmd.ExecuteScalar(), Integer)
con.Close()
Return count
End Function
End Class
These queries use some SQL that
may be new to you (namely, the COUNT and AVG aggregate functions).
However, these methods are just as easy to use from the client's
perspective as GetItems() and GetCategories():
Dim DB As New DBUtil()
Dim averagePrice As Decimal = DB.GetAveragePrice()
Dim totalItems As Integer = DB.GetTotalItems()
It may have occurred to you
that you can return information such as the total number of items
through a read-only property procedure (such as TotalItems) instead of a
method (in this case, GetTotalItems). Though this does work, property
procedures are better left to information that is maintained with the
class (in a private variable) or is easy to reconstruct. In this case,
it takes a database operation to count the number of rows, and this
database operation can cause an unusual problem or slow down performance
if used frequently. To help reinforce that fact, a method is used
instead of a property.