Schedule Application

Now that the basics of databound controls have been covered, it is time to apply this by building a schedule/contact application. Most simple applications can be thought of as a two-layer problem. There is the user interface, which is what you want the user to see, and the data access, which is how data is stored. Most of the programming work is in hooking up the visual layer to the underlying data layer. More complex applications will also have the so-called business logic layer. Our scheduling application is a fairly straightforward presentation of data, so it can be built easily without a business logic layer. I will, however, spend a little time discussing what kind of additions to this application would be most easily accomplished by adding a business logic layer.

Application Overview

Before getting into the design details for each layer, let's define the application to be developed. Our application is to be a basic scheduling application with a contact manager. Each user will have her own schedule and contacts. Because this application is intended to be available for trial on the book's Web site, users will need to be able to register themselves. The data for this application will be stored in a SQL Server database, and to demonstrate a best practice way of accessing the data, all data access will be done through stored procedures. Style sheets are used as much as possible for setting the style elements to maintain consistency across the application.

User Interface Design

When designing an application, I often start with the user interface design and then determine what kinds of data I need from what I want displayed. This is not always the best design method for all applications; for some applications, the data requirements are well known, and it is easier to do the database design first and then figure out a way to display the data in a manner that is clear to the end user. In this application, though, let's start with the user interface.

When I was designing this application, two areas were clearly necessary. There needed to be a section with the schedule and a section for the contacts. What wasn't so clear was what to do with the login and registration. In the end, I decided to break the application into the following three pages:

  • Home Page This is where the login and registration occur, and, in the case of this application, it also contains an explanation of the application.

  • Schedule Page This page is where the schedule is displayed.

  • Contact Page This page is where the contacts are displayed.

The three pages are connected with a simple menu bar. Each page is just that: a single .aspx page. Changes of what is displayed on each of the pages are done by implementing portions of each page as a user control and changing its visibility depending upon whether or not it needs to be displayed.

Designing with User Controls

User controls are a quick way of encapsulating user interface portions of your program in a way that is reusable. There are a couple of big disadvantages to user controls that limit their overall usefulness. These are the necessity to have an .ascx file and the corresponding inability to compile a user control to only a .dll file, and the inability to share a user control across Web applications. The second disadvantage is, in my experience, the biggest one.

Alternatively, user controls are easy to create because they are basically encapsulated .aspx pages. They are most useful for componentizing portions of an application when they will be used only in that application. If a control was to be used across a number of applications, it might be better to spend the extra time and create a custom control. Custom controls are covered in more detail in Chapter 15, "Server Controls and HttpModules."

The biggest thing about user controls is not how to use them, but when to use them. Currently, Visual Studio. NET does not provide as much support for user controls as I would really like. Specifically, when a user control is placed on another control, Visual Studio .NET does not render it visually so that you can see how it looks. Visual Studio .NET also does not automatically add an access variable in the code-behind file, and, at least when developing in C#, you must do all event hook-ups by hand. Although future versions of Visual Studio .NET may address some of these issues, currently it is quite tedious to nest user controls to any great extent if there are any data dependencies, because you must either handle events, pass data into the nested user controls in the Page_Load() method, or place the necessary data items in the .aspx page. The last method is the easiest, but it creates a dependency on the underlying page, which reduces the reusability of the code.

So when do you use them? Normally, you use a user control when your content logically fits together and you are likely to reuse that content in multiple places throughout an application but not across applications. It is also easier to implement portions of a page as a user control when the control is visible only part of the time because it is logically cleaner and easier to deal with than trying to place it all on the .aspx page. Generally, I try to avoid nesting user controls as much as possible, but I do use them to a great extent.

In this scheduling application, I use a number of user controls, and their usage pretty much follows my usage guidelines listed above. The menu is encapsulated into a user control because it is needed on all three pages. Other items that are logically grouped together for display purposes and functionality are also encapsulated into user controls. If I have some HTML that is conditionally displayed with no code-behind element, and it is not used on multiple pages, I normally place it in a Panel server control rather then in its own user control.

Home Page

This is the starting page for the application. It is where the user logs in or registers. It uses the following user controls:

  • MenuModule

  • LoginModule

  • RegisterModule

The LoginModule user control is displayed when the user is not logged in, and the RegisterModule user control is displayed if the user presses the Register button on the LoginModule user control.

If the user is logged in, then only the MenuModule user control is displayed, in addition to the HTML that is part of the home.aspx page.

Schedule Page

This page displays all the scheduling information. The user can select between a monthly view and a daily view. A set of three small calendars are used to select dates more quickly. This page uses the following user controls:

  • MenuModule

  • DayModule

  • MonthModule

  • ScheduleEditModule

  • SmallMonth

Of these user controls, the SmallMonth user control is the only one that doesn't really follow my rules for when to use user controls. The three small month calendars were encapsulated in a user control for logic reasons rather then visual reasons. The DayModule user control illustrates the use of a DataList server control, and the MonthModule user control shows how to bind data to a Calendar server control.

Contact Page

This page displays all the contact information. The user can see the contacts filtered by the first letter of the last name, or all at once. Contacts can be added, edited, and deleted. This page uses the following user controls:

  • MenuModule

  • ContactEdit

  • ContactView

This page is the one on which it could have been easy to overuse user controls. I was going to place the contact list on its own user control, but I decided that no real advantage would have been gained. The contact list is a DataGrid server control, and the alphabetical selection is done using a Repeater server control.

Designing a Data Access Layer

Now that you have an overview of the user-interface aspects of the scheduling application, let's take a closer look at the data access layer.

