One of the biggest problems you will encounter in your career as a developer is pesky users. They always seem to enter the wrong data. So we need to validate their input into your brilliant system.

There are several ways of doing this when using a domain model.

First, you could make sure that all your properties and constructors are designed in such a way that your object can never be in an invalid state. I wrote about this before. The biggest problem with this, is that it can get problematic really fast. I would therefore only advise such an approach for simple objects. Really simple objects.

The better thing to do is to create a validating framework. You can read an excellent article by JP Boodhoo. He describes how to do this in a C# kind of manner. He shows how to make a validation framework and adds a Validate method to the object. This is one method to do it. But his framework works just as well for an external framework where you pass in the object to be validated. And that is what my code in VB.Net does. I also don’t use predicates but the newer Func(of T).

First the interface for the BusinessRule:

vb.net
1
2
3
4
5
6
7
Namespace Model.Validation
    Public Interface IBusinessRule(Of T)
        ReadOnly Property Name() As String
        ReadOnly Property Description() As String
        Function IsBrokenBy(ByVal Item As T) As Boolean
    End Interface
End Namespace
Namespace Model.Validation
    Public Interface IBusinessRule(Of T)
        ReadOnly Property Name() As String
        ReadOnly Property Description() As String
        Function IsBrokenBy(ByVal Item As T) As Boolean
    End Interface
End Namespace

Then an implementation for this Interface:

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Namespace Model.Validation
    Public Class BusinessRule(Of T)
        Implements IBusinessRule(Of T)
 
        Private _name As String
        Private _description As String
        Private _action As Func(Of T, Boolean)
 
        Public Sub New(ByVal Name As String, ByVal Description As String, ByVal Condition As Func(Of T, Boolean))
            _action = Condition
            _name = Name
            _Description = Description
        End Sub
 
        Public Function IsBrokenBy(ByVal Item As T) As Boolean Implements IBusinessRule(Of T).IsBrokenBy
            Return Not _action(Item)
        End Function
 
        Public ReadOnly Property Description() As String Implements IBusinessRule(Of T).Description
            Get
                Return _description
            End Get
        End Property
 
        Public ReadOnly Property Name() As String Implements IBusinessRule(Of T).Name
            Get
                Return _name
            End Get
        End Property
    End Class
End Namespace
Namespace Model.Validation
    Public Class BusinessRule(Of T)
        Implements IBusinessRule(Of T)

        Private _name As String
        Private _description As String
        Private _action As Func(Of T, Boolean)

        Public Sub New(ByVal Name As String, ByVal Description As String, ByVal Condition As Func(Of T, Boolean))
            _action = Condition
            _name = Name
            _Description = Description
        End Sub

        Public Function IsBrokenBy(ByVal Item As T) As Boolean Implements IBusinessRule(Of T).IsBrokenBy
            Return Not _action(Item)
        End Function

        Public ReadOnly Property Description() As String Implements IBusinessRule(Of T).Description
            Get
                Return _description
            End Get
        End Property

        Public ReadOnly Property Name() As String Implements IBusinessRule(Of T).Name
            Get
                Return _name
            End Get
        End Property
    End Class
End Namespace

Of course you will always have more than one rule per object so we need an IBusinessRuleSet:

vb.net
1
2
3
4
5
6
7
8
9
Namespace Model.Validation
    Public Interface IBusinessRuleSet(Of T)
        Function Messages(ByVal Item As T) As IList(Of String)
        Function IsBroken(ByVal Item As T) As Boolean
        Function BrokenBy(ByVal Item As T) As IList(Of IBusinessRule(Of T))
        ReadOnly Property RulesCount() As Integer
        ReadOnly Property BrokenRulesCount() As Integer
    End Interface
End Namespace
Namespace Model.Validation
    Public Interface IBusinessRuleSet(Of T)
        Function Messages(ByVal Item As T) As IList(Of String)
        Function IsBroken(ByVal Item As T) As Boolean
        Function BrokenBy(ByVal Item As T) As IList(Of IBusinessRule(Of T))
        ReadOnly Property RulesCount() As Integer
        ReadOnly Property BrokenRulesCount() As Integer
    End Interface
End Namespace

