Creating the User-Centric Business Objects


Now that you have built the data-centric objects, you actually have something to call remotely. Before you add the user-centric object, though, you need a place to start putting your application constants.

The AppConstants Module

Add another class module to the NorthwindUC project and call it AppConstants.vb. Add the following code to the module:

 Option Explicit On Option Strict On Public Class AppConstants     Public Shared REMOTEOBJECTS As String = "http://localhost:80/Northwind/"   End Class 

The REMOTEOBJECTS variable holds the path to your remote objects. If you are using another computer to host the remote objects, you will need to replace localhost with the correct computer name. Also, if for some reason your IIS server is using a port other than 80, you will need to change the port number as well. Notice that you declare it as a shared variable so that you never have to instantiate this class—you just need to reference it to get at the value of the variable.

Note

Shared variables and methods are new to VB with the introduction of VB .NET. A shared method is one that can be called without instantiating the object (as with the AppConstants class you created earlier). A shared variable is a variable that exists across all instances of the object in which it is declared. It also does not require the class that it is declared in to be instantiated. The .NET Framework guarantees there will only ever be one instance of a shared variable or method during an application's lifetime.

Next, add the class shown in Listing 3-10 to the AppConstants module.

Listing 3-10: The ChangedEventArgs Class

start example
 Public Class ChangedEventArgs     Public Enum eChange         Added = 0         Updated = 1     End Enum     Private meChange As eChange     Public Sub New(ByVal ChangeEvent As eChange)         meChange = ChangeEvent     End Sub     Public ReadOnly Property Change() As eChange         Get             Return meChange         End Get     End Property End Class 
end example

You will use ChangedEventArgs for all of your objects to inform the presentation layer that a change has been made to the object and the type of change it is. This allows you to provide a generic method to respond to all of your object's events without being specific to a particular object. This is much the same way that .NET can respond to different objects using the same method—your method signatures will match from class to class. Also, this follows the .NET convention of using an object and a set of event arguments in another object as the signature for any actions that can occur on an object.

Note

You can see an example of this by looking at the Click event of the button object. The Click event signature looks like the following: ByVal sender As System.Object, ByVal e As System.EventArgs. And remarkably enough, the BackColorChanged event signature of a form looks like this: ByVal sender As System.Object, ByVal e As System.EventArgs. They are identical, but the EventArgs contains different information. This is how your ChangedEventArgs class will operate.

The Region Class

The user-centric object is going to start as a duplicate of the data-centric object. You can copy a lot of code from one to the other.

In the NorthwindUC project, rename the Class1.vb code module to Region.vb. Next, add (or copy from the RegionDC.vb file) the code in Listing 3-11 into the Region code module.

Listing 3-11: The User-Centric Region Code Module

start example
 Option Strict On Option Explicit On Imports NorthwindTraders.NorthwindShared.Structures Imports NorthwindTraders.NorthwindShared.Interfaces Public Class Region #Region " Private Attributes"     Private mintRegionID As Integer = 0     Private mstrRegionDescription As String = "" #End Region #Region " Public Attributes"     Public ReadOnly Property RegionID() As Integer         Get             Return mintRegionID         End Get     End Property     Public Property RegionDescription() As String         Get             Return mstrRegionDescription         End Get         Set(ByVal Value As String)             mstrRegionDescription = Value         End Set     End Property #End Region End Class 
end example

There are a couple of changes in Listing 3-11, but they are all minor. The initialization of the private variables now reads as follows:

 Private mintRegionID As Integer = 0 Private mstrRegionDescription As String = "" 

This sets the mintRegionID variable to zero and the region description to an empty string when the class is instantiated.

Also, you do not need to import the sqlClient or the Configuration namespace or implement the IRegion interface. Finally, the Region class does not inherit from the MarshalByRef object because it will not be called from another application domain.

Now, add the following code to the beginning of the class:

 Private mblnDirty As Boolean = False Public Loading As Boolean Private Const LISTENER As String = "RegionDC.rem" Private msRegion As structRegion Public Event ObjectChanged(ByVal sender As Object, _     ByVal e As ChangedEventArgs) 

The listener constant simply makes it easy for you to change the remote object to which you are connecting. This value will be used in several places in the class. The msRegion variable holds the original values that were returned to you from the database. This handles object state if the user presses the Cancel button after they have made changes. The ObjectChanged event will inform the presentation layer that a change has been made to your object—either that it has been added to the database or that the record has been updated. You can expand upon this later if you want.

