Wednesday, October 13, 2010
Expose Child Control In A Custom User Control For Javascript
The unelegant one first - Add an attribute in the hosting page load to handle the blur event of the standalone textbox triggering a __doPostBack. In the load event of the custom user control, adding code to use the FindControl function on the parent page to retrieve the standalone textbox's value and store this in the inside textbox.
While this did the task, the actual postback of the page was a real spoiler (especially since my page had more than just these two items).
Relooked at the same issue a while back and thought of another solution.
An elegant one - Expose the inside textbox as a property of the usercontrol. In the hosting page, add an attribute to handle the blur event, this time however use a simple javascript to set the value of the textbox exposed as a property to the value of the standalone textbox.
Consider the standalone textbox is called txt1 and the usercontrol uc1 has two textboxes - txt2 and txt3. We want to set the value of txt3 to be the same as txt1. We have now exposed txt3 as a property of uc1 say txt3TextBox
In code,
Dim blurJS As String = String.Format(document.getElementByID('{0}').value = document.getElementByID('{1}').value, uc1.txt3TextBox.ClientID, txt1.ClientID)
txt1.Attributes.Add("OnBlur", blurJS)
Well, I had more fun than I added here especially with the txt3 control being part of a view of the multiview and hence I needed to add more checks to ensure that the left hand side of the expression was not null and such. But that is another story.
Tuesday, January 15, 2008
Is that default namespace in XML bothering you?
For starters, I will create an XML file that contains a Person node at the root and three nodes - Name that lies in the n1 namespace, Age that lies in the n2 namespace and Gender that is part of the default namespace.
A listing of the Sample.xml file
<?xml version="1.0" encoding="utf-8" ?>
<Person xmlns:n1="http://codeedifice.com/N1" xmlns:n2="http://codeedifice.com/N2" xmlns="http://codeedifice.com/N">
<n1:Name>Alvin</n1:Name>
<n2:Age>10</n2:Age>
<Gender>Male</Gender>
</Person>
The example that I am listing here lists the top three approaches taken by developers who are still getting their hands dirty with XML that contains namespaces. The next three approaches are alternatives that you must work out for yourself and pick the one that suits your actual requirement
Sub Main()
Dim xdoc As XmlDocument = New XmlDocument
xdoc.Load("Sample.xml")
'Fails due to lack of namespace
Console.WriteLine("Looking for Person Node(s) without a namespace manager...")
Dim xNodLst1 As Xml.XmlNodeList = xdoc.SelectNodes("/Person")
Console.WriteLine(Space(4) & "Found " & xNodLst1.Count & " matche(s)")
Dim nsMgr As XmlNamespaceManager = New XmlNamespaceManager(xdoc.NameTable)
nsMgr.AddNamespace("n1", "http://codeedifice.com/N1")
nsMgr.AddNamespace("n2", "http://codeedifice.com/N2")
'Fails since the namespaces do not include the default one
Console.WriteLine("Looking for Person Node(s) with the namespace manager not including the default namespace...")
Dim xNodLst2 As Xml.XmlNodeList = xdoc.SelectNodes("/Person", nsMgr)
Console.WriteLine(Space(4) & "Found " & xNodLst1.Count & " matche(s)")
nsMgr.AddNamespace("", "http://codeedifice.com/N")
'Fails since the empty string namespace is not useful (quite contrary to popular vote)
Console.WriteLine("Looking for Person Node(s) with the namespace manager including the default namespace as an empty string...")
Dim xNodLst3 As Xml.XmlNodeList = xdoc.SelectNodes("/Person", nsMgr)
Console.WriteLine(Space(4) & "Found " & xNodLst3.Count & " matche(s)")
nsMgr.RemoveNamespace("", "http://codeedifice.com/N") 'take the useless one out
nsMgr.AddNamespace("ALVIN", "http://codeedifice.com/N")
'Finally a working solution
Console.WriteLine("Looking for Person Node(s) with the namespace manager including the default namespace as a unique value - ALVIN...")
Dim xNodLst4 As Xml.XmlNodeList = xdoc.SelectNodes("/ALVIN:Person", nsMgr) 'Note how the dummy namespace is used
Console.WriteLine(Space(4) & "Found " & xNodLst4.Count & " matche(s)")
'A slightly more elegant solution that does not rely on the unique value ALVIN to be created by the developer
Console.WriteLine("Looking for Person Node(s) without the namespace manager using the localName XPath function...")
Dim xNodLst5 As Xml.XmlNodeList = xdoc.SelectNodes("/*[local-name()='Person']")
Console.WriteLine(Space(4) & "Found " & xNodLst5.Count & " matche(s)")
'Accessing the Name and Age nodes correctly will require the use of the namespace
Console.WriteLine("Looking for Name Node(s) with the namespace manager using the localName XPath function...")
Dim xNodLst6 As Xml.XmlNodeList = xdoc.SelectNodes("/*[local-name()='Person']/n1:Name", nsMgr)
Console.WriteLine(Space(4) & "Found " & xNodLst6.Count & " matche(s)")
'Accessing the Name and Age nodes in a namespace agnostic fashion
'This implies that you are assuming that you do not care about the namespace and can be dangerous
Console.WriteLine("Looking for Name Node(s) without the namespace manager using the localName XPath function...")
Dim xNodLst7 As Xml.XmlNodeList = xdoc.SelectNodes("/*[local-name()='Person']/*[local-name()='Name']")
Console.WriteLine(Space(4) & "Found " & xNodLst7.Count & " matche(s)")
Console.ReadLine()
End Sub
And a look at the results
Thursday, January 10, 2008
Get XML Nodes that do not have a specific attribute
Sample code
Sub Main()
'Sample XML island
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder
sb.AppendLine("<items>")
sb.AppendLine(" <item type='Pencil' qty='5' IsCanceled='1' />")
sb.AppendLine(" <item type='Pen' qty='2' />")
sb.AppendLine(" <item type='Book' qty='1' />")
sb.AppendLine(" <item type='Pencil' qty='3' />")
sb.AppendLine("</items>")
Dim xDoc As Xml.XmlDocument = New Xml.XmlDocument
xDoc.LoadXml(sb.ToString)
Console.WriteLine("Original XML Content")
Console.WriteLine(sb.ToString)
'Original Strategy
Console.WriteLine("Active list of items")
Dim xNodLst As Xml.XmlNodeList = xDoc.SelectNodes("/items/item")
For Each xNod As Xml.XmlNode In xNodLst
If xNod.Attributes("IsCanceled") Is Nothing Then
Console.WriteLine("{0} [{1}]", xNod.Attributes("type").Value, xNod.Attributes("qty").Value)
End If
Next
Console.WriteLine()
'Alternate Strategy
Console.WriteLine("Active list of items (Alternate)")
Dim xNodAltLst As Xml.XmlNodeList = xDoc.SelectNodes("/items/item[not (@IsCanceled)]")
For Each xNod As Xml.XmlNode In xNodAltLst
Console.WriteLine("{0} [{1}]", xNod.Attributes("type").Value, xNod.Attributes("qty").Value)
Next
Console.ReadLine()
End Sub
In this example, I have eliminated nodes that do not have the IsCanceled attribute. The exact value of the attribute was not relevant to me, its existence was reason enough to have it eliminated from my result list.
A look at the execution result

