Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP.NET 2.0 Instant Results

.pdf
Скачиваний:
67
Добавлен:
17.08.2013
Размер:
11.03 Mб
Скачать

The Bug Base

Most of the items in the tables should have unique values. For example, there is no point in having two identical “Not reproducible” items in the Frequency table, because you wouldn’t be able to distinguish between the two. Most of the *InsertUpdate procedures use the following code to check for duplicates:

IF (@id IS NULL) -- New Item BEGIN

IF NOT EXISTS (SELECT 1 FROM Frequency WHERE Description = @description) BEGIN

--Insert the item here and return its new Id

--Insert code is left out of the example

END ELSE BEGIN

--There is already an item with the same description, so return -1 SELECT @returnValue = -1 -- Item already exists

END

END

ELSE

BEGIN

IF NOT EXISTS (SELECT 1 FROM Frequency WHERE Description = @description AND Id <> @id)

BEGIN

-- Update the item here and return the existing Id

--Update code is left out of the example

END ELSE BEGIN

--There is already an item with the same description, so return -1 SELECT @returnValue = -1 -- Item already exists

END

END

RETURN @returnValue

With this general pattern in mind it should be easy to understand how most of the procedures work. Not all procedures follow this strict pattern, so the few exceptions are explained during the discussion of the data access layer methods that use them.

These stored procedures are called from, and only called from, the methods in the four classes in the data access layer, which are discussed next.

BugManagerDB

Just as the BugManager class in the business layer, the BugManagerDB class (shown in Figure 12-15) is responsible for creating, changing, and getting bugs. Methods in this class talk directly to the database, using objects like the SqlConnection and SqlCommand. None of the methods contain SQL statements — all data access is done through the use of stored procedures. Most of the methods in this class accept a Guid that holds the current member’s ID. This ID is used in all stored procedures to determine if the member has sufficient rights to access the data.

407

Chapter 12

Figure 12-15

Similar to other classes you have seen in this book, the BugManagerDB class has only shared methods, so its constructor is marked as Private. The three other methods of the BugManagerDB class are listed in the following table:

Method

Return Type

Description

 

 

 

GetBug (ByVal id As Integer,

Bug

Retrieves a bug from the database,

ByVal memberId As Guid)

 

based on the ID passed to this

 

 

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 from the

 

 

database based on search criteria.

InsertUpdateBug

Integer

Saves a fully populated Bug object in

(ByVal theBug As Bug)

 

the database. The Integer returned

 

 

from this method is the new or current

 

 

ID of the bug in the database.

CommentManagerDB

The CommentManagerDB class, shown in Figure 12-16, performs the data access for the two methods defined in the CommentManager class.

Figure 12-16

Just as the CommentManager class, the CommentManagerDB class has only two methods (besides its hidden constructor): one for getting a list of comments that belong to a certain bug, and one to create a new comment, as explained in the following table:

408

 

 

 

The Bug Base

 

 

 

 

 

 

 

 

 

Method

Return Type

Description

 

 

 

 

 

GetCommentList (ByVal

DataSet

Returns a list of comments sorted by date

 

bugId As Integer)

 

in descending order for the requested bug.

 

InsertComment (ByVal bugId

n/a

Inserts a new comment in the Comment

 

As Integer, ByVal theBody

 

table and associates it with the bug

 

As String, ByVal theMemberId

 

designated by bugId.

 

As Guid)

 

 

 

 

 

 

To get lists of items, such as Frequency and Severity, from the database, the application has a

ListManagerDB class.

ListManagerDB

The ListManagerDB class has fewer methods than the ListManager class in the business layer because four of the methods in the ListManager class use the same GetListItems method. In addition to the GetListItems method, the ListManagerDB class has three other methods that map to the ones in the business layer (see Figure 12-17).

Figure 12-17

The following table gives a description of the entire ListManagerDB class:

Method

Return Type

Description

 

 

 

 

 

 

GetApplicationDescription

String

Returns the full description of an application