The data structure in the application is fairly simple. There are three tables, defined as follows:

  • tblUser This table contains the information about the user, including the username and password used to log in to the application.

  • tblContact This table contains information needed for each contact. It is linked with a foreign key relationship to tblUser.

  • tblAppointment This table contains information needed for each appointment. It too is linked with a foreign key relationship to tblUser.

Before going any further, I want to say that I hate repetitious programming. It really annoys me to spend hours typing almost the exact same code over and over. Nowhere is that more true than when writing a data-access layer. Many data-access layers that I have seen repeat much of the same code over and over when they're writing classes that contain and load the data for a specific type of data. My solution to this problem has usually been to write a base class that handles all of the actual data access. This class is used as the base class for all of the data objects that I then use.

When designing a base class for this purpose, I try very hard to keep all the code that actually accesses the database in this class. This way, when I use my data object, I don't have to think about things such as opening connections, loading data, and so on. This is all done automatically by the base class. Let's look at a sample data-access class to show what I mean. Here is a code snippet that shows the usage of the appointment data item. It loads the appointment data item with its data from the AppointmentID that is used as the primary key for tblAppoinment.

 Dim objAppointment As New AppointmentItem objAppointment.ID=2 'objAppointment is now loaded with the data for the row in 'tblAppointment with AppointmentID equal to 2. 

Wow! That was easy. With two lines of code, I could get the data for my data object. Now consider this: The data was loaded with a parameterized call to a stored procedure. To do this without a data-access layer would involve 20 lines to 30 lines of code. Now many data-access layers provide similar ease of retrieving data. Normally, though, they shift the code to the data-access item's class, in this case, the AppointmentItem class. As more data-access classes are created in larger projects, this is still quite a bit of work. Let's look at the implementation of the AppointmentItem class. Listing 5.2 shows the AppointmentItem data-access class. The class has an attribute named ItemDef that keeps appointment IDs.

Listing 5.2 AppointmentItem Data-Access Class AppointmentItem.vb
 Imports System.Data <ItemDef ("Appointment", "AppointmentID")> _ Public Class AppointmentItem     ' Inherits the base class for data.     Inherits DataBase     'Public Property Accessors     Public ReadOnly Property AppointmentID() As Integer         Get             Return GetFieldData("AppointmentID")         End Get     End Property     Public Property UserID() As Integer         Get             Return GetFieldData("UserID")         End Get         Set(ByVal Value As Integer)             SetFieldData("UserID",Value)         End Set     End Property     Public Property TimeStart() As DateTime         Get             Return GetFieldData("TimeStart")         End Get         Set(ByVal Value As DateTime)             SetFieldData("TimeStart",Value)         End Set     End Property     Public Property TimeEnd() As DateTime         Get             Return GetFieldData("TimeEnd")         End Get         Set(ByVal Value As DateTime)             SetFieldData("TimeEnd",Value)         End Set     End Property     Public Property Title() As String         Get             Return GetFieldData("Title")         End Get         Set(ByVal Value As String)             SetFieldData("Title",Value)         End Set     End Property     Public Property Note() As String         Get             Return GetFieldData("Note")         End Get         Set(ByVal Value As String)             SetFieldData("Note",Value)         End Set     End Property     Public ReadOnly Property User() As UserItem         Get             Dim objReader As IDataReader             objReader = ExecDataReader("sp_User_GetByID")             If objReader.Read() Then                 LoadDataRecord(objReader)             Else                 Throw New Exception("Invalid UserID")             End If             objReader.Close()         End Get     End Property End Class 

As you can see, in this case, the AppointmentItem class is not very complex, and it barely has enough code to retrieve and store data. The class that does all the actual work in my data access layer is the DataBase class, which is the base class of the AppointmentItem class.

The DataBase Class

The DataBase class as it has been implemented provides a number of helper functions that reduce the amount of coding necessary to access the database. One of these functions can be seen in the preceding AppointmentItem class in the implementation of the User property. Look at the call to ExecDataReader.

 objReader = ExecDataReader("sp_User_GetByID") 

Nowhere do you see any data being passed to the ExecDataReader() method. This is because the ExecDataReader() method does a number of things. First, it uses the static DeriveParameters() method from the SqlCommandBuilder to determine what parameters are necessary for the stored procedure call. Then, if there are no other parameters passed to the ExecDataReader() method, it uses the names of the parameters to try to match the parameters with the property data of the data object. So because my stored procedure looks like this:

 ALTER PROCEDURE dbo.sp_User_GetByID   (     @UserID int   ) AS   Select * FROM [tblUser] WHERE [UserID]=@UserID   RETURN 

and the AppointmentItem class has a data property named UserID, the data from the property will be placed in the parameter for the stored procedure call. If you need to pass data that is not a property in the data-item class, you can pass the extra data directly in the method call. So the following statement is equivalent to the previous call to ExecDataReader:

 objReader = ExecDataReader("sp_User_GetByID",UserID) 

When passing the data directly to the ExecDataReader() method, you need to be aware of a couple of things. The parameters are matched in order according to how they are defined in the stored procedure, and the number of parameters must match the number of parameters in the stored procedure. There are equivalent ExecNonQuery() and ExecScalar() methods to match the Execute() methods of an SqlCommand class.

Table 5.1. Public Interface

Name

Type

Description

ConnectionString

Property

This property is a string that contains the connection string to the data source to be used. This connection string is retrieved from the ConnectionString entry in the appSettings section in the web.config file.

dbConnection

Property

This property returns an SqlConnection object that is initialized with the ConnectionString.

ExecDataReader

Method

