Adding Business Rules to the User-Centric Object


When you add business rules to the user-centric objects, you expect to have these rule violations reported to the user interface immediately. For that reason, the only rules you should place here are the database constraint rules. The reason for this is that the real business rules can be quite involved and may necessitate some checks against the database or even other systems. Also, changes to the database structure are rare, but changes to the business rules can occur on a daily or weekly basis. Therefore, it is best to place the business rules in only one place.

Note

Placing database constraint business rules in the user-centric objects is optional, but if you have a lot of users, this helps cut down on the server load by only allowing calls to be made when the data is at least within the database constraint limits. Also, these rule violations will be returned to the user when they leave the current field in which they are working.

Using the BrokenRules Class

In the best situation, an object should be self-aware and should be able to report information about itself to other objects that consume it. A well-known author named Rockford Lhotka wrote a book called Visual Basic 6.0 Business Objects (Wrox Press, 1998) in which he builds just such an object. In this book he creates a class called the BrokenRules class. I read this book many years ago, and it became the book I always used as a reference. This section presents an updated version of the BrokenRules class, which behaves in a slightly different way but with the same purpose.

Add a new class to the NorthwindUC project and call it BrokenRules. This class is a specialized collection class that inherits from the DictionaryBase class. This class keeps track of the rules that have been broken in a user-centric object. Enter the code in Listing 5-3 in the BrokenRules module.

Listing 5-3: The BrokenRules Class

start example
 Option Strict On Option Explicit On Public Class BrokenRules     Inherits System.Collections.DictionaryBase     Public Event RuleBroken(ByVal IsBroken As Boolean)     Public Sub BrokenRule(ByVal strProperty As String, ByVal blnBroken As _ Boolean)         Try           If blnBroken Then                dictionary.Add(strProperty, blnBroken)           Else                dictionary.Remove(strProperty)           End If         Catch           'Do Nothing         Finally             If dictionary.Count > 0 Then                 RaiseEvent RuleBroken(True)             Else                 RaiseEvent RuleBroken(False)             End If         End Try     End Sub     Public Sub ClearRules()         dictionary.Clear()     End Sub End Class 
end example

This class works as follows:

  1. The class is instantiated when the object that it is checking the rules for is instantiated.

  2. When a property is set, if it breaks a rule, the BrokenRule method is called with a value of True.

  3. When a property is set, if it does not break a rule, the BrokenRule method is called with a value of False.

  4. If the rule had previously been broken, but it is no longer broken, then that value will be cleared from the dictionary object.

  5. If the rule had never been broken, it is passed with a value of False and the code tries to remove the value from the dictionary. Because the value was not in the dictionary, an exception will be thrown. This is the exception you catch and ignore.

  6. Finally, you check the count of the broken rules and raise the RuleBroken event with the appropriate flag.

    Tip

    The Catch block is included because if no Catch block is available to handle the error, even though the Finally block runs, the exception is not cleared from memory. To try this, when you have completed the coding in this chapter, comment out the Catch block and break a rule in the user interface to see what happens.

The ClearRules routine simply clears all of the rules from the dictionary object. You use this because every time you go back to the database to load an individual record, you want to reset any rules that have been previously broken. You will see this when you implement the BrokenRules class in the next section.

Implementing The BrokenRules Class

Now you need to implement the BrokenRules class in your Region class. The first thing you need to do is to declare the BrokenRules class using the WithEvents keyword so you can receive the message from the RuleBroken event. Add the following module-level declaration in the Region class:

 Private WithEvents mobjRules As BrokenRules 

Now you need to create an event in the Region class so that you can pass on this message to the user interface. To do this, create the following event:

 Public Event BrokenRule(ByVal IsBroken As Boolean) 

Now you need to respond to the RuleBroken event and pass this information on to the user interface. Add the following code to take care of this:

 Private Sub mobjRules_RuleBroken(ByVal IsBroken As Boolean) _ Handles mobjRules.RuleBroken      RaiseEvent BrokenRule(IsBroken) End Sub 