(ByVal applicationId As

 

based on its ID passed to this method.

Integer

 

 

GetApplicationItems

DataSet

Gets a list of Applications from the and

(ByVal activeOnly As

 

returns it as a DataSet. The activeOnly

Boolean, ByVal memberId

 

parameter is used to limit the list to active

As Guid)

 

applications. The memberId can be used to

 

 

limit the list to applications to which the

 

 

member has access.

GetFeatureItems (ByVal

DataSet

Gets a list of Feature items from the

applicationId As Integer)

 

database and returns it as a DataSet.

GetListItems (ByVal

DataSet

Returns a list with the requested items as

theListType As ListType)

 

a DataSet. The ListType parameter deter-

 

 

mines the type of list to return.

 

 

 

409

Chapter 12

The Member class in the business layer also has a counterpart in the data access layer: the MemberManagerDB class.

MemberManagerDB

The MemberManagerDB class has a single sub that can assign or remove a member from a specific application:

Method

Description

 

 

ChangeMemberApplicationBinding

Assigns or removes a member from an application.

(ByVal memberId As Guid, ByVal

When mustAssign is set to True, the member is

applicationId As Integer, ByVal

assigned to the requested application; otherwise the

mustAssign As Boolean)

member is removed.

 

 

In addition to the files in the BusinessLogic and DataAccess folders, two other files located in the App_Code folder are used throughout the site. The file AppConfiguration.vb contains a class with read-only properties that are essentially wrappers around the various <appSettings> keys in the Web.config file. Instead of typing ConfigurationManager.ConnectionStrings(“BugBase”).ConnectionString each time you need the connection string, you can now simply type AppConfiguration.ConnectionString.

The Helpers.vb file contains a few helper methods that are used in various pages in the site. The following section, “Code and Code Explanation,” discusses some of the methods defined in the file, such as

SetMemberId and CheckApplicationState. The FormatGridViewPagerBar method is used to format the pager bar that is displayed on each of GridView controls used in the application. That method isn’t explained any further, but it has enough inline comments for you to understand how it works.

Now that you’ve seen the design of the Bug Base and all of its important classes, it’s time to examine the

.aspx pages, their code-behind files, and the implementation of the classes in the business logic and data access layers.

Code and Code Explanation

This section digs into each of the important pages and shows you how they interact with each other and use the classes in the business layer. Instead of listing each page separately, this section takes a more usage-oriented approach by examining typical workflows for the Bug Base and discusses each page you’re visiting in the process. But before starting the tour, a few files need to be discussed first.

Root Files

In the root of the site you’ll find a number of files that are critical for the Bug Base application. Not each file is explained completely, but instead the focus is on the most important areas.

Web.config

The Web.config file is the central place for storing application settings and configuration information. For the Bug Base, there are a few important bits in this file.

410

The Bug Base

First of all, there is the connection string that is used throughout the site:

<add name=”BugBase” connectionString=”server=(local)\SqlExpress;AttachDbFileName=|DataDirectory|BugBase

.mdf;Integrated Security=true;User Instance=true” />

This connection string points to a local name instance of SQL Server called SqlExpress and uses a database called BugBase. The |DataDirectory| token in the AttachDbFileName attribute tells SQL Server to try to automatically attach the database located in the App_Data folder of the web site.

The next important piece in the Web.config file is the setup for the Membership and Roles providers that are used in the Bug Base. These providers allow you to implement security on your site with little to no coding. By default, when you enable the Membership on a site, ASP.NET creates a default database called aspnetdb.mdf for you. For the Bug Base, a different database was created that, in addition to the tables and procedures for membership and roles, also holds the objects required for the Bug Base. To tell the ASP.NET run time where to look for that database, the <providers> section of the <membership> node in the Web.config file must be configured correctly:

<membership>

<providers> <clear />

<add name=”AspNetSqlMembershipProvider” type=”System.Web.Security.SqlMembershipProvider, System.Web,

Version=2.0.0.0, Culture=neutral,

PublicKeyToken=b03f5f7f11d50a3a”

connectionStringName=”BugBase”

enablePasswordRetrieval=”false”

enablePasswordReset=”true”

requiresQuestionAndAnswer=”false”

applicationName=”/”

requiresUniqueEmail=”true”

passwordFormat=”Hashed”

maxInvalidPasswordAttempts=”5”

passwordAttemptWindow=”10”

passwordStrengthRegularExpression=””

/>

</providers>

</membership>

The <clear /> element removes the AspNetSqlMembershipProvider that is set up by default in the Machine.config file that applies to all sites on your server. The default setup points to the aspnetdb.mdf database mentioned earlier. Without removing this element, it’s not possible to override the settings and have the MembershipProvider use the custom database instead. With the original element removed, you can add your own and then indicate you want to use the BugBase database by setting the connectionString attribute that in turn points to the connection string defined earlier. The other attributes have to do with security settings for the provider. Refer to the MSDN documentation for their usage.

The Bug Base uses a role-based security mechanism to determine which actions a user is allowed to perform. Just as with the MembershipProvider, ASP.NET 2.0 has a ready-made provider for this, called the RoleProvider. The section that sets up this provider in the Web.config file looks like this:

411

Chapter 12

<roleManager defaultProvider=”SqlProvider” enabled=”true” cacheRolesInCookie=”true” cookieName=”.ASPROLES” cookieTimeout=”30”

cookiePath=”/”

cookieRequireSSL=”false”

cookieSlidingExpiration=”true”

cookieProtection=”All”

>

<providers>

<add

name=”SqlProvider”

type=”System.Web.Security.SqlRoleProvider”

connectionStringName=”BugBase”

/>

</providers>

</roleManager>

To show you the different options, the <roleManager> takes a different approach. Instead of using the <clear /> element to clear a previously defined role manager (called AspNetSqlRoleProvider in the Machine.config file), this code block sets up an entirely new provider with the name of SqlProvider. Because there is no conflict with an existing provider on the system, you don’t need to use <clear /> first.

This is all that’s required to configure the application so it uses the built-in Membership and Role providers.

The Web.config file also contains settings that determine if and to what e-mail address errors that occur should be e-mailed by code in the Global.asax file. The usage of these keys is further explained when the Global.asax file is discussed.

At the bottom of the Web.config file you find a number of <location> nodes. These nodes override the default <authorization> element to block or allow access to some files and folders for specific roles.

MasterPage.master

The master page defines the look and feel for all the pages in the site. This ensures a consistent layout throughout the site and makes it very easy to apply site-wide changes. The file consists largely of static HTML for the layout of the site, but a few sections are worth examining in greater detail.

The main menu that appears at the top of every page is made up of nested <ul> and <li> tags. The CSS file for the menu, Menu.css, is responsible for hiding or displaying the menus when you hover over them. Inside the menu a LoginView control is used to determine which menu items a user has access to, based on the current user’s role. The following code snippet demonstrates this:

<asp:LoginView runat=”server” ID=”lvReporting”> <RoleGroups>

<asp:RoleGroup Roles=”Manager”> <ContentTemplate>

<li>

<div>

<a href=”~/Reporting/Default.aspx” runat=”server”>Reporting</a> </div>

412

The Bug Base

<ul>

<li>

<a href=”~/Reporting/Default.aspx” runat=”server”>Reports</a> </li>

</ul>

</li>

</ContentTemplate>

</asp:RoleGroup>

</RoleGroups>

</asp:LoginView>

The content defined in the ContentTemplate is only accessible to users that are in the roles defined on the RoleGroup element, in this case the Manager role only.

The second important piece in the Master file is the use of ContentPlaceHolder controls. A Content PlaceHolder defines a region that can be overridden by pages that use the master page. The master page has two placeholders — one for the page title and one for content section of the page. The page title looks like this:

<h1>

<asp:ContentPlaceHolder ID=”plcTitle” runat=”server”></asp:ContentPlaceHolder> </h1>

The placeholder is put inside an <h1> tag so the content is always rendered as a heading. The placeholder for the main content section of each page looks very similar to the one for the heading.

Global.asax

The Global.asax file contains code for only one of the events, namely Application_Error. Whenever an unhandled exception occurs in the application, this event is fired. The code for this event builds up a string with the error details and sends it as an e-mail to the address configured in the Web.config. Before you enable this feature by setting SendMailOnErrors to True, make sure you also set valid e-mail addresses and an SMTP server in the Web.config file.

Web.sitemap

The final file in need of discussion is Web.sitemap. This file contains a lot of siteMapNode elements that define a conceptual map of the web site. This file is used as the data source for the SiteMapPath control in the BreadCrumb section of the master page. It’s also used to feed the TreeView control used in the SiteMap.aspx page in the Help folder.

Now that you’ve seen some of the framework files, it’s time to look at the files that are used in a typical workflow.

Filing a Bug

The central action of a Bug Base application is of course filing a bug, so it’s a logical choice to look at that first. This section walks you through filing a new bug, explaining each of the important parts of the files you visit in the process. This section assumes that the Bug Base is installed at http://localhost/BugBase. Refer to the section called “Setting up the Bug Base” for more details about installing the application.

413

Chapter 12

When you open the homepage of the Bug Base at http://localhost/BugBase the first time, you’re presented with a Login screen instead. The <authorization> section in the Web.config file blocks access to each of the pages in the site to unauthorized users. When an unauthorized request is made, you’re redirected to the Login page instead. This Login page contains very little code because most of the functionality required to log in a user is available out of the box. The markup section of the page contains just a Login control:

<asp:Login ID=”Login1” runat=”server” InstructionText=”Before you can

work with the Bug Base, you need to login.<br />Please type your user name and password and click the Log In button.”

TitleText=”” DestinationPageUrl=”~/Bugs/SwitchApplication.aspx” DisplayRememberMe=”False”>

</asp:Login>

The DestinationPageUrl attribute is set to SwitchApplication.aspx, the page the user is redirected to after a successful login. As a security measure, the Remember Me checkbox is disabled so users are required to log in each time they visit the bug base. If you get tired of entering your name and password every time, simply set the DisplayRememberMe attribute to True. This will display an additional Remember Me checkbox allowing you to automatically log in each time you return to the site.

There is no code in the code-behind file for this page — the authentication is completely carried out by the ASP.NET Framework.

When you supply a valid username and password (you can log in with the accounts listed at the beginning of this chapter) and click the Log In button you’re automatically logged in. ASP.NET validates the user against the database, and when the login details are correct the roles for the user are retrieved and stored in an encrypted cookie.

After you log in you’re redirected to SwitchApplication.aspx. Before you can work with most of the pages in the Bug Base you need to select an active application to work with. The SwitchApplication page allows you to select that application. In the Page_Load event of this page, the following code fires:

Helpers.SetMemberId()

The SetMemberId method, which you’ll find in the Helpers.vb file in the App_Code folder, tries to retrieve the current user’s ProviderUserKey, which is the unique ID for the user. This key is stored in a session variable so it’s available to all pages in the site. When the retrieval fails, the user is redirected back to Login.aspx.

The drop-down on the SwitchApplication page lists all the applications to which the user has access. The drop-down is filled by an ObjectDataSource control that calls an overloaded version of the method

GetApplicationItems in the business layer:

<asp:ObjectDataSource ID=”ObjectDataSource1” runat=”server” SelectMethod=”GetApplicationItems” TypeName=”ListManager”>

<SelectParameters>

<asp:SessionParameter Name=”memberId” SessionField=”MemberId” /> </SelectParameters>

</asp:ObjectDataSource>

This method expects the current user’s ID, which is passed to this method using a SessionParameter that retrieves the ID from a session variable called MemberId set earlier by the SetMemberId method.

414

The Bug Base

The GetApplicationItems method in turn calls another overloaded version that delegates the call to a method with the same name in the data access layer. This method is responsible for retrieving the applications from the database. The code in this method is typical for many of the data access methods in the data access layer:

Public Shared Function GetApplicationItems( _

ByVal activeOnly As Boolean, ByVal memberId As Guid) As DataSet

Dim dataSet As DataSet = New DataSet()

Dim sql As String = “sprocApplicationSelectList”

Try

Using myConnection As New SqlConnection(AppConfiguration.ConnectionString) Dim myCommand As SqlCommand = New SqlCommand(sql, myConnection) myCommand.CommandType = CommandType.StoredProcedure

myCommand.Parameters.AddWithValue(“@activeOnly”, activeOnly) If Not memberId = Guid.Empty Then

myCommand.Parameters.AddWithValue(“@memberId”, memberId)

End If

Dim myDataAdapter As SqlDataAdapter = New SqlDataAdapter() myDataAdapter.SelectCommand = myCommand myDataAdapter.Fill(dataSet)

myConnection.Close()

Return dataSet

End Using

Catch ex As Exception

Throw

End Try

End Function

First, the name of the stored procedure in the database is set. Then a new SqlConnection is created. The connection string comes from the custom class AppConfiguration that you saw earlier.

Then a SqlCommand is set up by assigning important properties such as the CommandText, CommandType, and Connection. The activeOnly parameter of the stored procedure determines whether all or only the active applications are to be retrieved from the database. As a second parameter, the ID of the member is passed. This ensures that you only get applications back that are assigned to the current user.

Finally, a SqlDataAdapter is created, which is then used to fill the DataSet with the results from the database using the SqlDataAdapter’s Fill method.

The stored procedure that gets the items from the database looks like this:

CREATE PROCEDURE sprocApplicationSelectList @activeOnly bit = null,

@memberId uniqueidentifier = null

AS

SELECT DISTINCT

415

Chapter 12

Id,

Description,

IsActive

FROM

Application

LEFT OUTER JOIN MemberApplication

ON Application.Id = MemberApplication.ApplicationId

WHERE

(IsActive = @activeOnly OR @activeOnly IS NULL)

AND (MemberApplication.MemberId = @memberId OR @memberId IS NULL) ORDER BY

Descriptionn

This stored procedure retrieves a list of all the applications that are assigned to the current member. You’ll recall from the discussion of the data model that members are linked to applications with the junction table called MemberApplication. The code in the stored procedure uses that junction with the LEFT OUTER JOIN to limit the list of applications to those that the member has access to. The LEFT OUTER JOIN as opposed to an INNER JOIN is used to allow the procedure to return all applications regardless of the member’s access rights when the parameter @memberId is null. This is used in the Management section that you see later.

When the ObjectDataSource in the .aspx page is done with the GetApplicationItems method, having retrieved the data, it fires its Selected event. In this event you can check if any data was returned from the database by looking at the ReturnValue property of the e argument. If the DataSet is empty — which it will be when the current member has no applications assigned — the drop-down is hidden and the user is presented with an error message:

Protected Sub ObjectDataSource1_Selected(ByVal sender As Object, _

ByVal e As System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs) _ Handles ObjectDataSource1.Selected

If CType(e.ReturnValue, DataSet).Tables.Count > 0 _

AndAlso CType(e.ReturnValue, DataSet).Tables(0).Rows.Count = 0 Then

lblErrorMessage.Visible = True lstApplication.Visible = False btnApply.Visible = False

End If End Sub

When the user has chosen an application from the drop-down and clicked the Apply button, the following code is executed:

Helpers.SetApplication (Convert.ToInt32( _

lstApplication.SelectedValue), lstApplication.SelectedItem.Text)

Dim redirectUrl As String = “~/Bugs/”

If Request.QueryString.Get(“OriginalPage”) IsNot Nothing Then

redirectUrl = Request.QueryString.Get(“OriginalPage”)

End If

Response.Redirect(redirectUrl)

This code sets the active application by calling Helpers.SetApplication, which stores the application ID in a session variable and then redirects the user to the previous page or to the default page in the Bugs folder when there was no previous page.

416