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:
vbnet
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:
```vbnet 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:
vbnet
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:
```vbnet 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
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:
```vbnet 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:
```vbnet 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```