Of course, if you wanted to include only nodes that had a specific attribute, you guessed right, just take the 'not' keyword out of the query -
Dim xNodAltLst As Xml.XmlNodeList = xDoc.SelectNodes("/items/item[@IsCanceled]")
Thursday, January 3, 2008
Template Method Design Pattern
PrintFormatter Class Listing
Public Class PrintFormatter
Public Enum DataRepresentation
TextFormat
XMLFormat
SQLDataTableFormat
End Enum
Private _TextData As String
Private _XMLData As Xml.XmlDocument
Private _SQLData As DataTable
Private _DataRepresentation As DataRepresentation
Public Sub New(ByVal PrintDataRepresentation As DataRepresentation)
_DataRepresentation = PrintDataRepresentation
End Sub
Public WriteOnly Property TextData() As String
Set(ByVal value As String)
_TextData = value
End Set
End Property
Public WriteOnly Property XMLData() As Xml.XmlDocument
Set(ByVal value As Xml.XmlDocument)
_XMLData = value
End Set
End Property
Public WriteOnly Property SQLData() As DataTable
Set(ByVal value As DataTable)
_SQLData = value
End Set
End Property
Public Sub PrintFormattedData()
_PrintHeader()
Select Case _DataRepresentation
Case DataRepresentation.TextFormat
_PrintTextData()
Case DataRepresentation.XMLFormat
_PrintXMLData()
Case DataRepresentation.SQLDataTableFormat
_PrintSQLData()
End Select
_PrintFooter()
End Sub
Private Sub _PrintHeader()
Console.WriteLine("This represents the page header")
End Sub
Private Sub _PrintFooter()
Console.WriteLine("This represents the page footer")
End Sub
Private Sub _PrintTextData()
Console.WriteLine("This represents the formatted text data - " & _TextData)
End Sub
Private Sub _PrintXMLData()
Console.WriteLine("This represents the formatted XML data - " & _XMLData.OuterXml)
End Sub
Private Sub _PrintSQLData()
Console.WriteLine("This represents the formatted SQL data - " & _SQLData.TableName)
End Sub
End Class
Not much of a class diagram hierarchy to show