Now add the following code at the bottom of the class:

 Public ReadOnly Property IsDirty() As Boolean     Get         Return mblnDirty     End Get End Property 

The IsDirty property determines whether the class has been edited. The Loading property signals the class that it is being loaded and that it should not mark itself as having been edited. You have not implemented the Loading property as a standard property; rather, it is just a public variable. You do not need to know when the value of the variable changes, so that would just add extra overhead.

Next, you need to implement the Loading property in the Region class. Edit the RegionDescription property so that it looks like the following:

 Public Property RegionDescription() As String      Get           Return mstrRegionDescription      End Get      Set(ByVal Value As String)           If mstrRegionDescription.trim <> Value Then                If Not Loading Then                     mblnDirty = True                End If                mstrRegionDescription = Value           End If      End Set End Property 

All this code does is check to see if the new value is equal to the current value. If it is not, the code checks to see if you are loading. If you are loading, then the dirty flag will not be set; otherwise, the class will be marked as dirty.

Constructors

Now you need to create a couple of constructors for your class. Constructors are new to VB with the introduction of .NET. They allow you to pass parameters to an object at instantiation time. All objects in VB .NET have a constructor with them by default even if they are not visible in code.

The default constructor is a Sub New method with no code. It is not displayed in the class because it is assumed. If you create a constructor that takes arguments, the default constructor will no longer exist. If you need to have the default constructor after you have added a constructor that takes arguments, you must add the constructor with no arguments and no code.

Note

If you do add a constructor with no arguments after you have added a constructor that takes arguments, you must be aware that a developer can instantiate your class and bypass any arguments you might be expecting.

Typically, you will not need to create your own constructor unless you have some specific setup that needs to occur when your class is instantiated. Here, you need to create a constructor because of the RegionID property. The property is read-only because you do not want it to be set by any outside calls once you have instantiated the object. So, by including it as a parameter to the constructor, you can set it, and it will not be changeable after that point.

You need to create two versions of your constructor. To do this, enter the following code in the Region class:

 Public Sub New()      'No code necessary right now End Sub Public Sub New(ByVal intID As Integer)      mintRegionID = intID End Sub 

The first constructor, which takes no arguments, is the default constructor. You need this constructor so that your interface can create a new instance of this object to be able to add a new Region. You need the second constructor so that your RegionMgr class (which you will create next) can load Region objects that contain their Region IDs.

In VB .NET, constructors always have the name New—no matter what class the constructor is a part of.

Note

C# and other languages use the convention that the constructor has the same name as the class.

Overriding the ToString Method

You need to take care of one more basic method: the ToString method. The ToString method is a member of the Object class. If it is not overridden, it returns the fully qualified type of the object. You will override it for the purpose of filling a combo box. This might sound a little strange, but .NET's method of filling list and combo boxes is slightly different (OK, it is radically different) from VB 6. Add the method as follows:

 Public Overrides Function ToString() As String      Return mstrRegionDescription End Function 

This method is straightforward; it simply returns a string—any string you want it to return. When you add an object to a list control (most list controls), it will display the value returned by the ToString method. You will see exactly how this works when you start adding objects to list and combo boxes.

The LoadRecord Method

It is time to add the main methods that your class will implement. The methods you need to add are the Load, Delete, and Save methods because a class should be self-contained, or encapsulated. Although your Region manager will be able to load all of the objects contained within it, as you will see, when you get to larger objects it will not be able to load the entire object. It will only be able to load what is needed for a list or combo box. So, in this case, the Load method is not as important (although you will see that it does still play a part in helping the object maintain a consistent state).

Add the code in Listing 3-12 to the Region class.

Listing 3-12: The LoadRecord Method of the Region Class

start example
 Public Sub LoadRecord()      Dim objIRegion As IRegion      objIRegion = CType(Activator.GetObject(GetType(IRegion), _ AppConstants.REMOTEOBJECTS & LISTENER), IRegion)      msRegion = objIRegion.LoadRecord(mintRegionID)      objIRegion = Nothing      LoadObject() End Sub Private Sub LoadObject()      With msRegion           Me.mintRegionID = .RegionID           Me.mstrRegionDescription = .RegionDescription      End With End Sub 
end example

