Getting all broken rules within an object graph with object graph position for each broken rule

Getting all broken rules within an object graph with object graph position for each broken rule

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


snakebyteme posted on Tuesday, August 31, 2010

We keep running into issues where developers on our team hook up a UI to a large object graph and then have trouble saving the parent object due to invalidity existing in various child objects.

I came up with an idea:

Serialize the object graph to XML and parse the XML to return a list of broken rules with each broken rule containing its object graph position (parent.child.grandchild).

Due to my lack of XML parsing skills, I ended up writing a function that returned the broken rules (without object graph location) using regular expression.

Can someone point me in the right direction on how to parse the XML from Csla.Serialization.NetDataContractSerializerWrapper so I can get the broken rule with the object graph location?

I feel like this could be a real time saver for development teams using CSLA if developers are able to quickly identify all broken rules and their location.

 

    Public Shared Function GetBrokenRules(ByRef obj As Object) As List(Of String)
        GetBrokenRules = New List(Of String)
        Dim responsePattern As String = "RuleName: {1}{0}Severity: {2}{0}Description: {3}"
        Dim brokenRulePattern As String = "\<[a-z]*:*BrokenRule z:Id=""\d+""\>(.*?)\</[a-z]*:*BrokenRule\>"
        Dim descriptionPattern As String = "\<[a-z]*:*_description z:Id=""\d+""\>([\d\D]+)\</[a-z]*:*_description>"
        Dim severityPattern As String = "\<[a-z]*:*_severity\>(.+)\</[a-z]*:*_severity>"
        Dim ruleNamePattern As String = "\<[a-z]*:*_ruleName z:Id=""\d+""\>([\d\D]+)\</[a-z]*:*_ruleName>"

        Dim mySerializer As New Csla.Serialization.NetDataContractSerializerWrapper
        Dim myStream As New MemoryStream
        mySerializer.Serialize(myStream, obj)
        myStream.Position = 0
        Dim myXMLString As String = ""
        Using myStream
            Using myStreamReader As New StreamReader(myStream)
                myXMLString = myStreamReader.ReadToEnd
            End Using
        End Using

        Dim matches As MatchCollection = Regex.Matches(myXMLString, brokenRulePattern)
        myXMLString = Nothing

        For Each itemMatch As Match In matches
            Dim descriptions As MatchCollection = Regex.Matches(itemMatch.Value, descriptionPattern)
            Dim severities As MatchCollection = Regex.Matches(itemMatch.Value, severityPattern)
            Dim ruleNames As MatchCollection = Regex.Matches(itemMatch.Value, ruleNamePattern)
            Try
                GetBrokenRules.Add(String.Format(responsePattern, vbCrLf, Regex.Replace(ruleNames.Item(0).Value, ruleNamePattern, "$1", RegexOptions.IgnoreCase), _
                                                  Regex.Replace(severities.Item(0).Value, severityPattern, "$1", RegexOptions.IgnoreCase), _
                                                  Regex.Replace(descriptions.Item(0).Value, descriptionPattern, "$1", RegexOptions.IgnoreCase)))
            Catch
            End Try

        Next

    End Function

JonnyBee replied on Tuesday, August 31, 2010

Hi,

There is code for this functionality for WindowsForms in CslaContrib.  See ErrorTreeView project on http://cslacontrib.codeplex.com and there is also an item in the bugtracker for a web version (http://lhotka.net/cslabugs/edit_bug.aspx?id=19)

If you want to go at it in WPF/SL you are welcome to share it on CslaContrib.

snakebyteme replied on Tuesday, August 31, 2010

That won't work.

Our objects lazy load their children and the ErrorTreeView would load properties into memory which were not there when we asked for the broken rules of the parent object.

That is the reason why I serialize the object and want to parse the XML nodes, and not the object itself.

I just need to know how to parse the XML returned from the serializer to get the "Broken Rules XML"

JonnyBee replied on Tuesday, August 31, 2010

Ok,

But I'd just like to add that this is still in the wish list for csla: http://www.lhotka.net/cslabugs/edit_bug.aspx?id=573 

I have a class at work that will handle managed lazy loaded properties correctly and aggregate the broken rules in an object graph.
You shouldn't have to serialize the entire object graph and then parse the XML to build a tree of broken rules.

 

 

chrduf replied on Wednesday, September 01, 2010

Hi

