Recommendations for BrokenRules handling when using interfaces

Recommendations for BrokenRules handling when using interfaces

Old forum URL: forums.lhotka.net/forums/t/1220.aspx


Henrik posted on Thursday, September 14, 2006

Hi all

I’m currently implementing an application that uses the party/role concept. For instance I have a Party class, that has a collection of Addresses and a collection of ContactMethods. The Party class can represent either a person or an organization.
I have created IPerson and IOrganization interfaces which the Party class implements. For example, the IPerson interface has FirstName, MiddleName and LastName properties whereas the IOrganization interface only has an OrganizationName property.
All of these classes and interfaces are put into a separate assembly to make them as reusable as possible.

In my application I have several root classes such as Doctor, School, Institution and so on, that each has a Party object embedded using composition. Each of these classes implement either the IPerson or the IOrganization interface which passes the properties on to the embedded Party object. This is to save the front-end developer from having to worry about whether or not to fill out first-, middle and lastname or organizationname, depending on which type of Party he/she is dealing with.

All this works like a charm. However, if a rule is broken in the Party object or one of it’s Address or ContactMethod objects I can’t figure out how to get to these. They won’t be in the root objects BrokenRulesCollection. I could add an extra property, to the root object, that returns the Party object’s BrokenRulesCollection, but this, kind of, doesn’t “feel” right.
I thought of, instead of implementing the IPerson or IOrganization interface directly in my root object, I would have a property that returns the Party object, but still I would like to return it as either one of the interface types and then I’m stuck with the same problem.

Doing some investigation into the depths of the framework I could of course implement an IBrokenRules interface that both the IPerson and IOrganization interfaces inherits from. This actually leads to my question(s). Is this the right way to go or am I missing something entirely? Is it correct of me to go the interface route in this situation or shouldn’t I use interfaces and just return the plain business objects and let it be up the the front-end developer to figure out which properties to set?

Thanks in advance
Henrik

JoeFallon1 replied on Saturday, September 16, 2006

<quote>I could add an extra property, to the root object, that returns the Party object’s BrokenRulesCollection, but this, kind of, doesn’t “feel” right.</quote>

My view is that a master BO which contains other BOs should be able to list out all rules that are broken using a single call. This is why I implemented AllRules in my custom layer that inherits from Csla. It was posted in the old message board and I have been using it for a long time now.

It can be called like this:

=============================================================

AllRules.GetAllBrokenRulesString(mSomeBO)

=============================================================

<Serializable()> _
  Public MustInherit Class MyBusinessBase(Of T As MyBusinessBase(Of T))
    Inherits BusinessBase(Of T)

#Region " AllRules "

    Private Shared BIZ_TYPE As Type = GetType(MyBusinessBase(Of T))
    Private Shared COLL_TYPE As Type = GetType(MyBusinessListBase(Of Core.BusinessBase))

    Public Function GetAllBrokenRulesString() As String
      Dim sb As New Text.StringBuilder
      Return GetBrokenRulesString(Me, sb)
    End Function

    Private Function GetBrokenRulesString(ByVal root As MyBusinessBase(Of T), ByRef sb As Text.StringBuilder) As String
      Dim j, k As Integer
      Dim t As Type = root.GetType()
      Dim obj As MyBusinessBase(Of T)
      Dim coll As MyBusinessListBase(Of Core.BusinessBase)
      Dim strg As String = root.BrokenRulesCollection.ToString

      If Len(strg) > 0 Then
        sb.Append("(" & root.Table & ")" & vbCrLf & strg & vbCrLf)
      End If

      Dim fi As System.Reflection.FieldInfo() = t.GetFields((BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.NonPublic))
      For j = 0 To fi.Length - 1
        If fi(j).FieldType.IsSubclassOf(COLL_TYPE) Then
          coll = CType(fi(j).GetValue(root), MyBusinessListBase(Of Core.BusinessBase))
          If Not coll Is Nothing Then
            'this is a child collection, cascade the call
            For k = 0 To coll.Count - 1
              obj = CType(coll(k), MyBusinessBase(Of T))
              If Not obj Is Nothing Then
                GetBrokenRulesString(obj, sb)
              End If
            Next
          End If
        ElseIf fi(j).FieldType.IsSubclassOf(BIZ_TYPE) Then
          obj = CType(fi(j).GetValue(root), MyBusinessBase(Of T))
          If Not obj Is Nothing Then
            GetBrokenRulesString(obj, sb)
          End If
        End If
      Next
      Return sb.ToString
    End Function