modMain Module Listing
Module modMain
Sub Main()
'Sample Data - one of each kind
Dim TextData As String = "<<TextData>>"
Dim XMLData As Xml.XmlDocument = New Xml.XmlDocument
XMLData.LoadXml("<xMLData />")
Dim SQLData As DataTable = New DataTable("SQLDataTable")
Dim oPrintFormatter As PrintFormatter
oPrintFormatter = New PrintFormatter(PrintFormatter.DataRepresentation.TextFormat)
oPrintFormatter.TextData = TextData
oPrintFormatter.PrintFormattedData()
oPrintFormatter = New PrintFormatter(PrintFormatter.DataRepresentation.XMLFormat)
oPrintFormatter.XMLData = XMLData
oPrintFormatter.PrintFormattedData()
oPrintFormatter = New PrintFormatter(PrintFormatter.DataRepresentation.SQLDataTableFormat)
oPrintFormatter.SQLData = SQLData
oPrintFormatter.PrintFormattedData()
Console.ReadLine()
End Sub
End Module
Let’s put our application to the test

Alright, things look good for a one off application requirement. However, extending this design to support additional data formats would require modifying the PrintFormatter class by adding additional data representations and modifying the PrintFormattedData method. A template method pattern can be effectively used in this example to eliminate the need to modify the base class and extending the base class to support additional data formats.
The primary idea behind the template method is to use a method to perform the actions that are common across representations – printing the header and footer while leaving the task of printing specific data representations to abstract methods. The PrintFormatter class can then be extended by the TextPrintFormatter to provide the implementation for printing text data. Similarly the XMLPrintFormatter can implement the abstract PrintSpecificData method to print XML Data while the SQLPrintFormatter can provide the SQL data printing logic.
PrintFormatter Class Listing
Public MustInherit Class PrintFormatter
'Our Template method
Public Sub PrintFormattedData()
PrintHeader()
PrintSpecificData()
PrintFooter()
End Sub
'Some methods that provide a default implementation
Public Sub PrintHeader()
Console.WriteLine("This represents the page header")
End Sub
Public Sub PrintFooter()
Console.WriteLine("This represents the page footer")
End Sub
'Abstract methods
Public MustOverride Sub PrintSpecificData()
End Class
TextPrintFormatter Class Listing
Public Class TextPrintFormatter
Inherits PrintFormatter
Private _TextData As String
Public Sub New(ByVal TextData As String)
_TextData = TextData
End Sub
Public Overrides Sub PrintSpecificData()
Console.WriteLine("This represents the formatted text data - " & _TextData)
End Sub
End Class
XMLPrintFormatter Class Listing
Public Class XMLPrintFormatter
Inherits PrintFormatter
Private _XMLData As Xml.XmlDocument
Public Sub New(ByVal XMLData As Xml.XmlDocument)
_XMLData = XMLData
End Sub
Public Overrides Sub PrintSpecificData()
Console.WriteLine("This represents the formatted XML data - " & _XMLData.OuterXml)
End Sub
End Class
SQLDataPrintFormatter Class Listing
Public Class SQLDataPrintFormatter
Inherits PrintFormatter
Private _SQLData As DataTable
Public Sub New(ByVal SQLData As DataTable)
_SQLData = SQLData
End Sub
Public Overrides Sub PrintSpecificData()
Console.WriteLine("This represents the formatted SQL data - " & _SQLData.TableName)
End Sub
End Class
The Template method pattern class hierarchy