This code simply passes the value that was given to the Region object up to the user interface. The last thing you need to do is to instantiate the mobjRules variable. Alter both of the constructors so that they look like the following:

 Public Sub New()      mobjRules = New BrokenRules()      mobjRules.BrokenRule("Region Description", True) End Sub Public Sub New(ByVal intID As Integer)      mobjRules = New BrokenRules()      mintRegionID = intID End Sub 

Notice that in the Sub New() constructor, you broke the Region Description rule. That is a new object and you do not know what will go there, but you do know that it requires at least one property that needs to be set, so on instantiation you mark the RegionDescription property as invalid.

Note

All properties that do not have default values (discussed later) should be broken. The logic behind this is the following: You do not want an object to be marked as valid until all of the values are set according to the rules. By breaking the rule to begin with, you ensure the object will not be valid until the property is set to a valid value.

However, for the constructor being called for an existing object that you are loading from the database, you can assume the rules have not been violated.

Note

There may be some controversy over this. If your application was used to put things into the database, the assumption is that you checked the values. There is also an assumption that if the value violated the database constraints (which is all you are checking for here), it would not have been allowed in the first place. I prefer not to waste processing power, especially as you start building objects with many more properties.

This is all the infrastructure that needs to be created to inform the user interface that the object has one or more broken rules; now you need a way to trap those rules.

Trapping Business Rule Violations

Because you are accessing the custom exceptions you created in the Errors namespace, you need to import that namespace into this module:

 Imports NorthwindTraders.NorthwindShared.Errors 

Now you will alter the RegionDescription Set code block. Listing 5-4 shows the original code for the RegionDescription property.

Listing 5-4: The Original RegionDescription Property

start example
 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 
end example

Add the rules you specified in the data-centric class first (in this case, it is a simple copy and paste) so that the RegionDescription property Set block of code now has the following additions (place this code right below the Set line, just as in the data-centric class):

 If Value.Length = 0 Then      Throw New ZeroLengthException Else      If Value.Length > 50 Then           Throw New MaximumLengthException(50)      End If End If 

Now, this code will not quite do it for you because once an error is thrown, it will be passed right up to the user interface. However, you want to make sure the object knows that it broke a rule (or did not break a rule) by passing the appropriate information to the BrokenRules class. To do this, wrap the code in a Try..Catch block. Listing 5-5 shows the entire modified RegionDescription property.

Listing 5-5: The Modified RegionDescription Property

start example
 Public Property RegionDescription() As String      Get           Return mstrRegionDescription      End Get      Set(ByVal Value As String)           Try                If Value.Length = 0 Then                     Throw New ZeroLengthException                Else                     If Value.Length > 50 Then                          Throw New MaximumLengthException(50)                     End If                End If                If mstrRegionDescription.trim <> Value Then                     mstrRegionDescription = Value                     If Not Loading Then                          mobjRules.BrokenRule("Region Description", False)                          mblnDirty = True                     End If                End If           Catch exc As Exception                mobjRules.BrokenRule("Region Description", True)                mstrRegionDescription = Value                mblnDirty = True                Throw exc           End Try      End Set End Property 
end example

Let's take a look at what is happening here and why. The beginning of the Set block looks the same as the RegionDC class. This is to be expected because there are no rules other than database constraints for which you are checking. After all of the business rules are checked, the code changes a little. First, you check to see if the value entered is already equal to the value that you had. The trim method is necessary because the field is declared as a char field in the database:

 If mstrRegionDescription.trim <> Value Then 

If it is, you do not want to mark the object as dirty if it has not really changed. If the values are different, then you want to set the module-level value equal to the value that was passed in (at this point, it has not broken any rules, so it is allowed):

 mstrRegionDescription = Value 

