How to implement BrokenRulesCollection in Lists

How to implement BrokenRulesCollection in Lists

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


rosjon posted on Tuesday, May 15, 2007

Hi,

My webapplication gives the user the possibility to change a lot of things about a Store,for instance openinghours and openinghours exception(a store will have openinghours 10-20 each day but on may 1st it is open only 10-14). Where do I implement a validationrule for unique dates, that is only one openinghoursexception on may 1st. My users wants this errormessage when they save the store not when adding the exception as it will require a postback.

There is no brokenrulescollection for lists, so I cant implement this rule in the list. I could add this rule to the Store object (having an OpeningHoursExceptionList) but the rule doesnt only apply to stores, it applies to every thing having openinghours exceptions. And sometimes this list is not "owned" by any parent, as it is possible to create a template list of openinghours exceptions to be used for new stores.

I have several other types of list with list specific validation. I have a list of concepts where the names of the concepts must be unique.Other examples of rules are that only three of the same types of store in the same area e.t.c.

Anyone have any suggestions? Why doesnt the lists have a brokenrulescollection and why cant u add businessrule to them?

It is impossible to create your own implementation of Brokenrulescollection as you cant inherit it (sealed) and not instatiate it(internal constructor)

 

 

david.wendelken replied on Tuesday, May 15, 2007

rosjon:

It is impossible to create your own implementation of Brokenrulescollection as you cant inherit it (sealed) and not instantiate it(internal constructor)

Check out the CslaContrib project.   (Links are on the forum home page).

Download the CslaContrib code, and Look for the CslaSrd project inside of it.

If they don't do what you want, you might still pick up some useful background info.

Classes of interest:

RuleBusinessBase -> subclasses BusinessBase, includes a BrokenRules and Rules property.

RuleBusinessListBase -> subclasses BusinessListBase

PublicRuleInfo and PublicRuleInfoList (a collection of all rules)

Resources File (rule error messages rewritten slightly so PublicRuleInfo can display them.)

StdRules -> a clone of CommonRules where the rules use the new Resources file error strings.

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

other classes of interest, but off this topic, are SmartInt16, SmartInt32, SmartInt64, SmartFloat, SmartDecimal and SmartBool, plus SmartSafeDataReader (which adds support for the new smart classes.)

 

 

 

RockfordLhotka replied on Tuesday, May 15, 2007

rosjon:

Anyone have any suggestions? Why doesnt the lists have a brokenrulescollection and why cant u add businessrule to them?

The reason lists don't have this concept, is because data binding has no way to display such error data. There's no equivalent to IDataErrorInfo for lists, and so putting a list into an invalid state with a list of broken rules is problematic - there's no pre-existing way to display such error information to the user in Windows, Web or WPF.

I typically just override Save() and check my list-based rules there - throwing a ValidationException if a rule is violated. The UI can handle that exception and give the user some meaningful display indicating the nature of the problem.

Even if BrokenRulesCollection were public, it wouldn't be useful. A broken rules collection from a list wouldn't be listing broken rules per-property, but rather perhaps per-child or something like that. The existing BRC is the wrong type to provide this sort of data. Really, I imagine what you would need from a list is much simpler - just a list of human-readable strings, because they'd never be attached to a specific property, and really shouldn't be attached to a specific child (because if that were the case than that child should be the object that is invalid).

So what you could do is create your own subclass of BLB, declare a protected virtual method like CheckRules() and override Save(). Also in your subclass you'd define a new public property that returns a read-only list of string values.

In Save() you would:

  1. Clear the list of string values
  2. Invoke CheckRules()
  3. If list-of-string-values.Length>0 throw a ValidationException
  4. If list-of-string-values.Length=0 continue
  5. Call MyBase.Save()

This would roughly mirror the behavior of BusinessBase, and your UI, after catching the exception, could figure out how to display the list of errors to the user.

Obviously your actual business collections would override CheckRules() and would do any collection-level rule-checking there - adding a human-readable text description of any errors into the list of strings.

david.wendelken replied on Wednesday, May 16, 2007

Hmm... As a work-around, here's a completely untested idea. :)

  1. Write code in the list to check thru the list for whatever problems you are concerned about.  It should return a go/no-go status or a detailed message, as needed.  (The code could also be inside the object contained in the list.)
  2. Create a rule on the object contained in the list.  Have it invoke the method in the list.