Issue 573, Add a GetAllBrokenRules() method that returns all broken rules for an object graph ( http://lhotka.net/cslabugs/edit_bug.aspx?id=573 )  is in the wish list with sample code that will return a consolidated list of all the current broken rules. This routine will not cause the lazy loaded to load.

Here is an updated version of the routine for CSLA 4.0

    public interface IGetAllBrokenRules
    {
        BrokenRulesCollection GetAllBrokenRules();
    } 

    /// <summary>

    /// This is the base class from which most business objects

    /// will be derived.

    /// </summary>

    /// <typeparam name="T">Type of the business object being defined.</typeparam>

    [Serializable()]

    public abstract class MyBusinessBase<T> : BusinessBase<T>, IGetAllBrokenRules where T : MyBusinessBase<T>

    {

 

        public BrokenRulesCollection GetAllBrokenRules()

        {

            BrokenRulesCollection list = new BrokenRulesCollection();

            if (FieldManager.HasFields)

            {

                // add broken rules at the root object level

                list.AddRange(this.BrokenRulesCollection);

 

                // get children objects of the current instance

                foreach (object child in this.FieldManager.GetChildren())

                {

                    // see if child implements ICPBrokenRules - if it does then you can call the GetAllBrokenRules method

                    if ((child is IGetAllBrokenRules) && (child as IEditableBusinessObject != null) && (((IEditableBusinessObject)child).IsValid == false))

                    {

                        list.AddRange(((IGetAllBrokenRules)child).GetAllBrokenRules());

                    }

                    else

                    {

                        // see if the object is a IEditableCollection - then it should be of type CPBusinessListBase

                        if (child is IEditableCollection)

                        {

                            // go and call the same function for every child in the collection since they must be of type IEditable

                            // determine if the object is of type CPBusinessListBase - if so, cast it to the corresponding type,

 

                            foreach (object nefew in (IEnumerable)child)

                            {

                                if (nefew is IGetAllBrokenRules)

                                {

                                    list.AddRange((nefew as IGetAllBrokenRules).GetAllBrokenRules());

                                }

                            }

                        }

                    }

 

                }

            }

            return list;

        }

 

    }

 

snakebyteme replied on Wednesday, September 08, 2010

Thanks for the code snippet.

The only issue I have is that a BrokenRulesCollection does not contain enough data.

Since you can have multiple invalidities in a List(of T), the only way to tell what is invalid in a collection is by the path taken to access the object.

The broken rule class only contains the callback name, property name and a description field.

For a "top-down" view of all broken rules, the result set will need the following attributes to be useful:

AccessPath (Where): ie. Master.ChildList(2).GrandChild

PropertyValue (What): (this is helpful to not have to access the object a second time)

BrokenRule (Who and Why):

 

I'll try to modify your routine to return my custom structure with the additional data.

It looks like I'll have access to the additional fields that I need.

Once again, thank you for providing the code.

JJLoubser replied on Tuesday, February 22, 2011

this is not working in CSLA.net 4.1

 

is there a diffrent class we can use to get the childrens brokenrules?

 

my attempt:

 

private BrokenRulesCollection GetAllBrokenRules()
        {
            BrokenRulesCollection list = new BrokenRulesCollection();
            if (FieldManager.HasFields)
            {
                list.AddRange(this.BrokenRulesCollection);
                foreach (object child in this.FieldManager.GetChildren())
                {
                   

                    ITrackStatus targetObject = child as ITrackStatus;

                    if (targetObject != null)
                    {
                        if (!targetObject.IsValid)
                        {
                            var object_ = targetObject.GetType();
                            string str_ = object_.ToString();

                            list.AddRange(((BusinessRules)child).GetAllBrokenRules()); // problem here, what class can I use?
                        }
                    }


//not working in csla.net 4.1

                   //if ((child is IGetAllBrokenRules) && (child as IEditableBusinessObject != null) && (((IEditableBusinessObject)child).IsValid == false))
                    //{
                    //    list.AddRange(((IGetAllBrokenRules)child).GetAllBrokenRules());
                    //}
                    //else
                    //{
                    //    if (child is IEditableCollection)
                    //    {
                    //        foreach (object nefew in (IEnumerable)child)
                    //        {
                    //            if (nefew is IGetAllBrokenRules)
                    //            {
                    //                list.AddRange((nefew as IGetAllBrokenRules).GetAllBrokenRules());
                    //            }
                    //        }
                    //    }
                    //}

                }
            }
            return list;
        }

JonnyBee replied on Tuesday, February 22, 2011

Hi,

Have you tried to call
     BrokenRules.GetAllBrokenRules(<businessobject>);

This mehod will run through the entire object graph and return a BrokenRulesTree - a flat list with

JJLoubser replied on Tuesday, February 22, 2011

that class is static, do not want to use static.

 

the class I want is IManageProperties, but I cannot access it from Csla.Core, why?

 

this is what i want to do:

 

Type thisType = child.GetType();

                                System.Reflection.MethodInfo theMethod = thisType.GetMethod("BrokenRulesCollection");

                                if (theMethod != null) //means it is a bussiness base class
                                {
                                    BrokenRulesCollection childrenBrokenRules = (BrokenRulesCollection)theMethod.Invoke(child, null);
                                    list.AddRange(childrenBrokenRules);

                                }
                                else // means it is a bussiness list base class
                                {


                                    foreach (var grandchild in ((Csla.Core.IManageProperties)child).GetChildren()) // not working, cannot access the class
                                    {
                                        Type thisChildType = grandchild.GetType();

                                        System.Reflection.MethodInfo theChildMethod = thisChildType.GetMethod("BrokenRulesCollection");

                                        if (theChildMethod != null) //means it is a bussiness base class
                                        {
                                            BrokenRulesCollection childrenBrokenRules = (BrokenRulesCollection)theChildMethod.Invoke(grandchild, null);
                                            list.AddRange(childrenBrokenRules);

                                        }
                                    }



                                }

 

make sense?

JJLoubser replied on Tuesday, February 22, 2011

thanks got it like this:

 

 public string getErrorStr
        {

            get
            {

                string str = "";



                //BrokenRulesCollection Collection_ = GetAllBrokenRules();
               
                BrokenRulesTree Collection_ = BusinessRules.GetAllBrokenRules(this);
               
                StringBuilder sb = new StringBuilder(20000);

                sb.AppendLine(" ");

                foreach (BrokenRulesNode items in Collection_)
                {
                    sb.AppendLine(" " + items.Object.ToString());
                  
                    if (items.BrokenRules != null && items.BrokenRules.Count > 0)
                        foreach (BrokenRule item in items.BrokenRules)
                        {
                            sb.AppendLine("- Description: " + item.Description + " Priority: " + item.Severity + " Where? at " + item.Property);
                        }
                }

                str = sb.ToString();

                return str;

            }

        }

ajj3085 replied on Wednesday, February 23, 2011

Not wanting to call the method because its static is a silly reason to not use it.

You're digging in deep via reflection and doing a lot of fragile error prone coding to avoid the use of a method that's already there that seems to do what you want.

What is public and what is kept internal is a decision made to increase the ability to change Csla over time.  Once something is public, much more care must be taken when changing it, and design mistakes are that much harder to fix because they could be breaking changes for everyone to upgrade. 

RockfordLhotka replied on Wednesday, February 23, 2011

This thread made my laugh out loud (no offense meant). But seriously :)