And an implementation:

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Namespace Model.Validation
    Public Class BusinessRuleSet(Of T)
        Implements IBusinessRuleSet(Of T)
 
        Private _rules As IList(Of IBusinessRule(Of T))
        Private _brokenRules As IList(Of IBusinessRule(Of T))
 
        Public Sub New(ByVal Rules As IList(Of IBusinessRule(Of T)))
            _rules = Rules
        End Sub
 
        Public Function BrokenBy(ByVal Item As T) As System.Collections.Generic.IList(Of IBusinessRule(Of T)) Implements IBusinessRuleSet(Of T).BrokenBy
            _brokenRules = New List(Of IBusinessRule(Of T))
            For Each _rule In _rules
                If _rule.IsBrokenBy(Item) Then
                    _brokenRules.Add(_rule)
                End If
            Next
            Return _brokenRules
        End Function
 
        Public ReadOnly Property BrokenRulesCount() As Integer Implements IBusinessRuleSet(Of T).BrokenRulesCount
            Get
                If _brokenRules Is Nothing Then
                    Return 0
                End If
                Return _brokenRules.Count
            End Get
        End Property
 
        Public ReadOnly Property RulesCount() As Integer Implements IBusinessRuleSet(Of T).RulesCount
            Get
                Return _rules.Count
            End Get
        End Property
 
        Public Function IsBroken(ByVal Item As T) As Boolean Implements IBusinessRuleSet(Of T).IsBroken
            If BrokenBy(Item).Count > 0 Then Return True
            Return False
        End Function
 
        Public Function Messages(ByVal Item As T) As System.Collections.Generic.IList(Of String) Implements IBusinessRuleSet(Of T).Messages
            Dim _l As New List(Of String)
            For Each _rule In BrokenBy(Item)
                _l.Add(_rule.Description)
            Next
            Return _l
        End Function
    End Class
End Namespace
Namespace Model.Validation
    Public Class BusinessRuleSet(Of T)
        Implements IBusinessRuleSet(Of T)

        Private _rules As IList(Of IBusinessRule(Of T))
        Private _brokenRules As IList(Of IBusinessRule(Of T))

        Public Sub New(ByVal Rules As IList(Of IBusinessRule(Of T)))
            _rules = Rules
        End Sub

        Public Function BrokenBy(ByVal Item As T) As System.Collections.Generic.IList(Of IBusinessRule(Of T)) Implements IBusinessRuleSet(Of T).BrokenBy
            _brokenRules = New List(Of IBusinessRule(Of T))
            For Each _rule In _rules
                If _rule.IsBrokenBy(Item) Then
                    _brokenRules.Add(_rule)
                End If
            Next
            Return _brokenRules
        End Function

        Public ReadOnly Property BrokenRulesCount() As Integer Implements IBusinessRuleSet(Of T).BrokenRulesCount
            Get
                If _brokenRules Is Nothing Then
                    Return 0
                End If
                Return _brokenRules.Count
            End Get
        End Property

        Public ReadOnly Property RulesCount() As Integer Implements IBusinessRuleSet(Of T).RulesCount
            Get
                Return _rules.Count
            End Get
        End Property

        Public Function IsBroken(ByVal Item As T) As Boolean Implements IBusinessRuleSet(Of T).IsBroken
            If BrokenBy(Item).Count > 0 Then Return True
            Return False
        End Function

        Public Function Messages(ByVal Item As T) As System.Collections.Generic.IList(Of String) Implements IBusinessRuleSet(Of T).Messages
            Dim _l As New List(Of String)
            For Each _rule In BrokenBy(Item)
                _l.Add(_rule.Description)
            Next
            Return _l
        End Function
    End Class
End Namespace

And now for the Validator class that you will be using all over the place if needed ;-) :

Text
1
2
3
4
5
6
7
Namespace Model.Validation
    Public Interface IValidator(Of T)
        Function IsValid(ByVal Item As T) As Boolean
        Function Validate(ByVal Item As T) As IList(Of IBusinessRule(Of T))
        Function Messages(ByVal Item As T) As IList(Of String)
    End Interface
End Namespace
Namespace Model.Validation
    Public Interface IValidator(Of T)
        Function IsValid(ByVal Item As T) As Boolean
        Function Validate(ByVal Item As T) As IList(Of IBusinessRule(Of T))
        Function Messages(ByVal Item As T) As IList(Of String)
    End Interface