modMain Module Listing
Module modMain
Sub Main()
'Sample Data - one of each kind
Dim TextData As String = "<<TextData>>"
Dim XMLData As Xml.XmlDocument = New Xml.XmlDocument
XMLData.LoadXml("<xMLData />")
Dim SQLData As DataTable = New DataTable("SQLDataTable")
Dim oPrintFormatter As PrintFormatter
oPrintFormatter = New TextPrintFormatter(TextData)
oPrintFormatter.PrintFormattedData()
oPrintFormatter = New XMLPrintFormatter(XMLData)
oPrintFormatter.PrintFormattedData()
oPrintFormatter = New SQLDataPrintFormatter(SQLData)
oPrintFormatter.PrintFormattedData()
Console.ReadLine()
End Sub
End Module
And a look at the results
Wednesday, January 2, 2008
Singleton Design Pattern
Well with requirements as simple as this, let’s dig in
TemperatureMonitor Class Listing
Public Class TemperatureMonitor
Private _IsExitSignalled As Boolean
Public Sub New()
_IsExitSignalled = False
End Sub
Public Sub StartMonitoring()
Dim oSerialCommunicator As SerialCommunicator = New SerialCommunicator
Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)
th.Start(oSerialCommunicator)
End Sub
Private Sub _MonitoringThread(ByVal oSerialCommunicator As Object)
Dim oSC As SerialCommunicator = CType(oSerialCommunicator, SerialCommunicator)
Do
'Add steps here to perform actual temperatur monitoring,
oSC.SendMonitoredInfo("Memory Temperature Message")
Threading.Thread.Sleep(2000) 'Sleep for 2 seconds
Loop Until _IsExitSignalled
End Sub
Public Sub SignalExit()
_IsExitSignalled = True
End Sub
End Class
MemoryMonitor Class Listing
Public Class MemoryMonitor
Private _IsExitSignalled As Boolean
Public Sub New()
_IsExitSignalled = False
End Sub
Public Sub StartMonitoring()
Dim oSerialCommunicator As SerialCommunicator = New SerialCommunicator
Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)
th.Start(oSerialCommunicator)
End Sub
Private Sub _MonitoringThread(ByVal oSerialCommunicator As Object)
Dim oSC As SerialCommunicator = CType(oSerialCommunicator, SerialCommunicator)
Do
'Add steps here to perform actual memory monitoring,
oSC.SendMonitoredInfo("Memory Monitor Message")
Threading.Thread.Sleep(2000) 'Sleep for 2 seconds
Loop Until _IsExitSignalled
End Sub
Public Sub SignalExit()
_IsExitSignalled = True
End Sub
End Class
SerialCommunicator Class Listing
Public Class SerialCommunicator
Public Sub New()
'Open the communication port
Console.WriteLine("Opening Serial Port")
End Sub
Public Sub SendMonitoredInfo(ByVal Message As String)
Console.WriteLine("Sending message " & Message)
End Sub
End Class
And here is what our classes look like

So what else, well the test harness to test our little application
modMain Module Listing
Module modMain
Sub Main()
Dim oTemperatureMonitor As TemperatureMonitor = New TemperatureMonitor
Dim oMemoryMonitor As MemoryMonitor = New MemoryMonitor
oTemperatureMonitor.StartMonitoring()
oMemoryMonitor.StartMonitoring()
Console.WriteLine("Press Enter to stop monitoring")
Console.ReadLine()
oTemperatureMonitor.SignalExit()
oMemoryMonitor.SignalExit()
Console.WriteLine("Monitoring stopped")
Console.ReadLine()
End Sub
End Module
And a first look at the results

