Before you begin trapping rules, you need to import the Errors namespace. Add the following line in the header of the RegionDC code module:
Imports NorthwindTraders.NorthwindShared.Errors
Next, add the following variable declaration in the header of the RegionDC class:
Private mobjBusErr As BusinessErrors
Now, in the Save routine, add the following line of code below the Dim statements but above the sRegion with statement:
mobjBusErr = New BusinessErrors()
Now you are ready to begin handling the rule violations.
The only property you are checking for business rule violations on is the RegionDescription property because the RegionID property is read-only. To review, this is what your RegionDescription property currently looks like:
Public Property RegionDescription() As String Get Return mstrRegionDescription End Get Set(ByVal Value As String) mstrRegionDescription = Value End Set End Property
Because you are going to be trapping for the violations when the property is set, you are only concerned with the Set block. All of your business rule checks will occur in a Try..Catch block, so start by altering the Set block of code so that it looks like the following:
Try mstrRegionDescription = Value Catch exc As Exception mobjBusErr.Add("Region Description", exc.Message) End Try
The Catch block of your routines will be used to catch any business violations you throw. When the error is thrown, you set the property name and pass in the exception message and then continue on your way.
To perform your validations, let's add the following If..Then..Else statement right below the Try statement:
If Value.Length = 0 Then Throw New ZeroLengthException Else If Value.Length > 50 Then Throw New MaximumLengthException(50) End If End If
Now you have all the rules in place to validate, and you can trap the violations.
Note | Examining the code, you will notice it is only going to catch one violation per rule. In other words, when an exception is thrown, you do not go back and check any of the additional rules. This is easier to handle than trying to catch multiple errors per property. As you will see when you get to the user interface, it is easier for you and less confusing for the user if you only handle one error per property. |
Next you will add another method that is extremely useful in large business applications—the GetBusinessRules method. This method has one purpose: to return a set of all of the rules implemented by the class. There are two good reasons for doing this. The first is that it makes sense in many applications to be able to inform the user of all of the rules of an object when they are performing data entry. The second reason is to allow other applications that may use your objects to be able to retrieve a list of your objects rules. Although these rules should be published in application documentation, it is often advantageous to allow the object to be self-describing.
Note | This is not truly a self-describing object, although it looks like one right now. It is not self-describing because first you have to duplicate your business rules, and second, the object does not really examine itself. Chapter 10, "Using Reflection," shows you how to create a truly self-aware class in terms of business rules. |
Add the following code to implement this method into the RegionDC class:
Public Function GetBusinessRules() As BusinessErrors Dim objBusRules As New BusinessErrors() With objBusRules .Add("Region Description", "The value cannot be null.") .Add("Region Description", "The value cannot be more than 50 " _ & "characters in length.") End With Return objBusRules End Function
This method creates a BusinessErrors object and adds all of the errors that can occur in your object to it. It then returns this collection of errors to the calling code. To allow this method to be accessible from the client code, you need to add another method to your IRegion interface. To do this, alter the IRegion interface in the Interfaces code module so that it reads as follows:
Public Interface IRegion Function LoadProxy() As DataSet Function LoadRecord(ByVal intID As Integer) As Structures.structRegion Function Save(ByVal sRegion As structRegion, _ ByRef intID As Integer) As BusinessErrors Sub Delete(ByVal intID As Integer) Function GetBusinessRules() As BusinessErrors End Interface
You made two changes to this interface. The first change is to the Save routine, which is now a function that returns a BusinessErrors object. The second change is the addition of the GetBusinessRules function. Once you incorporate these changes, you will have a couple of syntax errors until you import the following namespace into the Interfaces code module:
Imports NorthwindTraders.NorthwindShared.Errors
Note | This change is why you returned the ID of the saved record as a ByRef value. |
Return to the RegionDC class and alter the GetBusinessRules function so that it implements the interface function you just created. To do this, change the signature line of the method to read as follows:
Public Function GetBusinessRules() As BusinessErrors _ Implements IRegion.GetBusinessRules
Before you can report errors to the user interface, you need to alter your Save routine (at this point you have an error caused by your altering of the IRegion Interface)—first so that it matches your interface signature and second so that it actually returns your collection of errors. Alter the Save method signature to read as follows:
Public Function Save(ByVal sRegion As structRegion, _ ByRef intID As Integer) As BusinessErrors Implements IRegion.Save
The way this code is currently structured, the following events will take place when you try to save your object:
The BusinessErrors object is instantiated.
The public RegionDescription property will have its value set.
Any error will cause an error to be added to the BusinessError object.
The last thing you need to do is to respond to any business rules that were violated during the setting of the properties. To do this, add the following code to check to see if there were any errors (add this code after the With sRegion block in place of the code that is already there):
If mobjBusErr.Count = 0 Then cn.Open() With cmd .Connection = cn .CommandType = CommandType.StoredProcedure .CommandText = "usp_region_save" .Parameters.Add("@id", mintRegionID) .Parameters.Add("@region", mstrRegionDescription) .Parameters.Add("@new_id", SqlDbType.Int).Direction = _ ParameterDirection.Output .ExecuteNonQuery() intID = Convert.ToInt32(.Parameters.Item("@id").Value) End With cmd = Nothing cn.Close() End If Return mobjBusErr
The only change here is the addition of the If..Then statement and the addition of the Return line. The If statement checks to see if any errors were thrown. If you did, then you skip the block of code that calls the database; otherwise you save the data to the database. Finally, you return the collection of errors. On the user interface side, you will check the count of errors to see if you need to do anything with the object that is returned to you. Now you are ready to move on to the user-centric objects.