This method calls a stored procedure and returns the results in a SqlDataReader. The parameters for the stored procedure can either be passed as parameters to this method, or else be matched by name to the data stored in the derived data class.

ExecNonQuery

Method

This method calls a stored procedure and does not return any results. The parameters for the stored procedure can either be passed as parameters to this method, or else be matched by name to the data stored in the derived data class.

ExecScalar

Method

This method calls a stored procedure and returns the results as a scalar value in an object. The parameters for the stored procedure can either be passed as parameters to this method, or else be matched by name to the data stored in the derived data class.

GetFieldData

Method

Returns the data for a specified field.

SetFieldData

Method

Sets the data for a specified field.

ID

Property

Returns the value of the Primary key data field.

Update

Method

Updates the database with the data in the current data object. If the ID is 0, then a new record is inserted.

Delete

Method

Deletes the current record from the database.

Before looking at the actual code of the DataBase class, let's look at the properties and methods that are provided. Table 5.1 details the public methods and properties of this class.

Listing 5.3 shows the complete code listing for the DataBase class.

Listing 5.3 DataBase Class Listing DataBase.vb
 Imports System.Data Imports System.Data.SqlClient Imports System.Diagnostics Imports System.Reflection Public Class DataBase     Private m_FieldData As New NameObjectCollection     Private _m_ConnectionString As String     Private m_dbConnection As SqlConnection     Public Sub New()         ResetData()     End Sub     Private Sub ResetData()         _FieldData.Clear()     End Sub     ' This property contains the connection string.     Public ReadOnly Property ConnectionString() As String         Get             Dim configurationAppSettings As _                 System.Configuration.AppSettingsReader = _                 New System.Configuration.AppSettingsReader                 m_ConnectionString = _                 CType(configurationAppSettings. _                 GetValue("ConnectionString", _                 GetType(System.String)), String)             Return _ConnectionString         End Get     End Property     Public ReadOnly Property dbConnection() As SqlConnection         Get             If (IsNothing(m_dbConnection)) Then                 m_dbConnection = _                     New SqlConnection(m_ConnectionString)             End If             Return m_dbConnection         End Get     End Property     Private Sub AddParameters( _         ByVal objCommand As SqlCommand, _         ByVal objValues() As Object)         Dim objValue As Object         Dim I As Integer         Dim objParameter As SqlParameter         objCommand.Parameters.Clear()         SqlCommandBuilder.DeriveParameters(objCommand)         I = 0         For Each objParameter In objCommand.Parameters             If objParameter.Direction = ParameterDirection.Input _                 Or objParameter.Direction = _                 ParameterDirection.InputOutput Then                 objValue = objValues(I)                 objParameter.Value = objValue                 I = I + 1             End If         Next     End Sub     Private Sub AddFieldParameters _         (ByVal objCommand As SqlCommand)         Dim objParameter As SqlParameter         objCommand.Parameters.Clear()         SqlCommandBuilder.DeriveParameters(objCommand)         For Each objParameter In objCommand.Parameters             objParameter.Value = _                 _FieldData.Item(objParameter.ParameterName. _                 Substring(1))         Next     End Sub     Public Function ExecDataReader _         (ByVal strStoredProc As String, _         ByVal ParamArray objValues() As Object) _         As SqlDataReader         Dim objCommand As SqlCommand         Dim objReader As SqlDataReader         objCommand = New SqlCommand         objCommand.CommandText = strStoredProc         objCommand.CommandType = CommandType.StoredProcedure         objCommand.Connection = dbConnection         Try             objCommand.Connection.Open()             If (objValues.Length = 0) Then                 AddFieldParameters(objCommand)             Else                 AddParameters(objCommand, objValues)             End If             objReader = objCommand. _                 ExecuteReader(CommandBehavior.CloseConnection)         Catch ex As Exception             If objCommand.Connection.State.Open Then                 objCommand.Connection.Close()             End If         End Try         Return objReader     End Function     Public Sub ExecNonQuery _         (ByVal strStoredProc As String, _         ByVal ParamArray objValues() As Object)         Dim objCommand As SqlCommand         objCommand = New SqlCommand         objCommand.CommandText = strStoredProc         objCommand.CommandType = CommandType.StoredProcedure         objCommand.Connection = dbConnection         Try             objCommand.Connection.Open()             If (objValues.Length = 0) Then                 AddFieldParameters(objCommand)             Else                 AddParameters(objCommand, objValues)             End If             objCommand.ExecuteNonQuery()         Catch ex As Exception             Throw ex         Finally             If objCommand.Connection.State.Open Then                 objCommand.Connection.Close()             End If         End Try     End Sub     Public Function ExecScalar _         (ByVal strStoredProc As String, _         ByVal ParamArray objValues() As Object)         Dim objCommand As SqlCommand         Dim objReturn As Object         objCommand = New SqlCommand         objCommand.CommandText = strStoredProc         objCommand.CommandType = CommandType.StoredProcedure         objCommand.Connection = dbConnection         Try             objCommand.Connection.Open()             If (objValues.Length = 0) Then                 AddFieldParameters(objCommand)             Else                 AddParameters(objCommand, objValues)             End If             objReturn = objCommand.ExecuteScalar()         Catch ex As Exception             Throw ex         Finally             If objCommand.Connection.State.Open Then                 objCommand.Connection.Close()             End If         End Try         Return objReturn     End Function     Private m_ItemDefData As ItemDefAttribute     Public Function GetItemDefData() As ItemDefAttribute         If IsNothing(m_ItemDefData) Then             m_ItemDefData = CType(Attribute.GetCustomAttribute( _                 Me.GetType(), GetType(ItemDefAttribute)), _                 ItemDefAttribute)         End If         Return m_ItemDefData     End Function     Public Function GetFieldData _         (ByVal strProperty As String) As Object         Return _FieldData.Item(strProperty)     End Function     Public Sub SetFieldData _         (ByVal strProperty As String, ByVal Value As Object)         _FieldData.Item(strProperty) = Value     End Sub     Public Sub LoadDataRecord(ByVal objRecord As IDataReader)         Dim objItem As Object         Dim strKey As String         Dim I As Integer         Dim objRowDef As DataTable         objRowDef = objRecord.GetSchemaTable()         For I = 0 To objRowDef.Rows.Count - 1             strKey = objRowDef.Rows.Item(I).Item(0)             Try                 objItem = objRecord.Item(strKey)                 If Not objItem.GetType.IsInstanceOfType( _                     GetType(System.DBNull)) Then                     _FieldData.Item(strKey) = objItem                 End If             Catch ex As Exception             End Try         Next     End Sub     Public Property ID() As Integer         Get             Return _FieldData.Item(GetItemDefData().PrimaryKey)         End Get         Set(ByVal Value As Integer)             Dim objReader As SqlDataReader             Dim strItem = GetItemDefData.Name             If Value > 0 Then                 ' Note that the stored procedure name follows                 '   a pattern for its naming.                 objReader = ExecDataReader _                     ("sp_" & strItem & "_GetByID", Value)                 If objReader.Read() Then                     LoadDataRecord(objReader)                 Else                     Throw New Exception("Invalid ID")                 End If             Else                 ResetData()             End If             objReader.Close()         End Set     End Property     Public Sub Update()         Dim strItem As String         Dim strProc As String         strItem = GetItemDefData.Name         If ID = 0 Then             strProc = "sp_" & strItem & "_Insert"             _FieldData.Item(GetItemDefData().PrimaryKey) = _                 ExecScalar(strProc)         Else             strProc = "sp_" & strItem & "_Update"             ExecNonQuery(strProc)         End If     End Sub     Public Sub Delete()         Dim strItem = GetItemDefData.Name         ExecNonQuery("sp_" & strItem & "_Delete")     End Sub End Class <AttributeUsage(AttributeTargets.Class, _     Inherited:=True, AllowMultiple:=False)> _ Public Class ItemDefAttribute     Inherits System.Attribute     'Private fields     Private m_Name As String     Private m_PrimaryKey As String     Public Sub New _         (ByVal Name As String, ByVal PrimaryKey As String)          M_Name = Name          m_PrimaryKey = PrimaryKey     End Sub     Public ReadOnly Property Name() As String         Get             Return m_Name         End Get     End Property     Public ReadOnly Property PrimaryKey() As String         Get             Return m_PrimaryKey         End Get     End Property End Class 

