Cascading dependent properties

Cascading dependent properties

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


nelis posted on Tuesday, January 12, 2010

Hi, I would assume that the business/validation rules for dependent properties would also trigger validation of their dependent properties (regardless of BypassPropertyChecks). At least in cases where the business rule is used to change a property's value. Example: Text change triggers ToUpper to update UpperText. The change of UpperText should than trigger Reverse to update ReverseUpperText. Last update is only performed if I remove the BypassPropertyChecks from ToUpper.

Question: is this intended behavior? Why?

Code:

public class MyObject : BusinessBase<MyObject>

{

private static PropertyInfo<string> TextProperty = RegisterProperty<string>(c => c.Text);

public string Text

{

get { return GetProperty<string>(TextProperty); }

set { SetProperty<string>(TextProperty, value); }

}

private static PropertyInfo<string> UpperTextProperty = RegisterProperty<string>(c => c.UpperText);

public string UpperText

{

get { return GetProperty<string>(UpperTextProperty); }

private set { SetProperty<string>(UpperTextProperty, value); }

}

private static PropertyInfo<string> ReverseUpperTextProperty = RegisterProperty<string>(c => c.ReverseUpperText);

public string ReverseUpperText

{

get { return GetProperty<string>(ReverseUpperTextProperty); }

private set { SetProperty<string>(ReverseUpperTextProperty, value); }

}

protected override void AddBusinessRules()

{

base.AddBusinessRules();

ValidationRules.AddRule<MyObject>(ToUpper, UpperTextProperty, -1);

ValidationRules.AddDependentProperty(TextProperty, UpperTextProperty);

ValidationRules.AddRule<MyObject>(Reverse, ReverseUpperTextProperty, -1);

ValidationRules.AddDependentProperty(UpperTextProperty, ReverseUpperTextProperty);

}

private static bool ToUpper<T>(T target, global::Csla.Validation.RuleArgs e) where T : MyObject

{

using (target.BypassPropertyChecks)

{

target.UpperText = target.Text.ToUpper();

}

return true;

}

private static bool Reverse<T>(T target, global::Csla.Validation.RuleArgs e) where T : MyObject

{

using (target.BypassPropertyChecks)

{

target.ReverseUpperText = new string(target.UpperText.ToCharArray().Reverse().ToArray());

}

return true;

}

public MyObject() { /* I know this is not CSLA like! */ }

}

[TestMethod]

public void MyObject_CascadingRules()

{

MyObject obj = new MyObject();

obj.Text = "Hello world!";

Assert.AreEqual("Hello world!", obj.Text);

Assert.AreEqual("HELLO WORLD!", obj.UpperText);

Assert.AreEqual("!DLROW OLLEH", obj.ReverseUpperText);

}

JonnyBee replied on Tuesday, January 12, 2010

Hi,

ValidationRules for a property is checked when either of the following occurs:
* PropertyHasChanged is raised
* ValidationRules.CheckRules are executed.

BypassPropertyChecks bypasses both check for Authorication (CanWriteProperty) and the call to PropertyHasChanged (which in turn calls MarksDirty on the object).

Your workaround could be to add a specific call to ValidationRules.CheckRules inside the rule to avoid the object being marked as Dirty as a result of the rule processing.

private static bool ToUpper<T>(T target, global::Csla.Validation.RuleArgs e) where T : MyObject
{

    using
(target.BypassPropertyChecks)
   {
 
         target.UpperText = target.Text.ToUpper();
         ValidationRules.CheckRules(UpperTextProperty.Name);
  
}
}



nelis replied on Wednesday, January 13, 2010

Thanks, however....

* And when the ValidationRules for a property it depends on are checked!

That's what I am pointing at. This is only performed for the 1st level of dependents. Now that I learned from Rocky's video 5 that validation rules can also be used as business rules that update a property's value, I would expect that in such cases the ValidationRules of the dependent's dependents should also be checked (which of course is only useful when the property did change). This should (imo) not have to be triggered by a PropertyHasChanged nor handled by a manual invoke as suggested by JonnyBee.

This of course was a simplified example (I could also have made ReverseUpperText dependent on Text and set the priorities properly). In this case it is rather easy to get the desired result.

What I am trying to do is provide a generic conditional value/expression mechanism. That should allow the developer to specify a condition (Expression<Func<T, bool>>) and a value (Expression<Func<T, [property type]>>). Those expressions are than 'parsed' for the dependencies. The developer using that mechanism shouldn't be bothered about cascading dependencies and my generic code shouldn't have to detect them either (I think ;-).

JonnyBee replied on Wednesday, January 13, 2010

Hi,

The problem with rules that update a property is that you may generate infinite loops.

Remember - there is legacy kode for properties (2.x and 3.0 style) when we did not have managed properties (and you had to call PropertyHasChanged yourself). And there is managed properties with private backingfield and get/set methods that does typeconversion.

If you remove the BypassPropertyChecks (which would probably be right here as the user should not be allowed to modify a property that he/she is not allowed to edit) - then you will get your desired behaviour, ie: rules will for that field will be checked and the object marked as dirty.

private static bool ToUpper<T>(T target, global::Csla.Validation.RuleArgs e) where T : MyObject
{

      target.UpperText = target.Text.ToUpper();
      return true;
}

private static bool Reverse<T>(T target, global::Csla.Validation.RuleArgs e) where T : MyObject
{
    
target.ReverseUpperText = new string(target.UpperText.ToCharArray().Reverse().ToArray());
     return true;
}


Copyright (c) Marimer LLC