#End Region

  End Class


 

skagen00 replied on Saturday, September 16, 2006

Hello Henrick,

I have to say that I agree with Joe, but I also wanted to mention that we're following the contact module design that you're talking about as well - I've seen different flavors of it but the terms contact mechanism and party roles are unmistakable.

Feel free to contact me if you'd ever like to discuss particular challenging areas (contact mechanisms and the sharing of them is a challenging aspect imo).

Regards,

Chris

 

Henrik replied on Monday, September 18, 2006

Chris

Please see my reply to Joe Fallon for how I'm going to solve my problem.

This is my 6th application where I use the party/role concept. Some of my app's just use the party and it's address and contactmethod collections as simple datacontainers where each person or organization has it's own set og addresses and contactmethods. In only two of my applications have I implemented sharing of contact mechanisms and I agree with you, it's very challenging. It isn't trivial.

If I run into other challeging areas of these concepts I will take you up on your offer. You may also contact me, if there is any info I can give you.

I picked the party/role concept up from Len Silverston's book "The Data Model Resource Book" volume 1 & 2, which I can highly recommend. He presents a conceptual model og how to deal with people and organizations in a combined party concept, which roles these parties play and how they relate to each other.

/Henrik

Henrik replied on Monday, September 18, 2006

Joe

Thank you for your suggestion. I have actually come across your function on the old messageboard, but had forgotten about it. I particularly like that the method is generic and can be implemented in BusinessBase.
The method will work perfectly for objects “acting-as” another object, by  implementing it’s interface and hiding the actual object from the front-end developer.

For child-objects visible to the front-end developer I’m not sure that returning a single string with all broken rules for all child-objects will solve my problem. Many of my UI’s contains multiple tab pages and I would like to be able to pinpoint the exact fields that is causing validation errors. If I just return a string with all broken rules for the root and all child-objects I cannot distinguish them from each other. In this situation I will still rely on the “GetBrokenRulesCollection” of each child object.

/Henrik

SoftwareArchitect replied on Tuesday, October 10, 2006

Joe,

I have been trying to port this over to simply a shared function that I pass a business object to...that is, I don't want to modify the csla and I am hoping that I don't have to inherit from a specialized class

After playing around with this for a while I feel really stupid.  I think it is my understanding of generics...but how can I check for a type of business list base.  The following lines are the ones that I can't seem to get. 

Any pointers (from anyone) would be greatly appreciated!  I am having a dumb afternoon.


Private Shared COLL_TYPE As Type = GetType(MyBusinessListBase(Of Core.BusinessBase))
Dim coll As MyBusinessListBase(Of Core.BusinessBase)
coll = CType(fi(j).GetValue(root), MyBusinessListBase(Of Core.BusinessBase))

I don't have a MyBusinessListBase and I don't know what type my BusinessListBase will be!

Thanks,
Mike

JoeFallon1 replied on Tuesday, October 10, 2006

Mike,

Turns out this was a tough issue for me too. I finally decided that the use of Generics was incorrect in this code. On a side note you can get COLL_TYPE to compile using this totally unintuitive trick:

Private Shared COLL_TYPE As Type = GetType(MyBusinessListBase(Of , ))

It never occured to me that you could *omit* the T, C parameters!

Anyway - it turns out that "generics are NOT polymorphic". Memorize that one! If you want to treat a bunch of various BOs as the same type then you can't use their generic base class - you need to "find" a common interface. That is one reason that Rocky has been adding so many interfaces to Csla 2.1. If you can't "find" one of his then you need to build your own.

I did not find one for collections so I built this one:

Public Interface IBusinessCollection
    ReadOnly Property Item(ByVal index As Integer, ByVal useInterface As Boolean) As ImyBusinessObject
    ReadOnly Property IsDirty() As Boolean
    ReadOnly Property IsValid() As Boolean
    ReadOnly Property Count() As Integer
  End Interface

Then in my Base class for BusinessListBase I implemented the interface so that all of my concrete collections have it:

#Region " IBusinessCollection Implementation "

    'useInterface is a fake parameter and is only used to change the method signature so we can Overload the Item method.
    'It does not use the Boolean value at all.
    'This way the Base Item method can still return a strongly typed child BO.
    'This Item method returns the Interface version of the child BO as IEditableBusinessObject.
    Public Overloads ReadOnly Property Item(ByVal index As Integer, ByVal useInterface As Boolean) As IMyBusinessObject Implements IBusinessCollection.Item
      Get
        Return MyBase.Item(index)
      End Get
    End Property

    Public Overloads ReadOnly Property Count() As Integer Implements IBusinessCollection.Count
      Get
        Return MyBase.Count
      End Get
    End Property

    Public Overloads ReadOnly Property IsDirty() As Boolean Implements IBusinessCollection.IsDirty
      Get
        Return MyBase.IsDirty
      End Get
    End Property

    'override CSLA2 code to look like this - no short circuiting involved here.
    'run through all the child objects and if any are invalid then the collection is invalid
    'call IsValid on all children so that when we examine Broken Rules we get the right set of values.
    'Rocky stopped looping after he found the first invalid child.
    Public Overrides ReadOnly Property IsValid() As Boolean Implements IBusinessCollection.IsValid
      Get
        Dim result As Boolean = True

        For Each child As C In Me
          If Not child.IsValid Then
            result = False
          End If
        Next

        Return result
      End Get
    End Property

#End Region

I did something similar for Root BOs.

Public Interface IMyBusinessObject
    Inherits IEditableBusinessObject
    ReadOnly Property BrokenRulesCollection() As Validation.BrokenRulesCollection
    Property Table() As String
    Property IsOwner() As Boolean
    ReadOnly Property OwnedObjects() As IList
    ReadOnly Property OwnedCollections() As IList
  End Interface