There are a number of assumptions that need to be kept in mind if you use this as the base class for your data access classes. They are as follows:

  • The ItemDefAttribute must be applied to any dependant data-access classes. Its two parameters are the name of data access item in the way you want to use it in stored procedures and the primary key field name for this table.

  • Create the four mandatory stored procedures for each data table.

  • Each of the data properties in the data-access class must be named identically to the field names in your data table.

There is a mandatory stored procedure for each of the following actions: Deleting, Inserting, Updating and Retrieving a record by its primary key ID value. Following is an SQL listing of all four stored procedures for the AppointmentItem class: The stored procedures are named sp_Name_Action, where Name is the name of the data-access class as defined in the ItemDefAttribute that was applied to the class, and Action is one of Update, Insert, Delete, or GetByID.

 CREATE PROCEDURE dbo.sp_Appointment_Insert (   @UserID int,   @TimeStart datetime,   @TimeEnd datetime,   @Title varchar(500),   @Note varchar(2000) ) AS   INSERT INTO [tblAppointment]     VALUES(@UserID,@TimeStart,@TimeEnd,@Title,@Note)   SELECT Convert(int,@@IDENTITY) AS [AppointmentID]   RETURN CREATE PROCEDURE dbo.sp_Appointment_Update (   @AppointmentID int,   @UserID int,   @TimeStart datetime,   @TimeEnd datetime,   @Title varchar(500),   @Note varchar(2000) ) AS   Update [tblAppointment]   Set [UserID]=@UserID,[TimeStart]=@TimeStart,     [TimeEnd]=@TimeEnd,[Title]=@Title,[Note]=@Note   WHERE [AppointmentID]=@AppointmentID   RETURN CREATE PROCEDURE dbo.sp_Appointment_Delete (   @AppointmentID int ) AS   DELETE FROM [tblAppointment] WHERE [AppointmentID]=@AppointmentID   RETURN ALTER PROCEDURE dbo.sp_Appointment_GetByID   (     @AppointmentID int   ) AS   Select * FROM [tblAppointment] WHERE [AppointmentID]=@AppointmentID   RETURN 

Developing a data-access layer in this way requires more work when developing the initial base class, but the subsequent reuse of that class and the reduced effort in developing the rest of the data access layer make it well worthwhile.

The Business Logic Layer

The first thing many of you may wonder is why I didn't develop a business logic layer for this application. The answer is simple: I don't do any business logic-type things to my data. In this application, the data is selected and then displayed. There is nothing being processed with the data. So where might you use a business logic layer? If you were to extend the application to allow meetings, with multiple people receiving notifications, and having best time scheduling within a range of dates and times, then you would want to implement this logic in a business logic layer. At this point, you are starting to do much more then just retrieve and store data. I feel that business logic layers should not be developed just to have them, but they should be developed where there is a need for them.