All this looks pretty simple on the face of it; however, as any experienced programmer who has used the COM port of a PC will tell you, you cannot concurrently open the same port more than once. This implies that the information that the Temperature Monitoring thread will produce will need to be sent over the serial interface in a timeshared fashion along with the information produced by the Memory Monitoring thread. This also implies that the highlighted portion in the output which indicates that the port has been opened concurrently is an issue and this application cannot be used for sending messages out.
So, how do we fix this port sharing issue? Put the Singleton pattern to use. We transform our SerialCommunicator class into a singleton so that it returns a single instance of itself to all requesting methods. With only a single instance of the SerialCommunicator, we eliminate the issue of opening the serial port concurrently. To transform this class into a Singleton, the first thing we need to do is to prevent its external instantiation using the New keyword. We can achieve this by changing the scope of the constructor to Private.
Hold on, won’t that prevent object instantiation? In the conventional sense, Yes! However a private constructor implies that the object can be instantiated within the class. What good is that? How will our TemperatureMonitor and MemoryMonitor acquire this instance? Ok, Enough beating around the bush. Just add a shared method that creates and returns an instance of the class. Ensure that it creates an instance only if an instance of the class does not exist. If an instance exists, it simply returns that instance.
Well, here is the new and improved Singleton SerialCommunicator class –
SerialCommunicator Class Listing
Public Class SerialCommunicator
Private Shared _oSerialCommunicator As SerialCommunicator
Private Sub New()
'Open the communication port
Console.WriteLine("Opening Serial Port")
End Sub
Public Shared Function GetSerialCommunicator() As SerialCommunicator
If _oSerialCommunicator Is Nothing Then
_oSerialCommunicator = New SerialCommunicator
End If
Return _oSerialCommunicator
End Function
Public Sub SendMonitoredInfo(ByVal Message As String)
SyncLock Me
Console.WriteLine("Sending message " & Message)
End SyncLock
End Sub
End Class
We need to change our TemperatureMonitor and MemoryMonitor classes as well since the public constructor of the SerialCommunicator is no longer available.
TemperatureMonitor StartMonitoring Method Listing
Public Class TemperatureMonitor
'No change in code here
Public Sub StartMonitoring()
Dim oSerialCommunicator As SerialCommunicator = SerialCommunicator.GetSerialCommunicator
Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)
th.Start(oSerialCommunicator)
End Sub
'No change in code or here
End Class
MemoryMonitor StartMonitoring Method Listing
Public Class MemoryMonitor
'No change in code here
Public Sub StartMonitoring()
Dim oSerialCommunicator As SerialCommunicator = SerialCommunicator.GetSerialCommunicator
Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)
th.Start(oSerialCommunicator)
End Sub
'No change in code here
End Class
Our class diagram is identical to our original. The only difference being the scope of the SerialCommunicator constructor and an additional method in the same class to create and return an instance of the SerialCommunicator class.

With no change in our test harness, let’s put our application to the test

Notice that we only have one “Opening Serial Port” message indicating our Singleton pattern is in effect.
Adapter Design Pattern
GameCharacter Interface Listing
Public Interface GameCharacter
Sub Eat()
Sub Talk()
Sub Fight()
Sub Move()
End Interface
Soldier Class Listing
Public Class Soldier
Implements GameCharacter
Public Sub Eat() Implements GameCharacter.Eat
Console.WriteLine("Soldier is eating")
End Sub
Public Sub Fight() Implements GameCharacter.Fight
Console.WriteLine("Soldier is fighting")
End Sub
Public Sub Move() Implements GameCharacter.Move
Console.WriteLine("Soldier is moving slowly")
End Sub
Public Sub Talk() Implements GameCharacter.Talk
Console.WriteLine("Soldier is talking")
End Sub
End Class
Let us assume that the Animal class and the inheriting sub-classes for specific animals were created by another developer on the team.
Animal Class Listing
Public MustInherit Class Animal
Public Sub Sleep()
Console.WriteLine("Animal is sleeping")
End Sub
Public Sub Drink()
Console.WriteLine("Animal is drinking")
End Sub
Public Sub Eat()
Console.WriteLine("Animal is eating")
End Sub
Public Sub Graze()
Console.WriteLine("Animal is grazing")
End Sub
Public Sub MakeSound()
Console.WriteLine("Animal is making some noise")
End Sub
End Class
The TrainedElephant class extends the Animal class by adding the Move and Attack methods
TrainedElephant Class Listing
Public Class TrainedElephant
Inherits Animal
Public Sub Move()
Console.WriteLine("TrainedElephant is moving")
End Sub
Public Sub Attack()
Console.WriteLine("TrainedElephant is attack")
End Sub
End Class
Let us have a quick look at our class and interface hierarchy

