ASP.NET 2.0 Instant Results
.pdfThe Bug Base
web project. This allows for even further abstraction of the code and promotes reuse. However, for many applications this new code folder is all you need to create well-designed applications.
The remainder of this section discusses the business layer and the data access layer. The presentation layer is discussed in the section “Code and Code Explanation.”
The Business Layer
The business layer of the Bug Base is located in the BusinessLogic folder inside the App_Code folder in the root of the application. It consists of eight classes and one enumeration, each of which is discussed in detail in the next section.
Bug
The Bug class, shown in Figure 12-7 and located in Bug.vb in the BusinessLogic folder, is the main entity in the application. It represents the bug that is filed through the web interface and stored in the database.
Figure 12-7
The Bug class exposes only properties; it has no behavior in terms of methods, other than two constructors. All actions you can perform on a bug, such as filing, changing, and searching lists of bugs, are carried out by the BugManager class.
The Bug class has the following properties:
Property |
Type |
Description |
|
|
|
Application |
NameValue |
A NameValue object holding the Id and the |
|
|
Description for the application the bug is filed |
|
|
against. |
CreatedDateAndTime |
DateTime |
The date and time the bug was filed. |
|
|
|
397
Chapter 12
Property |
Type |
Description |
|
|
|
CreateMemberId |
Guid |
The unique ID of the user who filed the bug. |
Title |
String |
A short description of the bug, used to quickly |
|
|
identify bugs. |
Description |
String |
The full description of the bug, possibly including |
|
|
a detailed set of instructions to reproduce the |
|
|
behavior and describe the problem. |
Feature |
NameValue |
A NameValue object holding the Id and the |
|
|
Description for the feature of the application the |
|
|
bug is filed against. |
Frequency |
NameValue |
The Frequency describes how often a bug occurs |
|
|
and how many users are likely to run into it. |
Id |
Integer |
Each bug is represented by a unique ID. The ID is |
|
|
automatically generated by the Bug table in the |
|
|
database whenever a new bug is inserted. |
Priority |
Integer |
The Priority of a bug often determines the order |
|
|
in which bugs should be fixed. |
Reproducibility |
NameValue |
The Reproducibility describes if and how often |
|
|
the bug can be reproduced. |
Severity |
NameValue |
The Severity describes the impact of a bug, |
|
|
ranging from usability issues to loss of data and |
|
|
application crashes. |
Status |
NameValue |
The Status indicates the current state of the bug. |
|
|
A status in turn can determine if the bug should |
|
|
be treated as closed. Refer to the Status table in the |
|
|
database for a full list of all the Status items. |
UpdatedDateAndTime |
DateTime |
The date and time the bug was last updated. |
UpdateMemberId |
Guid |
The unique ID of the user who last updated the bug. |
|
|
|
The Bug class also has two constructors:
Property |
Description |
|
|
New () |
Creates a new Bug object with all properties set to their |
|
default values. |
New (ByVal id As Integer) |
Creates a new Bug object with most properties set to their |
|
default values. The id that is passed to the constructor is set |
|
as the Id of the bug. |
Because the Bug class has only properties, it cannot perform any actions, such as saving itself in the database. Instead, these actions are carried out by the BugManager class.
398
The Bug Base
BugManager
The BugManager class (see Figure 12-8) is responsible for all actions on bugs. It has methods to insert new and change existing bugs and to retrieve lists of bugs that match specific search criteria. The BugManager class exposes two read-only properties called Count and MemberId. The Count property returns the number of bugs currently held by the BugManager in the private field _theBugList. The MemberId property contains the current member’s ID and is used to check access rights in the business and data access layers.
Figure 12-8
The BugManager class also has the following methods:
Method |
Return Type |
Description |
|
|
|
InsertUpdateBug |
Integer |
Saves a fully populated Bug object. It does this by |
(ByVal theBug As Bug) |
|
calling InsertUpdateBug on the BugManagerDB |
|
|
class and passing it the instance of the bug. The |
|
|
Integer returned from this method is the new or |
|
|
current ID of the bug in the database. |
GetBug (ByVal |
Bug |
Retrieves a bug based on the ID passed to this |
id As Integer) |
|
method. Returns Nothing when the bug could not |
|
|
be found or the user doesn’t have permission to |
|
|
view it. |
GetBugList |
List(Of Bug) |
Retrieves a list of bugs optionally based on search |
(+ one additional |
|
criteria and sorted on one of the bug’s properties. |
overload) |
|
The list that is returned is actually a strongly typed |
|
|
list of bugs, using the new generics feature of the |
|
|
.NET Framework. Each of the two overloads is |
|
|
discussed in greater detail in the section “Code and |
|
|
Code Explanation.” |
399
Chapter 12
Almost all of the methods in the BugManager class do nothing more than delegate their responsibility to a method with the same name in the BugMagagerDB class. The only exception is the GetBugList method that also sorts the list of bugs by using the BugComparer class, which is discussed in the next section.
BugComparer
The BugComparer class implements the IComparer(Of Bug) interface, which enables sorting of objects in a list that uses generics. It implements the only required method, Compare, and has a constructor that accepts the name of a Bug property to sort on as a parameter. The Compare method compares the two Bug objects passed to it and returns an integer indicating whether the first Bug object is less than, equal to, or greater than the second Bug object. Because of its tight relation with sorting bugs in the BugManager, the BugComparer is implemented as a nested class in the BugManager class, visible in Figure 12-8.
CommentManager
When users update an existing bug, they can add a comment to provide additional information. These comments are handled by the CommentManager class, which is shown in Figure 12-9.
Figure 12-9
This is a very simple class to insert and retrieve comments and has only two methods:
Method |
Return Type |
Description |
|
|
|
GetCommentList (ByVal |
DataSet |
Returns a list of comments for the |
bugId As Integer) |
|
requested bug by calling into the |
|
|
CommentManagerDB class. |
InsertComment (ByVal |
n/a |
Inserts a new comment in the Comment |
bugId As Integer, ByVal |
|
table in the database and associates it |
theBody As String, |
|
with the bugId passed to this method. |
ByVal theMemberId As Guid) |
|
|
To get the various lists, such as Frequency and Severity in the presentation layer, the business layer has a ListManager class, which is discussed next.
ListManager
The ListManager class is responsible for retrieving lists that are displayed on the web site. It has nine public shared methods (see Figure 12-10) to retrieve applications, features, and lists of other bug properties, such as the Severity, Reproducibility, Status, and Frequency. These lists are used in the presentation layer to
400
The Bug Base
fill drop-down menus. Because it has only shared methods, the constructor of the class is hidden by marking it Private. This prevents you from accidentally creating instances of the ListManager class. To use the methods in the class, you can simply call them on the class name.
Figure 12-10
The ListManager class caches most of these lists in the ASP.NET cache using a SqlCacheDependency, so there is no need to hit the database every time they are needed. Because these lists are used quite often, this greatly increases the application’s performance. You see later how this works. The following table lists the public methods that are used for working with Applications and Features:
Property |
Return Type |
Description |
|
|
|
GetApplicationItems |
DataSet |
Returns a list with Applications as a DataSet |
|
|
(+ two additional overloads). The DataSet |
|
|
contains two columns: the ID and the |
|
|
Description of the item in the database. The |
|
|
overloads are used to limit the list to active |
|
|
applications, or to applications to which a |
|
|
user has access. |
GetApplicationDescription |
String |
Returns the user-friendly name of an |
(ByVal applicationId As |
|
application. |
Integer) |
|
|
GetFeatureItems |
DataSet |
Returns a list with Feature items as a DataSet. |
|
|
The DataSet contains two columns: the ID |
|
|
and the Description of the item in the |
|
|
database. |
The methods that return the lists for Frequency, Reproducibility, Severity, and Status all follow the same pattern. They return the requested items as a DataSet that has an ID and a Description column. Under the hood, they call the private method GetListItems and pass it a custom ListType enumeration (defined in the BusinessLogic folder in the file called ListType.vb) to indicate the type of list to retrieve. The GetListItems method then calls into the data access layer to get the items from the database.
401
Chapter 12
MemberManager
The MemberManager class (see Figure 12-11) is responsible for changing the user’s access rights in the database. Because the ASP.NET 2.0 Framework already provides a lot of ready-to-use classes to work with users and security settings in your application, the implementation of the MemberManager is very simple.
Figure 12-11
The MemberManager class has two public subs that allow you to assign and unassign a user to a specific application:
Property |
Description |
|
|
AssignMemberToApplication |
Assigns a member indicated by memberId to the |
(ByVal memberId As Guid, |
requested application. |
ByVal applicationId As Integer) |
|
UnAssignMemberFromApplication |
Removes a member from the requested application. |
(ByVal memberId As Guid, |
|
ByVal applicationId As Integer) |
|
Both these methods call a private member in the MemberManager class called ChangeMember ApplicationBinding and pass it either True or False to indicate whether the user should be added to or removed from the application.
NameValue
Many of the properties of a bug in the database, such as the Severity and the Reproducibility, are actually foreign keys to other tables in the BugBase database. These tables are often referred to as domain tables. This means that only the ID is stored with the bug. To the end-user of the application, these IDs are meaningless. To display the friendly name of these properties in the user interface, the Bug class exposes these properties as NameValue objects. The NameValue class (see Figure 12-12) has a Value property that holds the underlying ID in the database. The Name property exposes the friendly name.
You can create a new NameValue by calling the default constructor and then set the Name and Value properties individually. Alternatively, you can call the overloaded constructor that accepts values for the Name and Value properties as arguments.
SearchCriteria
The SearchCriteria class (see Figure 12-13) is used by the BugManager in the GetBugList methods. The GetBugList allows you to search for bugs that match a comprehensive list of search criteria.
402
The Bug Base
Figure 12-12
Figure 12-13
Instead of passing each of these criteria separately to this method, you can pass a single SearchCriteria object that exposes public properties for each of the criteria. The GetBugList method examines each of these properties and builds up the criteria parameters that are passed to the database. This is explained in more detail when the BugManagerDB class is examined in the next section.
The Data Access Layer
The data access layer in the Bug Base is designed to work with SQL Server only, because it uses types you find in the System.Data.SqlClient namespace, like the SqlConnection and SqlCommand objects. However, to make it easier to switch databases later in the lifetime of the application, none of the methods in the layer returns data provider–specific types. Instead, each of these methods returns standard types like a DataSet, or custom generics lists, like the bug list. If you decide to change the database you’re using, all you need to change is the methods in the data access layer. As an alternative to changing the data access layer each time you want to target a different database, you can also recode the data access layer using the provider factories pattern that you saw in Chapter 6.
403
Chapter 12
The only exception to this rule is the GetList method in the ListManager class. This method uses SqlCacheDependency classes to cache data from the database. The cache is invalidated whenever the underlying table in the database changes. SQL cache invalidation only works with SQL Server, so if you decide to switch databases, you’ll need to modify the GetList method by either removing the code responsible for caching altogether or by implementing a different caching strategy.
The use of DataSets in the data access layer causes some overhead when compared to lightweight objects like the DataReader. However, this overhead can be minimized by implementing a thorough caching strategy, as is done by the methods in the ListManager class. By creating a SqlCacheDependency on the relevant tables in the database, you can in fact increase performance. All of the domain list tables, such as Severity and Reproducibility, are cached as DataSets in memory, so there is no need to hit the database each time you need them. Only when the table is changed — something that won’t happen very often — is the item removed from the cache and needs to be reconstructed. This greatly reduces the number of calls made to the database, something that cannot be accomplished using DataReader objects.
Before discussing the data access layer, you should take a look at the design of the database first. Because each of the methods in the data access layer talks directly to the SQL Server 2005 database, it’s important to understand how the database is designed. Figure 12-14 displays the database diagram for the Bug Base, showing most of its tables and relations.
Figure 12-14
Figure 12-14 does not show the tables that have been added for the ASP.NET 2.0 Membership and Role providers, except for the aspnet_Users table that has relations with other tables in the Bug Base. The following table discusses each table in the database and its intended purpose:
404
|
|
The Bug Base |
|
|
|
|
Table Name |
Description |
|
|
|
|
Application |
Holds a list with all the applications you can file bugs against. The column |
|
|
IsActive determines whether the application is still in use. |
|
aspnet_Users |
This table is added by the aspnet_regsql.exe tool when you enable the |
|
|
database for Membership and Roles. It holds the user accounts for the Bug |
|
|
Base application. The UserId, a GUID, is used to link other tables to this |
|
|
table. |
|
Bug |
The logged bugs are stored in this table. Besides a Title, a Description, and the |
|
|
date the bug was created and updated, this table largely consists of foreign |
|
|
keys pointing to domain list tables. |
|
Comment |
Holds comments that users can add to existing bugs. The CreateMemberId |
|
|
has a link to the aspnet_Users table to keep track of who added the comment. |
|
Feature |
Features are the main parts that make up your application. A bug should |
|
|
be logged against a specific feature, to make it clearer where the bug occurs |
|
|
and who’s responsible for it. A feature is always tied to an application, so |
|
|
the Feature table has an ApplicationId column that points back to the |
|
|
Application table. |
|
Frequency |
The frequency of a bug defines how often, or by how many users, a bug |
|
|
will be encountered. This table holds a list with possible options that the |
|
|
user can choose from. |
|
MemberApplication |
Users should not be able to log bugs against any arbitrary application. An |
|
|
Administrator can assign members to a specific application through the |
|
|
web application. This assignment is stored in the junction table Member- |
|
|
Application. |
|
Reproducibility |
The reproducibility of a bug defines whether a bug is reproducible at all, |
|
|
and if so, how often. Just as the Frequency and Feature tables, this is a |
|
|
domain list table that stores the description for each item with a primary |
|
|
key. This key is then used as a foreign key in the Bug table. |
|
Severity |
The severity describes the impact of the bug. This domain list table holds |
|
|
the various options for this bug property. |
|
Status |
This table holds a list with possible status options for a bug. The ClosesBug |
|
|
column determines whether the bug becomes inactive with a specific status. |
|
|
This is the case for a status such as Deferred, Closed, or Not a Bug. |
|
|
|
In addition to these 10 tables, the database also contains a number of stored procedures. Many of these procedures follow a strict naming pattern:
sprocTableNameSelectSingleItem
sprocTableNameSelectList
sprocTableNameInsertUpdateSingleItem
405
Chapter 12
The first procedure selects a single record from a table referred to by TableName. The WHERE clause always uses at least the primary key of the table to limit the number of records to a maximum of 1, as in the following procedure that queries a feature from the database:
CREATE PROCEDURE sprocFeatureSelectSingleItem
@id int
AS
SELECT
Id,
Description,
ApplicationId
FROM
Feature
WHERE
Id = @id
The *SelectList procedures query a list of related items from the database, such as a list of features, bugs, applications, and so on. They often look very similar to the SelectSingleItem bugs in terms of the columns they return, but they don’t use the primary key of the table in the WHERE clause, and they often sort the result set using an ORDER BY clause.
All the *InsertUpdate procedures are capable of both inserting new and updating existing items in the database. They do that by looking at the @Id parameter passed to this procedure. If that parameter — which represents the primary key of the record in the table — is null, a new record is inserted. Otherwise, an existing record is updated where the @id parameter is used in the WHERE clause as demonstrated in the following code:
CREATE PROCEDURE sprocFrequencyInsertUpdateSingleItem
@id int = null, @description nvarchar (100)
AS
DECLARE @returnValue int
IF (@id IS NULL) -- New Item
BEGIN
--Insert the item here and return its new Id
--Insert code is left out of the example SELECT @returnValue = Scope_Identity()
END
ELSE
BEGIN
--Update the item here and return the existing Id
--Update code is left out of the example
SELECT @returnValue = @id
END
- Return the new or existing Id to the calling code RETURN @returnValue
406