UPDATE: DECORATing Web Controls
One immediate flaw with the existing code is that the PersonBuilder class relies on an implementation of IControlLocator. While this might not appear to be that big of an issue since, after all, it is a web application, the design can be improved upon by extracting the interaction between the PersonBuilder and DataGridItemControlLocator classes:
Public Interface IControlLocator
Function FindControl(controlId as String) as Control
End Interface
Public Interface IPersonDataRetriever
Function GetName() As String
Function GetAge() As Integer
End Interface
Public Class DataGridPersonDataRetriever
Implements IPersonDataRetriever
Private _dgiControlLocator as IControlLocator
Public Sub New(locator as IControlLocator)
If IsNothing(locator) Then Throw New ArgumentNullException("locator")
_dgiControlLocator = locator
End Sub
Public Function GetName() As String Implements IPersonDataRetriever.GetName()
return FindTextBox("NameField").Text
End Function
Public Function GetAge() As Integer Implements IPersonDataRetriever.GetAge()
Return Integer.Parse(FindTextBox("AgeField")).Text
End Function
Private Function FindTextBox(controlId as String) As TextBox
Retrun DirectCast(_dgiControlLocator.FindControl(controlId), TextBox)
End Function
End Class
Public Class DataGridItemControlLocator
Implements IControlLocator
private dgItem as DataGridItem
private controlPrefix as String = ""
Public Sub New(dgi as DataGridItem)
If IsNothing(dgi) Then Throw New AgrumentNullException("dgi")
dgItem = dgi
End Sub
Public Sub New(dgi as DataGridItem, prefix as String)
Me.New(dgi)
If IsNothing(prefix) OrElse String.Empty.Equals(prefix.Trim()) Then Throw New ArgumentNullException("prefix")
controlPrefix = prefix.Trim()
End Sub
Public Function FindControl(controlId as String)as Control Implements IControlLocator.FindControl
Return dgItem.FindControl(prefix + controlId)
End Function
End Class
Public Class PersonBuilder
Protected personData as IPersonDataRetriever
Public Sub New(aPersonData as IPersonDataRetriever
If IsNothing(aPersonData) Then Throw New ArgumentNullException("aPersonData")
personData = aPersonData
End Sub
Public Function MakePerson() as Person
dim thePerson as Person = New Person()
thePerson.Name = personData.GetName()
thePerson.Age = personData.GetAge()
Return thePerson
End Function
End Class
Some might not like this as it makes the code for creating the PersonBuilder more complex:
Private Sub PersonDataGrid_ItemCommand(s as object, e as DataGridEventArgs)
Dim thePerson as Person '//or Load the Person object
Select Case e.CommandName.ToLower().Trim()
Case "update"
thePerson = New PersonBuilder(New DataGridPersonDataRetriever(New DataGridItemControlLocator(e.Item, "Edit"))).MakePerson()
case "insert"
thePerson = New PersonBuilder(New DataGridPersonDataRetriever(New DataGridItemControlLocator(e.Item, "New"))).MakePerson()
end select
'//other possible logic removed
End Sub
However, if you ask me, I feel the resulting design separates the concerns from the classes in a much better fashion then the original code. The PersonBuilder class is no longer "coupled" to the IControlLocator interface, which allows the BUILDER to be used in a WinForms environment, should the app need to include rich-client support in the future.
I'm not sure, as I'm no GoF patterns expert, but this seems as close to a Model-View-Controller design as one might be able to achieve when implementing in-place-editing with the various Data-controls that are available as-of Framework 1.1.
I'd love to hear any one's thoughts!