Implementation Details

Rather then trying to place all the code for this application here, I will discuss in detail only the code that is related to the main topic of this chapter; namely, databound server controls. With this in mind, we will look first at the Contact.aspx page.

In the Contact.aspx page, the server controls in question are located on the page and not on a user control. The user controls on this page are only for entering and editing contact data.

Listing 5.4 Contact.aspx.vb Contact Page Code-Behind File
 Public Class Contact     Inherits System.Web.UI.Page     Public WithEvents ctlContactEdit As ContactEdit     Public WithEvents ctlContactView As ContactView #Region " Web Form Designer Generated Code "     'This call is required by the Web Form Designer.     <System.Diagnostics.DebuggerStepThrough()> Private Sub _       InitializeComponent()     End Sub     Protected WithEvents pnlMain As System.Web.UI.WebControls.Panel     Protected WithEvents Panel1 As System.Web.UI.WebControls.Panel     Protected WithEvents ctlAlphaList As _       System.Web.UI.WebControls.Repeater     Protected WithEvents ctlContactGrid As _       System.Web.UI.WebControls.DataGrid     Protected WithEvents btnAddContact As _        System.Web.UI.WebControls.LinkButton     Protected WithEvents Panel2 As System.Web.UI.WebControls.Panel     'NOTE: The following placeholder declaration is required by the Web     '  Form Designer.     'Do not delete or move it.     Private designerPlaceholderDeclaration As System.Object     Private Sub Page_Init(ByVal sender As System.Object, ByVal e As _       System.EventArgs) Handles MyBase.Init         'CODEGEN: This method call is required by the Web Form Designer         'Do not modify it using the code editor.         InitializeComponent()     End Sub #End Region     Public ctlMenuModule As MenuModule     Private m_AuthUser As UserItem     Private Sub Page_Load(ByVal sender As System.Object, _         ByVal e As System.EventArgs) Handles MyBase.Load         ctlMenuModule.CurrentMenu = "Contact"         If Not IsPostBack Then             State = ContactState.Normal         End If         Me.LoadAlphaList()         Me.LoadContactGrid()     End Sub     Public ReadOnly Property AuthUser() As UserItem         Get             If IsNothing(m_AuthUser) Then                 m_AuthUser = New UserItem                 m_AuthUser.LoadByUsername(Me.User.Identity.Name)             End If             Return m_AuthUser         End Get     End Property     Public Enum ContactState         Normal = 0         Edit = 1         View = 2     End Enum     Public Property State() As ContactState         Get             If IsNothing(ViewState("ContactState")) Then                 ViewState("ContactState") = ContactState.Normal             End If             Return CType(ViewState("ContactState"), ContactState)         End Get         Set(ByVal Value As ContactState)             ViewState("ContactState") = Value             If Value = ContactState.Normal Then                 ctlContactEdit.Visible = False                 ctlContactView.Visible = False             ElseIf Value = ContactState.Edit Then                 ctlContactEdit.Visible = True                 ctlContactView.Visible = False             ElseIf Value = ContactState.View Then                 ctlContactEdit.Visible = False                 ctlContactView.Visible = True             End If         End Set     End Property     Private Class CountItem         Private m_Name As String         Private m_Value As Integer         Public Sub New(ByVal strName As String, _             ByVal nValue As Integer)             m_Name = strName             m_Value = nValue         End Sub         Public ReadOnly Property Name()             Get                 Return m_Name             End Get         End Property         Public ReadOnly Property Value()             Get                 Return m_Value             End Get         End Property     End Class     Private Sub LoadAlphaList()         Dim objCounts() As CountItem         Dim I As Integer         Dim isValidReader As Boolean         Dim objReader As IDataReader         Dim strName As String         objCounts = Array.CreateInstance(GetType(CountItem), 27)         objCounts(0) = _             New CountItem("All - ", Page.AuthUser.ContactCount)         objReader = Page.AuthUser.ContactAlphaCountsReader         isValidReader = objReader.Read()         For I = 1 To 26             strName = Chr(Asc("A") + I - 1)             If isValidReader Then                 If objReader.Item("AlphaInx") = I Then                     objCounts(I) = New CountItem(strName, _                         objReader.Item("ContactCount"))                     isValidReader = objReader.Read()                 Else                     objCounts(I) = New CountItem(strName, 0)                 End If             Else                 objCounts(I) = New CountItem(strName, 0)             End If         Next         objReader.Close()         Me.ctlAlphaList.DataSource = objCounts         Me.ctlAlphaList.DataBind()     End Sub     Private Sub LoadContactGrid()         Dim objReader As IDataReader         If AlphaSelect = "ALL" Then             objReader = Page.AuthUser.ContactsReader         Else             objReader = Page.AuthUser.GetContactsByFirstLetter _                 (AlphaSelect)         End If         Me.ctlContactGrid.DataSource = objReader         Me.ctlContactGrid.DataBind()     End Sub     Private Property AlphaSelect() As String         Get             If IsNothing(ViewState.Item("AlphaSelect")) Then                 ViewState.Item("AlphaSelect") = "ALL"             End If             Return ViewState.Item("AlphaSelect")         End Get         Set(ByVal Value As String)             If Value.Length > 3 Then                 ViewState.Item("AlphaSelect") = _                     Value.Substring(0, 3).ToUpper()             Else                 ViewState.Item("AlphaSelect") = Value             End If             LoadAlphaList()             LoadContactGrid()         End Set     End Property     Public Shadows Property Page() As Contact         Get             Return CType(MyBase.Page, Contact)         End Get         Set(ByVal Value As Contact)             MyBase.Page = Value         End Set     End Property     Private Sub ctlAddContact_Click(ByVal sender As Object, _         ByVal e As System.EventArgs) Handles btnAddContact.Click         State = ContactState.Edit         ctlContactEdit.ContactID = 0     End Sub     Private Sub ctlContactGrid_ItemCommand( _         ByVal source As Object, _         ByVal e As DataGridCommandEventArgs) _         Handles ctlContactGrid.ItemCommand         If e.CommandName = "EditItem" Then             Me.State = ContactState.View             Me.ctlContactView.ContactID = _                 Convert.ToInt32(e.CommandArgument)             Me.ctlContactView.DataBind()         End If     End Sub     Private Sub ctlAlphaList_ItemCommand( _         ByVal source As Object, _         ByVal e As RepeaterCommandEventArgs) _         Handles ctlAlphaList.ItemCommand         If e.CommandName = "CHANGE" Then             Me.AlphaSelect = e.CommandArgument         End If     End Sub End Class 