End Namespace

And the implementation:

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Imports TDB2009.Dal.Model.Common.Interfaces
 
Namespace Model.Validation
    Public MustInherit Class Validator(Of T As IDomainEntity)
        Implements IValidator(Of T)
 
        Private _rules As IBusinessRuleSet(Of T)
 
        Public Sub New(ByVal Rules As IBusinessRuleSet(Of T))
            _rules = Rules
        End Sub
 
        Public Function IsValid(ByVal Item As T) As Boolean Implements IValidator(Of T).IsValid
            Return Not _rules.IsBroken(Item)
        End Function
 
        Public Function Validate(ByVal Item As T) As IList(Of IBusinessRule(Of T)) Implements IValidator(Of T).Validate
            Return _rules.BrokenBy(Item)
        End Function
 
        Public Function Messages(ByVal Item As T) As IList(Of String) Implements IValidator(Of T).Messages
            Return _rules.Messages(Item)
        End Function
    End Class
End Namespace
Imports TDB2009.Dal.Model.Common.Interfaces

Namespace Model.Validation
    Public MustInherit Class Validator(Of T As IDomainEntity)
        Implements IValidator(Of T)

        Private _rules As IBusinessRuleSet(Of T)

        Public Sub New(ByVal Rules As IBusinessRuleSet(Of T))
            _rules = Rules
        End Sub

        Public Function IsValid(ByVal Item As T) As Boolean Implements IValidator(Of T).IsValid
            Return Not _rules.IsBroken(Item)
        End Function

        Public Function Validate(ByVal Item As T) As IList(Of IBusinessRule(Of T)) Implements IValidator(Of T).Validate
            Return _rules.BrokenBy(Item)
        End Function

        Public Function Messages(ByVal Item As T) As IList(Of String) Implements IValidator(Of T).Messages
            Return _rules.Messages(Item)
        End Function
    End Class
End Namespace

And this is an example of an implemented BusinessRuleset:

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Imports TDB2009.Dal.Model.Validation
 
Namespace Model.AnalysisManagement.Validators.Rulesets
    Public Class FiberTypeRulesSet
        Inherits BusinessRuleSet(Of FiberType)
        Implements Interfaces.IFiberTypeRulesSet
 
        Public Sub New()
            MyBase.New(MakeRules)
        End Sub
 
        Private Shared Function MakeRules() As IList(Of IBusinessRule(Of FiberType))
            Dim _Rules As New List(Of IBusinessRule(Of FiberType))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyDescriptionEn", "Description En can not be empty", Function(x) Not String.IsNullOrEmpty(x.Description.En)))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyDescriptionFr", "Description Fr can not be empty", Function(x) Not String.IsNullOrEmpty(x.Description.Fr)))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyDescriptionNl", "Description Nl can not be empty", Function(x) Not String.IsNullOrEmpty(x.Description.Nl)))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyKey", "Key can not be empty", Function(x) Not String.IsNullOrEmpty(x.FiberType)))
            Return _Rules
        End Function
    End Class
End Namespace
Imports TDB2009.Dal.Model.Validation

Namespace Model.AnalysisManagement.Validators.Rulesets
    Public Class FiberTypeRulesSet
        Inherits BusinessRuleSet(Of FiberType)
        Implements Interfaces.IFiberTypeRulesSet

        Public Sub New()
            MyBase.New(MakeRules)
        End Sub

        Private Shared Function MakeRules() As IList(Of IBusinessRule(Of FiberType))
            Dim _Rules As New List(Of IBusinessRule(Of FiberType))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyDescriptionEn", "Description En can not be empty", Function(x) Not String.IsNullOrEmpty(x.Description.En)))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyDescriptionFr", "Description Fr can not be empty", Function(x) Not String.IsNullOrEmpty(x.Description.Fr)))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyDescriptionNl", "Description Nl can not be empty", Function(x) Not String.IsNullOrEmpty(x.Description.Nl)))
            _Rules.Add(New BusinessRule(Of FiberType)("NotEmptyKey", "Key can not be empty", Function(x) Not String.IsNullOrEmpty(x.FiberType)))
            Return _Rules
        End Function
    End Class
End Namespace