The desire to have a way to get a consolidated list of broken rules was on the wish list for a long time. Doing this in a maintainable and useful manner took some thought, and the solution we implemented reflects those requirements.

Coupled with LINQ queries to manipulate the results, I think our solution is a good one that should meet nearly every need.

I am curious therefore, as to how the fact that the solution is in a static method can have any bearing on its value?

jpmir replied on Friday, March 11, 2011

Hi Rocky,

You said:

"Coupled with LINQ queries to manipulate the results, I think our solution is a good one that should meet nearly every need"

... and it seems to be good enough but, Do you have any code snippet or sample we could read to understand how to present BrokenRulesCollections to end users using this approach?

Thanks in advance!

Jean Paul

Charleh replied on Saturday, March 12, 2011

I am currently using reflection to get the broken rules in a tree structure which contains the object reference and it's broken rules as child members of the collection. This can be bound to a tree view and shows the user what the issues were at each level (using ToString on each business object to get the description for the user) - the performance isn't too bad but this method obviously causes a lazyload of the children when inspecting the properties.

The benefit of this method is that user is more aware of where the errors are if they are editing a parent or deep in a grandchild

We had issues where the original dev for one of our systems had code like this:

if(!bizObj.IsValid) MessageBox.Show(bizObj.BrokenRules.

ToString());

Of course - this only gives you a list of rules on bizObj! Any children which may be invalid do not show - and if there are many children, identifying where the error is quickly becomes a headache. I often found that users were presented with a blank box! Not very helpful!

I created a collection class called BusinessError (BusinessError : List<BusinessError>) - that way I could just flag each object as a brokenrule or a business object with broken rules and hold a list of the broken rules or a reference to the invalid object and its broken rules and any children with broken rules (phew did that make sense?!)

Binding took care of the rest

Also useful is that this box can show warnings on the object and information messages - they don't show up as validation errors in standard binding, so this supports these brokenrule types by allowing the user to view them before saving (they get an extra button to save as long as there are no error severity rules)

Creating a base class which gets the broken rules as a tree structure might be useful - so a logical extension of chrduf's code might be to get the broken rules in a tree with references to the object using that method. Don't forget to subclass your business objects though - or you might be scratching your head :)

Copyright (c) Marimer LLC