On the Contact page, user controls are used to collect and display the detailed data about the contact. There are two user controls, ContactEdit and ContactView. ContactEdit is also used for new contacts. The visibility of these controls is set depending upon whether or not it should be displayed.

The ContactEdit user control is a good example of a self-contained editing control. Listing 5.5 details this control. The code of other similar controls will not be shown here.

Listing 5.5 ContactEdit.ascx.vb Code-Behind File for User Control to Edit Contact Data
 Public Class ContactEdit     Inherits System.Web.UI.UserControl #Region " Web Form Designer Generated Code "     'This call is required by the Web Form Designer.     <System.Diagnostics.DebuggerStepThrough()> Private Sub _       InitializeComponent()         Dim configurationAppSettings As _          System.Configuration.AppSettingsReader = _             New System.Configuration.AppSettingsReader         Me._Contact = New ScheduleVB.ContactItem     End Sub     Protected WithEvents Panel1 As System.Web.UI.WebControls.Panel     Protected WithEvents lblEdit As System.Web.UI.WebControls.Label     Protected WithEvents Label1 As System.Web.UI.WebControls.Label     Protected WithEvents Label2 As System.Web.UI.WebControls.Label     Protected WithEvents Label3 As System.Web.UI.WebControls.Label     Protected WithEvents Label4 As System.Web.UI.WebControls.Label     Protected WithEvents Label5 As System.Web.UI.WebControls.Label     Protected WithEvents Label6 As System.Web.UI.WebControls.Label     Protected WithEvents Label7 As System.Web.UI.WebControls.Label     Protected WithEvents Label8 As System.Web.UI.WebControls.Label     Protected WithEvents Panel2 As System.Web.UI.WebControls.Panel     Protected WithEvents btnUpdate As System.Web.UI.WebControls.Button     Protected WithEvents btnCancel As System.Web.UI.WebControls.Button     Protected WithEvents RequiredFieldValidator1 As _        System.Web.UI.WebControls.RequiredFieldValidator     Protected WithEvents editLastName As _        System.Web.UI.WebControls.TextBox     Protected WithEvents editFirstName As _        System.Web.UI.WebControls.TextBox     Protected WithEvents editMiddleName As _        System.Web.UI.WebControls.TextBox     Protected WithEvents editEmail As System.Web.UI.WebControls.TextBox     Protected WithEvents editWorkPhone As _         System.Web.UI.WebControls.TextBox     Protected WithEvents editHomePhone As _         System.Web.UI.WebControls.TextBox     Protected WithEvents editCellPhone As _         System.Web.UI.WebControls.TextBox     Protected WithEvents editFax As _         System.Web.UI.WebControls.TextBox     Protected WithEvents RegularExpressionValidator1 As _          System.Web.UI.WebControls.RegularExpressionValidator     Protected WithEvents RegularExpressionValidator2 As _         System.Web.UI.WebControls.RegularExpressionValidator     Protected WithEvents RegularExpressionValidator3 As _         System.Web.UI.WebControls.RegularExpressionValidator     Protected WithEvents RegularExpressionValidator4 As _         System.Web.UI.WebControls.RegularExpressionValidator     Protected WithEvents RegularExpressionValidator5 As _         System.Web.UI.WebControls.RegularExpressionValidator     'NOTE: The following placeholder declaration is required by the Web     '   Form Designer.     'Do not delete or move it.     Private designerPlaceholderDeclaration As System.Object     Private Sub Page_Init(ByVal sender As System.Object, ByVal e As _          System.EventArgs) Handles MyBase.Init         'CODEGEN: This method call is required by the Web Form Designer         'Do not modify it using the code editor.         InitializeComponent()     End Sub #End Region     Private m_ContactID As Integer     Protected m_Contact As ScheduleVB.ContactItem     Private Sub Page_Load(ByVal sender As System.Object, _         ByVal e As System.EventArgs) Handles MyBase.Load         'Put user code to initialize the page here     End Sub     Public Overloads Sub DataBind()         Me.editLastName.Text = ContactObj.LastName         Me.editMiddleName.Text = ContactObj.MiddleName         Me.editFirstName.Text = ContactObj.FirstName         Me.editEmail.Text = ContactObj.Email         Me.editWorkPhone.Text = ContactObj.WorkPhone         Me.editHomePhone.Text = ContactObj.HomePhone         Me.editCellPhone.Text = ContactObj.CellPhone         Me.editFax.Text = ContactObj.Fax     End Sub     Private Sub btnUpdate_Click(ByVal sender As System.Object, _         ByVal e As System.EventArgs) Handles btnUpdate.Click         ContactObj.LastName = Me.editLastName.Text         ContactObj.MiddleName = Me.editMiddleName.Text         ContactObj.FirstName = Me.editFirstName.Text         ContactObj.Email = Me.editEmail.Text         ContactObj.WorkPhone = Me.editWorkPhone.Text         ContactObj.HomePhone = Me.editHomePhone.Text         ContactObj.CellPhone = Me.editCellPhone.Text         ContactObj.Fax = Me.editFax.Text         ContactObj.UserID = Page.AuthUser.ID         ContactObj.Update()         Response.Redirect("Contact.aspx")     End Sub     Private Sub btnCancel_Click(ByVal sender As System.Object, _         ByVal e As System.EventArgs) Handles btnCancel.Click         Page.State = Contact.ContactState.Normal     End Sub     Public Shadows Property Page() As Contact         Get             Return CType(MyBase.Page, Contact)         End Get         Set(ByVal Value As Contact)             MyBase.Page = Value         End Set     End Property     Public Property ContactID() As Integer         Get             If IsNothing(ViewState.Item("ContactID")) Then                 ViewState.Item("ContactID") = 0             End If             Return ViewState.Item("ContactID")         End Get         Set(ByVal Value As Integer)             ViewState.Item("ContactID") = Value             If Value = 0 Then                 lblEdit.Text = "Add Contact"             Else                 lblEdit.Text = "Edit Contact"             End If         End Set     End Property     Public ReadOnly Property ContactObj() As ContactItem         Get             If IsNothing(m_Contact) Then                 m_Contact = New ContactItem             End If             If ContactID > 0 And _Contact.ID <> ContactID Then                 m_Contact.ID = ContactID             End If             m_Contact.UserID = Page.AuthUser.ID             Return m_Contact         End Get     End Property End Class 