You start by declaring a variable of type IRegion. The most important line of Listing 3-12 is the Activator.GetObject line, which retrieves your reference to the remote object. Without it, there is no remote communication. (How this works and what it does exactly are topics for a complete book, so I will not go into it here. However, I recommend further reading about it if you use this method to communicate remotely.)

The first thing you pass to this call is the type of object you are going to be getting—in this case, an object that implements the IRegion interface. Next, you pass it the objectUri, which is simply the Uniform Resource Locator (URL) to your object and is contained in the REMOTEOBJECTS shared variable. When using Option Strict, you must convert the entire return value to the correct object type by using the CType statement. At this point you are able to make calls on the methods of the interface. Even if your remote object had other public methods (if you will remember, there are a couple of public properties that exist for the RegionDC class), you would not be able to call them because they were not implemented as part of the interface and you have not actually obtained a reference to your object. What you have done is obtained a reference to an object that you know nothing about except that it implements a specific interface that you know all about! That is why you are limited to making calls on the interface implementation only.

Finally, you load up the object from the structure that is returned. Note that the structure is a module-level variable, and you leave the values in it after you are done with it. You will see why in a little bit.

The Delete Method

Next, add the following code for the Delete method:

 Public Sub Delete()      Dim objIRegion As IRegion      objIRegion = CType(Activator.GetObject(GetType(IRegion), _  AppConstants.REMOTEOBJECTS & LISTENER), IRegion)      objIRegion.Delete(mintRegionID)      objIRegion = Nothing End Sub 

As with the previous method, this method just gets a reference to the remote object and makes a method call on the interface. You are not passing in the ID of the record to delete. The object is acting on itself and needs no outside intervention. This is another example of the encapsulation that you are trying to achieve.

The Save Method

This method is a little more involved (and this method will always be more involved than every other method in an object in a business application). Add the code shown in Listing 3-13 to the Region class.

Listing 3-13: The Save Method

start example
 Public Sub Save()      'Only save the object if it has been edited      If mblnDirty = True Then           Dim objIRegion As IRegion           Dim intID As Integer           Dim sRegion As structRegion           'Store the original ID of the object           intID = mintRegionID           'Assign the values of the object to a structure of           'type structRegion           With sRegion                .RegionID = mintRegionID                .RegionDescription = mstrRegionDescription           End With          'Obtain a reference to the remote object          objIRegion = CType(Activator.GetObject(GetType(IRegion), _          AppConstants.REMOTEOBJECTS & LISTENER), IRegion)          'Save the object          objIRegion.Save(sRegion, mintRegionID)          objIRegion = Nothing          'Check the original ID and see if it was a new object          If intID = 0 Then                'If it was, raise the event as an added                RaiseEvent ObjectChanged(Me, New _                ChangedEventArgs(ChangedEventArgs.eChange.Added))          Else                'If it was not, raise the event as an update                RaiseEvent ObjectChanged(Me, New _                ChangedEventArgs(ChangedEventArgs.eChange.Updated))          End If          'The object is no longer dirty          mblnDirty = False      End If End Sub 
end example

Because this code is fully commented, let's only touch on one piece of code:

 intID = mintRegionID 

The comment with this line of code says, "Store the original ID of the object." But why? When you instantiate the object, if you call the default constructor, mintRegionID is zero. This tells the object that it is new and has not come from the database. So, if it is new, once you call the Save method of the interface, the value of mintRegionID will change. The Save method of the IRegion interface takes an ID ByRef. This means that the value will change automatically. So, after the Save method has been called, you have no way to determine if the object is new. Storing the state before the call allows you to determine this.

Finally, you raise the appropriate event to the calling code (in this case, the user interface) to tell the code that a change has been made. This may not make sense at this point because you are initiating the call in the user interface so you know if it is new, right? Well, it turns out that the form that makes this call is not the form that receives this event. This is an easy way for you to pass information between forms but let the objects handle the details.

Note

It is good object-oriented practice to make sure an object is as self-aware as possible and can communicate information about itself. That means it has the ability to inform other objects when its state changes. In some cases (as you will see in the next chapter), you need helper classes to accomplish this.

Later on, you will be adding additional events to the class that will make your object more robust.




Building Client/Server Applications with VB. NET(c) An Example-Driven Approach
Building Client/Server Applications Under VB .NET: An Example-Driven Approach
ISBN: 1590590708
EAN: 2147483647
Year: 2005
Pages: 148
Authors: Jeff Levinson

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