Now that you've learned how to build and use a simple business object, let's use one to build something more useful. Many Web sites have areas that only registered users can access, or that offer personalized content for those users. In these Web sites, it may be useful to encapsulate the complexity of representing a registered user. All functions associated with a user, such as logging them on and off, updating their profile information, and so on, can be easily packaged into a business object.
In essence, this object will represent a generic user, and it should contain methods and properties that completely describe that user. It should also contain methods to perform actions on users, such as adding new users, deleting old ones, and validating. Specifically, you want the following properties:
An object to represent a user's identity, including her full name, username, ID, and so on.
Methods to add, remove, and update users.
Methods to validate users.
This functionality will be implemented as two separate classes. One will be used to represent all the user's details, and another will perform the methods described previously. This allows you to treat the user's details as a group a separate entity that you can manipulate. Figure 15.5 depicts the relationship between your two objects.
Figure 15.5. The UserDetail object will hold details, while the User object will hold functionality.
You'll be using the user database you created on Day 8, "Beginning to Build Databases." The beginning of your file is shown in Listing 15.7.
Listing 15.7 The UserDetails Class
1: Imports System 2: Imports System.Data 3: Imports System.Data.OleDb 4: 5: Namespace TYASPNET 6: 7: Public Class UserDetails 8: public FirstName as string 9: public LastName as string 10: public UserName as string 11: public Password as string 12: public UserID as string 13: End Class
| || |
Save this file as user.vb. This file starts out the same way as the previous object. You import the necessary namespaces (lines 1 through 3) and declare your namespace (line 5). Your UserDetails class, beginning on line 7, contains all the properties that you'll need to represent your user. (These aren't all the properties in the database. Some have been left out for simplicity's sake. You can add more if you want.)
Note that if you are building this object using C#, you'll need to initialize these public properties to empty strings:
public string FirstName = ""; public string LastName = ""; ...
This is because we'll be comparing these string values later on, and if they are not initialized, the comparison won't work properly.
Next you have your User class, which contains the functions you need. This class is shown in Listing 15.8.
Listing 15.8 The User Class
14: Public Class User 15: public function Login(UserName as string, Password as _ 16: string) as string 17: dim intId as string = "0" 18: dim Conn as new OleDbConnection("Provider=" & _ 19: "Microsoft.Jet.OLEDB.4.0;" & _ 20: "Data Source=c:\ASPNET\data\banking.mdb") 21: 22: dim objCmd as OleDbCommand = new OleDbCommand _ 23: ("SELECT UserID FROM tblUsers WHERE " & _ 24: "UserName = '" & UserName & "' AND " & _ 25: "Password = '" & Password & "'", Conn) 26: dim objReader as OleDbDataReader 27: 28: try 29: objCmd.Connection.Open() 30: objReader = objCmd.ExecuteReader 31: 32: do while objReader.Read 33: intId = objReader.GetInt32(0).ToString 34: loop 35: catch ex as OleDbException 36: throw ex 37: end try 38: 39: return intID 40: end function 41: 42: public function GetDetails(UserID as integer) as _ 43: UserDetails 44: dim Conn as new OleDbConnection("Provider=" & _ 45: "Microsoft.Jet.OLEDB.4.0;" & _ 46: "Data Source=c:\ASPNET\data\banking.mdb") 47: 48: dim objCmd as OleDbCommand = new OleDbCommand _ 49: ("SELECT FirstName, LastName, UserName, " & _ 50: "Password FROM tblUsers WHERE UserID = " & _ 51: UserID, Conn) 52: dim objReader as OleDbDataReader 53: 54: try 55: objCmd.Connection.Open() 56: objReader = objCmd.ExecuteReader 57: catch ex as OleDbException 58: throw ex 59: end try 60: 61: dim objDetails as new UserDetails 62: 63: while objReader.Read() 64: objDetails.FirstName = objReader.GetString(0) 65: objDetails.LastName = objReader.GetString(1) 66: objDetails.UserName = objReader.GetString(2) 67: objDetails.Password = objReader.GetString(3) 68: objDetails.UserID = UserID.ToString 69: end while 70: objReader.Close 71: 72: return objDetails 73: end function 74: 75: public function Update(objDetails as UserDetails, _ 76: intUserID as integer) as boolean 77: dim objOldDetails as new UserDetails 78: objOldDetails = GetDetails(intUserID) 79: 80: with objDetails 81: if .FirstName = "" then 82: .FirstName = objOldDetails.FirstName 83: end if 84: if .LastName = "" then 85: .LastName = objOldDetails.LastName 86: end if 87: if .Username = "" then 88: .UserName = objOldDetails.UserName 89: end if 90: if .Password = "" then 91: .Password = objOldDetails.Password 92: end if 93: end with 94: 95: dim Conn as new OleDbConnection("Provider=" & _ 96: "Microsoft.Jet.OLEDB.4.0;" & _ 97: "Data Source=c:\ASPNET\data\banking.mdb") 98: 99: dim strSQL as string = "UPDATE tblUsers SET " & _ 100: "FirstName = '" & objDetails.FirstName & _ 101: "', " & _ 102: "LastName = '" & objDetails.LastName & "', " & _ 103: "UserName = '" & objDetails.UserName & "', " & _ 104: "[Password] = '" & objDetails.Password & _ 105: "' " & "WHERE UserID = " & intUserID 106: dim objCmd as OleDbCommand = new OleDbCommand _ 107: (strSQL, Conn) 108: 110: objCmd.Connection.Open() 111: objCmd.ExecuteNonQuery 112: catch ex as OleDbException 113: throw ex 114: finally 115: objCmd.Connection.Close 116: end try 117: 118: return true 119: end function 120: End Class 121: 122: End Namespace
| || |
That's quite a large class, and you haven't even added all the methods yet. Luckily, it should be easy for you to understand and you can move through it quickly.
The Login function on line 15 takes a username and password and verifies it against the database. If they represent a valid user, you return the corresponding UserID.
The GetDetails function on line 42 takes a UserID value and retrieves the values from the database. On line 61, you create a new instance of the UserDetails class defined earlier. You set its properties to the values returned from the database and return that UserDetails object to the user. By storing all the values in this UserDetails object, you're providing an easy mechanism for the user to access the values.
The Update function updates a row in the user database. It takes a UserDetails object with the updated values for each property. What if the user only needs to change one value, though? You don't want to require the user to specify all the values if most of them haven't changed. Therefore, you allow the user to specify only the values that are changed in the UserDetails object, and this function automatically fills the others with the original values. This functionality is implemented on lines 77 93.
The objOldDetails variable on line 77 holds the original user property values, which are retrieved from the database using the GetDetails function. The subsequent series of if statements checks for the user detail values that haven't been specified by the user and sets them to the original values. Now you can build a SQL statement using the UserDetails object without worrying that you'll lose original data.
The With object keyword on line 80 means that all subsequent variables preceded by a period belong to object. For instance:
objDetails.FirstName = "" objDetials.LastName = ""
That's the same as this:
With objDetails .FirstName = "" .LastName = "" end with
The latter method saves you some typing when there are a lot of properties for the object.
The with keyword does not exist in C#, so you're stuck with using the long version.
You build your update SQL statement on line 99 and execute your query in the try block. If the update was successful, you return true.
Save this file as User.vb, navigate to the appropriate directory, and compile it with the following command:
vbc /t:library /out:..\bin\User.dll /r:System.dll /r:System.Data.Dll User.vb
You can compile multiple files into a single DLL if you like. For example:
vbc /t:library /out:..\bin\TYASPNET.dll /r:System.dll /r:System.Data.Dll User.vb Database.vb
This compiles both the user objects and the database object you created earlier into one file. This is easier to keep track of than multiple DLLs. In fact, instead of specifying each filename that you want to add into the single DLL, you can use wildcards. For example, the following line concatenates all files that end in .vb in the current directory into a single DLL:
vbc /t:library /out:..\bin\TYASPNET.dll /r:System.dll /r:System.Data.Dll *.vb
Just be sure that you want all such files in the current directory to be compiled together.
Listing 15.9 shows a simple ASP.NET page that uses these objects (UserDetails and User).
Listing 15.9 Using Your User Objects
1: <%@ Page Language="VB" %> 2: 3: <script runat="server"> 4: sub Page_Load(Sender as Object, e as EventArgs) 5: if Not Page.IsPostBack then 6: dim objUser as new TYASPNET.User 7: dim objDetails as new TYASPNET.UserDetails 8: 9: objDetails = objUser.GetDetails(1) 10: lblMessage.Text = "Hello " & _ 11: objDetails.FirstName & "!" 12: end if 13: 14: end sub 15: 16: sub Update(Sender as Object, e as EventArgs) 17: dim objUser as new TYASPNET.User 18: dim objDetails as new TYASPNET.UserDetails 19: 20: objDetails.FirstName = tbName.Text 21: if objUser.Update(objDetails, 1) then 22: objDetails = objUser.GetDetails(1) 23: lblMessage.Text = "Hello " & _ 24: objDetails.FirstName & "!" 25: else 26: lblMessage.Text = "Update failed!" 27: end if 28: end sub 29: </script> 30: 31: <html><body> 32: <form runat="server"> 33: <asp:Label runat="server" /><p> 34: Change your name?<br> 35: <asp:Textbox runat="server"/><br> 36: <asp:Button runat="server" 37: OnClick="Update" Text="Submit" /> 38: </form> 39: </body></html>
When the user first comes to this page, she will see a welcome message with the UserID 1. In the Page Load event, you instantiate your two new objects and use the GetDetails method to return a UserDetails object. You then display a simple welcome message.
When the user enters a new name in the box and clicks Submit, the Update method takes control. It sets the name property of a new UserDetails object to the new value and calls the User.Update method. If the update is successful, this method returns true and displays a welcome message with the new name. Figure 15.6 shows the output after entering a new name.
Figure 15.6. Your user objects manipulate user information with only a few lines of code in the ASP.NET page.
Your ASP.NET page is now very short. The equivalent page without the business objects would be much longer. Now, it only contains code that interfaces directly with the UI. Likewise, any other page that needs to implement this functionality is also spared the trouble of dealing with the database objects directly.
Imagine if you needed to add another function to the User object, or if the database structure changed. If you had implemented these individual functions on every ASP.NET page that required it, you'd have to make a lot of changes to reflect the database change. With the User object, you can just make one change in the .vb file and recompile. You're off and running again without having to dig through all the ASP.NET pages involved.
Another benefit of business objects is that they can be used on other Web servers. You could sell this User object for developers to use on their own sites. (Of course, you may want to add more functionality first.)
Instead of creating all the database commands and objects from scratch in the User class, you could just as easily use the Database class you created earlier today in "Developing Business Objects." When you compile the User objects, you have to modify the command as follows:
vbc /t:library /out:..\bin\User.dll /r:System.dll /r:System.Data.Dll /r:TYASPNET.dll User.vb
Specifically, you need to add a reference to your database object in TYASPNET.dll.
A Few Considerations
Notice that you hard-coded a few values into your user.vb file, such as the connection string. This type of variable doesn't belong in the business object, but rather should be placed in a separate configuration file, such as web.config (we'll get to that in Day 18, "Configuring and Deploying ASP.NET Applications"). The business object could easily retrieve this string using the GetConfig method of the HttpContext object (see Day 18). This promotes a much more flexible application you don't have to recompile every time the database changes.
Just because the business object shouldn't be dependent on which database you use, that doesn't mean the object should have no awareness of the database. Placing SQL statements that are specific to a particular database is fine, and it's done that way very often. A business object should depend on a format for the database, but not the actual location of it.
Of course, all SQL statements can be moved into stored procedures as well. The object can then pass parameters to the stored procedures. As long as the names of the procedures stay the same, you can easily add more functionality to them without having to modify the source code of the business objects and recompiling.
When you develop business objects, always try to think of the logical separations of functionality. The previous objects represented a user. They shouldn't contain any code that displays data to the user or any SQL statements that return data. They should only contain user-specific methods and properties. This will help you when designing your applications.
Also, don't try to stuff too much functionality into one object. For instance, you saw that the user information is easily and logically separated from the user methods. Rather than having one object perform all the functionality, you can have two that can rely on each other.