The MonthModule user control shows how to display custom data in a calendar control. To get the calendar to look the way I wanted it to, I had to insert a div tag around the date, and then use a style sheet to position it properly. Shortened appointment titles are then displayed below the date. Listing 5.6 show the details of this user control.

Listing 5.6 MonthModule.ascx.vb Code-Behind File for User Control to Display Month View
 Public Class MonthModule     Inherits System.Web.UI.UserControl #Region " Web Form Designer Generated Code "     'This call is required by the Web Form Designer.     <System.Diagnostics.DebuggerStepThrough()> Private Sub _       InitializeComponent()     End Sub     Protected WithEvents ctlBigCalendar As _         System.Web.UI.WebControls.Calendar     Protected WithEvents Panel1 As System.Web.UI.WebControls.Panel     'NOTE: The following placeholder declaration is required by the Web     '   Form Designer.     'Do not delete or move it.     Private designerPlaceholderDeclaration As System.Object     Private Sub Page_Init(ByVal sender As System.Object, ByVal e As _          System.EventArgs) Handles MyBase.Init         'CODEGEN: This method call is required by the Web Form Designer         'Do not modify it using the code editor.         InitializeComponent()     End Sub #End Region     Private Sub Page_Load(ByVal sender As System.Object, _         ByVal e As System.EventArgs) Handles MyBase.Load         _Page = Page         Me.ctlBigCalendar.SelectedDate = Page.SelectedDate         Me.ctlBigCalendar.VisibleDate = Page.SelectedDate     End Sub     Private Sub ctlBigCalendar_DayRender(ByVal sender As Object, _         ByVal e As System.Web.UI.WebControls.DayRenderEventArgs) _         Handles ctlBigCalendar.DayRender         Dim objReader As IDataReader         Dim objAppointment As New AppointmentItem         Dim I As Integer         e.Cell.Controls.AddAt(0, _             New LiteralControl("<div class='CalendarDate'>"))         e.Cell.Controls.Add(New LiteralControl("</div>"))         I = 0         objReader = Me.Page.AuthUser.GetAppointmentsByDate(e.Day.Date)         While (objReader.Read())             Dim objLabel As New LinkButton             Dim strTitle As String             If I > 0 Then                 e.Cell.Controls.Add(New LiteralControl("<br>"))             End If             objAppointment.LoadDataRecord(objReader)             strTitle = objAppointment.Title             If strTitle.Length > 13 Then                 strTitle = strTitle.Substring(0, 10) & "..."             End If             objLabel.Text = strTitle             objLabel.ToolTip = _                 objAppointment.TimeStart.ToShortTimeString() & _                 "~" & _                 objAppointment.TimeEnd.ToShortTimeString() & _                 ": " & _                 objAppointment.Title             e.Cell.Controls.Add(objLabel)             I = I + 1         End While         objReader.Close()     End Sub     Private WithEvents _Page As New Schedule     Public Shadows Property Page() As Schedule         Get             Return CType(MyBase.Page, Schedule)         End Get         Set(ByVal Value As Schedule)             MyBase.Page = Value         End Set     End Property     Private Sub _Page_SelectedDateChanged(ByVal sender As Object, _         ByVal e As System.EventArgs) _         Handles _Page.SelectedDateChanged         Me.ctlBigCalendar.SelectedDate = Page.SelectedDate         Me.ctlBigCalendar.VisibleDate = Page.SelectedDate     End Sub     Private Sub ctlBigCalendar_SelectionChanged(ByVal sender As Object, _         ByVal e As System.EventArgs) _         Handles ctlBigCalendar.SelectionChanged         Page.SelectedDate = ctlBigCalendar.SelectedDate     End Sub End Class 