I think this should work even for empty lists, because as you add objects to it, they will follow the rules.

If you expose the child object's broken rules collection, it can be databound and thus displayed.

As a possible optimization, if you don't want it looping thru the list with every update of every object in it, you might have the rule automatically pass if it's not the first object in the list.  If you go this route, you might need to modify the save logic in the list to check whether the first object has any broken rules before saving.  Might need some additional UI coding to make for a more pleasant user experience, as a record they weren't working on could suddenly get broken.

Hope that gives you some ideas.  Let us know what works!

JoeFallon1 replied on Wednesday, May 16, 2007

Check out this thread for one way to implement rules for collections. I use this for my ASP.Net app to return a list of broken rules.

http://forums.lhotka.net/forums/post/7444.aspx

Joe

 

xal replied on Wednesday, May 16, 2007

While this is not strictly related, it might be relevant. I wrote a blog post recently on validating date ranges inside a collection.
My rationale here is that (with the exception of some very very rare cases) the collection is not the one that is "invalid". It's the item inside the collection that's breaking a rule, and thus, you should be able to inform the user which item is invalid. This of course sometimes involves checking data in other lists or within the items contained in the same list (like my scenario of validating date ranges).
You can read more about it and get the code here.


Andrés

ben replied on Tuesday, October 02, 2007

Hi Andrés,

 

I’m afraid you have a BUG in your code. Please try this:

 

1 Execute your example application.

2 Go to the last row of the grid.

3 Type “18/12/2014” in the StartDate column. See that this change makes a conflict with previous row which is marked as invalid (IsValid = False) but the last row isn’t marked as invalid. The last item is not Validated.

 

The problem is in “DateRangeCollection.vb“. Here you have a possible solution:

 

<Serializable()> _

Public Class DisjointDecimalIntervalList(Of T As DisjointDecimalIntervalList(Of T, C), C As DisjointDecimalInterval(Of C))

 

...

 

Protected Sub ValidateRange(ByVal typeRange As List(Of C), ByVal addedItem As C)

    If typeRange.Count = 0 Then

        'nothing to do

        Exit Sub

    ElseIf typeRange.Count = 1 Then

        'only one item can't

        typeRange(0).ResetDateRangeOverlap()

        typeRange(0).ValidateRange()

        Exit Sub

    End If

    'In order to avoid unnecesary nested loops, we make sure none are marked as overlapping first.

    'This will not trigger unnecesary validation routines inside the object.

    Dim adding As Boolean = addedItem IsNot Nothing

    If Not adding Then

        For Each i As C In typeRange

            i.ResetDateRangeOverlap()

        Next

    Else

        addedItem.ResetDateRangeOverlap()

    End If

    Dim total As Integer = typeRange.Count - 1

    'If Not adding Then

    '    total -= 1

    'End If

    For idxi As Integer = 0 To total - 1

        Dim i As C = typeRange(idxi)

        If adding Then

            If Not ReferenceEquals(i, addedItem) Then

                If Not i.DateRangeOverlap Then

                    If i.IsDateRangeOverlapped(addedItem) Then

                        addedItem.DateRangeOverlap = True

                        i.DateRangeOverlap = True

                    End If

                ElseIf Not addedItem.DateRangeOverlap Then

                    addedItem.DateRangeOverlap = addedItem.IsDateRangeOverlapped(i)

                End If

            End If

        Else

            For idxj As Integer = idxi + 1 To total

                Dim j As C = typeRange(idxj)

                If Not ReferenceEquals(i, j) Then

                    If Not i.DateRangeOverlap OrElse Not j.DateRangeOverlap Then

                        If i.IsDateRangeOverlapped(j) Then

                            i.DateRangeOverlap = True

                            j.DateRangeOverlap = True

                            'Exit For

                        End If

                    End If

                End If

            Next

        End If

        i.ValidateRange()

    Next

    If adding Then

        addedItem.ValidateRange()

    Else ' Init ********  Solution to the BUG! ***********

        Dim lastItem As C = typeRange(total)

        lastItem.ValidateRange()

        ' End ********  Solution to the BUG! ***********

    End If

End Sub

...

 

Tank you very much for your contribution. It has been very helpful to me.

 

Benjamin

ben.m.s(at)terra.es

 

 

PS. I am from Spain so you can speak (write) in Spanish to me if you want to.

Copyright (c) Marimer LLC