If you are loading the object, then you want to skip marking anything as dirty or playing with the BrokenRules class because it is unnecessary. If you are not loading the object, though, you need to let the BrokenRules class know that if there is a broken rule for this property that it is no longer broken and should be removed:

 mobjRules.BrokenRule("Region Description", False) 

You also know at this point that the object changed, so you need to set the dirty flag:

 mblnDirty = True 

Next comes the Catch block. First you catch the fact that an exception was thrown and you break the rule for that property in the BrokenRules object:

 mobjRules.BrokenRule("Region Description", True) 

Caution

It is especially important to note that whatever you call the property when you pass it to the BrokenRule method does not matter. What does matter is that you use the same name for a single property. The reason for this is that this is the key in the dictionary object in the BrokenRules object!

Then you set the property to a value you know violates the business rules (in almost all cases it should probably be the value that was passed in because that triggered the rule violation in the first place):

 mstrRegionDescription = Value mblnDirty = True 

Then you set the IsDirty flag to true. Why do you do that? Why don't you leave both of those properties alone? The answer takes some explaining. If you do not do either of these things, then this is what will happen when the user enters an invalid value:

  1. The invalid value will be passed to the Region object.

  2. It will violate one of the business rules.

  3. Code execution will jump to the Catch block.

  4. The rule will be marked as broken.

  5. The error will be rethrown to the user interface.

Nowhere in these steps did the value of mstrRegionDescription change. The only thing that effectively occurred was that the RegionDescription property was marked as having a broken rule. Now, assume that the user said, "Whoops," and either does an undo or types the same entry. In that case, the following steps would take place:

  1. The valid value will be passed to the Region object.

  2. It will not violate any of the business rules.

  3. It will be compared with the module-level RegionDescription variable.

  4. There will be no difference between the two values.

  5. Code execution continues back in the user interface.

Although the value in the object will be correct (because it never changed), the object will not be marked as dirty and there will be a rule broken for the RegionDescription property. The user interface will never be able to determine that everything is OK. That is a problem. So by setting the property to an invalid value and marking it as dirty, the user interface knows that the object has changed somehow (this is useful so you can ask a user if they really want to close a form and lose their changes), and the property is different than what was originally entered. If the original value is put back in, it will not match the internal value.

Note

You are purposely setting the value of a property to a bad value. This is one reason that the business rules should be rechecked in the data-centric business logic. But, because you have broken a rule, the Save routine will not run!

The Save routine will not run if there is a broken rule, so you should add the code in to make that a reality because the class will not do it on its own!

Modify the Save routine so that the entire routine is in an IfThen statement that reads as follows:

 Public Sub Save()      If mobjRules.Count = 0 Then           Original code here           .           .      End If End Sub 

Now the Save routine will not run if there are any broken rules. And finally you need to add one read-only property called IsValid. This property lets another object check the status of this object. Listing 5-6 shows the code for this property.

Listing 5-6: The IsValid Property

start example
 Public ReadOnly Property IsValid() As Boolean      Get           If mobjRules.Count > 0 Then                Return False           Else                Return True           End If      End Get End Property 
end example

This way you can always check the status of your object, which you will do in the next section.

Returning Business Rules to the User Interface

In the previous section you added a GetBusinessRules method to your data-centric objects. You need to implement a routine in the user-centric objects that can retrieve this data for you. Then you need to implement a form that you can use to display this information to the user.

Add the code for the GetBusinessRules method in Listing 5-7 to the Region class.

Listing 5-7: The GetBusinessRules Method

start example
 Public Function GetBusinessRules() As BusinessErrors      Dim objIRegion As IRegion      Dim objBusRules As BusinessErrors      objIRegion = CType(Activator.GetObject(GetType(IRegion), _      AppConstants.REMOTEOBJECTS & LISTENER), IRegion)      objBusRules = objIRegion.GetBusinessRules      objIRegion = Nothing      Return objBusRules End Function 
end example

This routine calls the GetBusinessRules method on the remote object and returns a BusinessErrors collection, which you then return to the user interface.




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