And finally, lets list our requirement. With an existing design that resembles this, we would like to maintain a list of all character instances in the game including humans as well as animals that can fight and move. A quick solution is to use a generic list that holds GameCharacter objects. Since Soldiers natively implement the GameCharacter interface, it is quite easy to add a Soldier to this list. However, there is no relation between the Animal class and the GameCharacter interface and neither does the TrainedElephant class implement the Animal interface. It, therefore makes it impossible to add the TrainedElephant as a GameCharacter to the list.

One solution is to modify the code for the TrainedElephant class so that it implements the GameCharacter Interface. Although this seems easy from the looks of it, this may not be practical in a real life application where this portion of the code is built by another developer or when the source code for this class is not available for modification. In such scenarios, we can employ the services of the Adapter pattern to adapt the functionality of the TrainedElephant class to map to the GameCharacter class.
The TrainedElephantAdapter class simply wraps around a TrainedElephant object while implementing the GameCharacter interface. Thus, we do not need to modify the source code for the TrainedElephant or the Animal class and can use this TrainedElephantAdapter object interchangeably as a GameCharacter.
TrainedElephantAdapter Class Listing
Public Class TrainedElephantAdapter
Implements GameCharacter
Private _oElephant As TrainedElephant
Public Sub New(ByVal oElephant As TrainedElephant)
_oElephant = oElephant
End Sub
Public Sub Eat() Implements GameCharacter.Eat
_oElephant.Eat()
End Sub
Public Sub Fight() Implements GameCharacter.Fight
_oElephant.Attack()
End Sub
Public Sub Move() Implements GameCharacter.Move
_oElephant.Move()
End Sub
Public Sub Talk() Implements GameCharacter.Talk
_oElephant.MakeSound()
End Sub
End Class
Our class diagram now contains our new TrainedElephantAdapter which encapsulates the TrainedElephant object while implementing the GameCharacter interface

Finally, a little test harness using the TrainedElephantAdapter to do the magic for us
modMain Module Listing
Module modMain
Sub Main()
Dim GameCharacters As List(Of GameCharacter) = New List(Of GameCharacter)
GameCharacters.Add(New Soldier)
'And here is the Wrapped up TrainedElephant
GameCharacters.Add(New TrainedElephantAdapter(New TrainedElephant))
For Each oGC As GameCharacter In GameCharacters
oGC.Eat() : oGC.Fight() : oGC.Move() : oGC.Talk()
Next
Console.ReadLine()
End Sub
End Module
And the results of using the Adapter pattern
Facade Design Pattern
Consider a business application for a mortgage company. The Mortgage Company uses multiple services that are both internal to the company as well as require interaction with other parties to determine if a loan can be sanctioned. Using the Facade pattern here is easy, create a class say LoanHelper. We can then create shared method unless you prefer to create an instance of the LoanHelper. Add the method calls to the different internal and external services. This LoanHelper method can then be used anywhere within your code to validate a prospect before allotting a loan and determining the rate of interest.
LoanHelper Class Listing
Public Class LoanHelper
Public Const DEFAULT_LOAN_PERCENT As Double = 10.0
Public Shared Function GetEligbleLoanCriteria(ByVal SSN As String, ByVal RequiredLoanAmount As Double, _
ByRef EligibleLoanAmount As Double, ByRef EligibleLoanPercent As Double) As Boolean
'Add the method calls to various systems - internal and external to determine
'the values of EligibleLoanAmount and the EligibleLoanAmount
EligibleLoanAmount = 100000
EligibleLoanPercent = 8.25
Console.WriteLine("Façade class LoanHelper was invoked to determine Eligible Loan Criteria")
Return True 'Return false if SSN failed to match
End Function
End Class
modMain Module Listing
Module modMain
Sub Main()
Dim SSN As String = "SampleSSN"
Dim RequiredLoanAmount As Double = 100000
Dim EligibleLoanAmount As Double = 0
Dim EligibleLoanPercent As Double = LoanHelper.DEFAULT_LOAN_PERCENT
Dim IsSSNMatch As Boolean = LoanHelper.GetEligbleLoanCriteria(SSN, RequiredLoanAmount, _
EligibleLoanAmount, EligibleLoanPercent)
Console.ReadLine()
End Sub
End Module
A look at the classes

And the results