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 OverviewBefore 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 DesignWhen 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:
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 ControlsUser 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 PageThis is the starting page for the application. It is where the user logs in or registers. It uses the following user controls:
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 PageThis 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:
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 PageThis 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:
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 LayerNow 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:
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.vbImports 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 ClassThe 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.
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.vbImports 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:
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 LayerThe 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 DetailsRather 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 FilePublic 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 DataPublic 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 ViewPublic 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 ViewImports 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(" ")) 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 |