The DayModule user control gives another example of how a Repeater server control can be used. Listing 5.7 shows the details of this module.

Listing 5.7 DayModule.ascx.vb Code-Behind File for User Control to Display Day View
 Imports System.Collections.Specialized Public Class DayModule     Inherits System.Web.UI.UserControl #Region " Web Form Designer Generated Code "     'This call is required by the Web Form Designer.     <System.Diagnostics.DebuggerStepThrough()> Private Sub _         InitializeComponent()     End Sub     Protected WithEvents Panel1 As System.Web.UI.WebControls.Panel     Protected WithEvents ctlDay As System.Web.UI.WebControls.Repeater     Protected WithEvents Time As System.Web.UI.WebControls.Label     'NOTE: The following placeholder declaration is required by the Web     '   Form Designer.     'Do not delete or move it.     Private designerPlaceholderDeclaration As System.Object     Private Sub Page_Init(ByVal sender As System.Object, ByVal e As _          System.EventArgs) Handles MyBase.Init         'CODEGEN: This method call is required by the Web Form Designer         'Do not modify it using the code editor.         InitializeComponent()     End Sub #End Region     Private m_StartTime As DateTime     Private m_EndTime As DateTime     Private m_Increment As TimeSpan     Private m_ItemCount As Integer     Private m_Appointments As IDataReader     Private m_HasAppointment As Boolean     Private m_NextAppointment As AppointmentItem     Protected WithEvents m_Page As Schedule     Private Sub Page_Load(ByVal sender As System.Object, _         ByVal e As System.EventArgs) Handles MyBase.Load         m_StartTime = New DateTime(1, 1, 1, 8, 0, 0)         m_EndTime = New DateTime(1, 1, 1, 18, 0, 0)         m_Increment = New TimeSpan(0, 30, 0)         m_ItemCount = 20         m_Page = Page         LoadData()     End Sub     Public Shadows Property Page() As Schedule         Get             Return CType(MyBase.Page, Schedule)         End Get         Set(ByVal Value As Schedule)             MyBase.Page = Value         End Set     End Property     Public Sub LoadNextAppointment()         If IsNothing(m_Appointments) Then             m_Appointments = m_Page.AuthUser. _                 GetAppointmentsByDate(m_Page.SelectedDate)         End If         If Not m_Appointments.IsClosed Then             m_HasAppointment = m_Appointments.Read()             If m_HasAppointment Then                 m_NextAppointment = New AppointmentItem                 m_NextAppointment.LoadDataRecord(m_Appointments)             Else                 m_Appointments.Close()             End If         End If     End Sub     Public Sub LoadData()         Dim dtCurTime As System.DateTime         Dim strTimeList As New StringCollection         Dim I As Integer         m_Appointments = Nothing         LoadNextAppointment()         dtCurTime = m_StartTime         I = 0         While (dtCurTime < m_EndTime)             strTimeList.Add(dtCurTime.ToShortTimeString())             dtCurTime = dtCurTime.Add(m_Increment)             I = I + 1         End While         Me.ctlDay.DataSource = strTimeList         Me.ctlDay.DataBind()         If Not m_Appointments.IsClosed Then             m_Appointments.Close()         End If     End Sub     Private Sub ctlDay_ItemDataBound(ByVal sender As Object, _         ByVal e As RepeaterItemEventArgs) _         Handles ctlDay.ItemDataBound         Dim objAppointments As Panel         Dim objItem As RepeaterItem         Dim dtCurrent As New DateTime         Dim nAptCount As Integer         objItem = e.Item         If (objItem.ItemType = ListItemType.Item) Or _             (objItem.ItemType = ListItemType.AlternatingItem) _             Then             dtCurrent = _DateTime.Parse(_Page.SelectedDate. _                 ToShortDateString() & " " & objItem.DataItem)             objAppointments = _                 CType(objItem.FindControl("pnlAppointments"), _                 Panel)             If _HasAppointment Then                 nAptCount = 0                 While (_NextAppointment.TimeStart < _                     dtCurrent.Add(Me._Increment) And _                     m_HasAppointment)                     Dim objLink As New LinkButton                     objLink.Text = m_NextAppointment.TimeStart. _                         ToShortTimeString() & " - " & _                         m_NextAppointment.TimeEnd. _                         ToShortTimeString() & ": " & _                         m_NextAppointment.Title                     objLink.ToolTip = m_NextAppointment.Note                     objLink.CommandName = "EditAppointment"                     objLink.CommandArgument = _                         m_NextAppointment.ID.ToString()                     AddHandler objLink.Command, _                         AddressOf ctlDay_ItemCommand                     If nAptCount > 0 Then                         objAppointments.Controls.Add( _                             New LiteralControl("<br>"))                     End If                     objAppointments.Controls.Add(objLink)                     LoadNextAppointment()                     nAptCount = nAptCount + 1                 End While             End If             If nAptCount = 0 Then                 objAppointments.Controls.Add( _                     New LiteralControl("&nbsp;"))             End If         End If     End Sub     Private Sub ctlDay_ItemCommand(ByVal source As Object, _         ByVal e As System.Web.UI.WebControls.CommandEventArgs)         If e.CommandName = "EditAppointment" Then             Page.ctlEditModule.AppointmentID = _                 Convert.ToInt32(e.CommandArgument)         End If     End Sub     Private Sub _Page_SelectedDateChanged(ByVal sender As Object, _         ByVal e As System.EventArgs) _         Handles _Page.SelectedDateChanged         LoadData()     End Sub End Class 


ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ISBN: 321159659
EAN: N/A
Year: 2003
Pages: 175

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