#Region " IMyBusinessObject Implementation "

    Public Overrides ReadOnly Property BrokenRulesCollection() As Validation.BrokenRulesCollection Implements IMyBusinessObject.BrokenRulesCollection
      Get
        Return MyBase.BrokenRulesCollection
      End Get
    End Property

    Public Property Table() As String Implements IMyBusinessObject.Table
      Get
        Return mTable
      End Get
      Set(ByVal value As String)
        mTable = value
      End Set
    End Property

    'set to True if the derived BO contains other BOs.
    'Allows the derived classes to skip overriding of IsDirty and IsValid.
    Public Property IsOwner() As Boolean Implements IMyBusinessObject.IsOwner
      Get
        Return mIsOwner
      End Get
      Set(ByVal value As Boolean)
        mIsOwner = value
      End Set
    End Property

    'Extract just the directly owned objects
    Protected ReadOnly Property OwnedObjects() As IList Implements IMyBusinessObject.OwnedObjects
      Get
        If mOwnedObjects Is Nothing Then
          GetOwnedFields()
        End If

        Return mOwnedObjects
      End Get
    End Property

    'Extract just the directly owned collections
    Protected ReadOnly Property OwnedCollections() As IList Implements IMyIBusinessObject.OwnedCollections
      Get
        If mOwnedCollections Is Nothing Then
          GetOwnedFields()
        End If

        Return mOwnedCollections
      End Get
    End Property

    'fill the 2 array lists once with the Root BOs and Editable Collections and use many times in Property gets above.
     Private Sub GetOwnedFields()
      mOwnedCollections = New ArrayList
      mOwnedObjects = New ArrayList

      Dim fields As FieldInfo() = Me.GetType().GetFields(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
      Dim field As FieldInfo

      For Each field In fields
        If GetType(IBusinessCollection).IsAssignableFrom(field.FieldType) Then
          mOwnedCollections.Add(field.GetValue(Me))
        ElseIf GetType(IMyBusinessObject).IsAssignableFrom(field.FieldType) Then
          mOwnedObjects.Add(field.GetValue(Me))
        End If
      Next
    End Sub

#End Region

Then I re-wrote the code for GetBrokenRulesString to use the 2 new interfaces instead of the generic code which did not work.

Public Overrides ReadOnly Property IsDirty() As Boolean
      Get
        If mIsOwner Then
          Return MyBase.IsDirty OrElse CheckDirty(Me)
        Else
          Return MyBase.IsDirty
        End If
      End Get
    End Property

    Private Shared Function CheckDirty(ByVal root As MyBusinessBase(Of T)) As Boolean
      Dim coll As IBusinessCollection

      For Each coll In root.OwnedCollections
        If Not coll Is Nothing Then
          If coll.IsDirty Then
            Return True
          End If
        End If
      Next

      Dim obj As IMyBusinessObject

      For Each obj In root.OwnedObjects
        If Not obj Is Nothing Then
          If obj.IsDirty Then
            Return True
          End If
        End If
      Next

      Return False
    End Function

    'we always want to know all broken rules so do not bail out of the logic early if the parent container object is invalid.
    'Do NOT change the "And" to "AndAlso".
    'Many BOs have code in IsValid to make a final decision on whether or not a rule is broken. If the code never gets called
    'then when we list out AllRules.GetAllBrokenRulesString we may not be correct.
    Public Overrides ReadOnly Property IsValid() As Boolean
      Get
        If mIsOwner Then
          'Do NOT change the "And" to "AndAlso".
          Return MyBase.IsValid And CheckValid(Me)
        Else
          Return MyBase.IsValid
        End If
      End Get
    End Property

    Private Shared Function CheckValid(ByVal root As MyBusinessBase(Of T)) As Boolean
      Dim coll As IBusinessCollection

      For Each coll In root.OwnedCollections
        If Not coll Is Nothing Then
          If Not coll.IsValid Then
            Return False
          End If
        End If
      Next

      Dim obj As IMyBusinessObject

      For Each obj In root.OwnedObjects
        If Not obj Is Nothing Then
          If Not obj.IsValid Then
            Return False
          End If
        End If
      Next
      Return True
    End Function

    Public Function GetAllBrokenRulesString() As String
      Dim sb As New Text.StringBuilder
      Return GetBrokenRulesString(Me, sb)
    End Function

    Private Function GetBrokenRulesString(ByVal root As IMyBusinessObject, ByRef sb As Text.StringBuilder) As String
      Dim i As Integer
      Dim obj As IMyBusinessObject
      Dim coll As IBusinessCollection
      Dim brokenRules As String = root.BrokenRulesCollection.ToString

      If Len(brokenRules) > 0 Then
        sb.Append("(" & root.Table & ")" & vbCrLf & brokenRules & vbCrLf)
      End If

      For Each coll In root.OwnedCollections
        'this is a child collection, cascade the call
        For i = 0 To coll.Count - 1
          obj = coll.Item(i, True)
          If Not obj Is Nothing Then
            GetBrokenRulesString(obj, sb)
          End If
        Next
      Next

      For Each obj In root.OwnedObjects
        If Not obj Is Nothing Then
          GetBrokenRulesString(obj, sb)
        End If
      Next

      Return sb.ToString
    End Function

Joe

SoftwareArchitect replied on Wednesday, October 11, 2006

Joe,

WOW...thanks for all the work on that.  I will look through it later this week but I didn't want to wait to say "thanks". 

This looks more involved than just inheriting from my own base class (like you did)!  As a contractor, I am looking for the most straightforward approach for those who I will be leaving the code to...I will have to think on this one....

Thanks so much for your quick and very complete reply.

Mike

Annesh replied on Friday, January 30, 2009

When displaying a message to the user about a broken child, I use this linq code which loops through the child properties picking up the invalid business bases within the business list bases (will need a similar query for child business base within currentroot and possibily recursive if more than 2 levels):

var invalidobjs = from prop in CurrentRoot.GetType().GetProperties()
                        where
                            (
                                (prop.PropertyType.GetInterface(typeof (Csla.Core.ITrackStatus).Name) != null)
                                &&
                                (prop.PropertyType.GetInterface(typeof (IQueryable<CHILDOBJ>).Name) != null)
                            )
                        let value = (Csla.Core.ITrackStatus) prop.GetValue(CurrentRoot, null)
                        where ((!value.IsValid) && (value != null))
                        let obj = (IQueryable<CHILDOBJ>)value
                        from b in obj
                        where !b.IsValid
                        select b;
            foreach (var invalidobj in invalidobjs)
            {
                var busobj = invalidobj as Csla.Core.BusinessBase;
                if (busobj == null) continue;
                View.DisplayBrokenRules(busobj.BrokenRulesCollection); // first invalid child
                break;
            }

Copyright (c) Marimer LLC