Nested Business Rules

Nested Business Rules

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


k2so posted on Tuesday, May 04, 2010

My team is considering upgrading some existing code written in CSLA3.5 to 4.0.

One of the major concern we have is the BusinessRules change. We have tested out the ErrorTreeView found in the codeplex contribution, for reporting broken rules with hierarchy, which works fine with 3.5.

Now that 4.0 is introducing this new way in defining BusinessRules, does it affect the BrokenRulesCollection at all and has anyone tried the ErrorTreeView with 4.0 ?

RockfordLhotka replied on Thursday, May 06, 2010

BrokenRulesCollection is essentially the same as it was in 3.x.

The only real difference is that you can now create an instance of BrokenRulesCollection yourself, and load it with data yourself if you'd like - which replaces the Merge() concept from 3.8. This only impacts you if you were creating a single BRC by merging all BRC objects from an object graph - and I don't think the ErrorTreeView does this - I think it keeps all the different BRC objects intact.

k2so replied on Thursday, May 06, 2010

Thanks Rocky,

We are currently not doing merges on the BRC objects, so that shouldn't have any effects

I am in the process of converting the ErrorTreeView to CSLA 4.0 and wondering if BRC.CollectionChanged in 4.0 is same as BRC.ListChanged in 3.x?

RockfordLhotka replied on Thursday, May 06, 2010

That is correct.

k2so replied on Monday, May 10, 2010

Hi Rocky,

I've changed the validations to Rules and BusinessRules using examples found on your blog, and it works fine until when I look at one of the BrokenRules in the BRC, the description is always "object reference not set to ...". Then I checked the CommonRules.MinValue<T>.DefaultDescription and .Format, both are null, and thinking this may be the cause.

So, do we always have to set a default description and format if using any of the common rules?

RockfordLhotka replied on Monday, May 10, 2010

I'm not sure what is causing your issue.

The MinValue and MaxValue rules don't use DefaultDescription - they use localized resource strings.

The Format property is used if it is set, otherwise it is ignored.

k2so replied on Tuesday, May 11, 2010

Thanks Rocky, I will keep looking into it.

Here is my code defining the property and the rule, am I missing anything?

        private static PropertyInfo<int> CannotBeNegativeProperty = RegisterProperty(p => p.CannotBeNegative, "Property", -1);
        public int CannotBeNegative
        {
            get { return GetProperty<int>(CannotBeNegativeProperty); }
            set { SetProperty<int>(CannotBeNegativeProperty, value); }
        }

        protected override void AddBusinessRules()
        {
            // For testing, break this rule by setting to negative integer.
            //ValidationRules.AddRule(CommonRules.IntegerMinValue, new CommonRules.IntegerMinValueRuleArgs(CannotBeNegativeProperty, 0));
            BusinessRules.AddRule(new Csla.Rules.CommonRules.MinValue<int>(CannotBeNegativeProperty, 0));           
        }

RockfordLhotka replied on Tuesday, May 11, 2010

I don't know. Here's my test class:

  [Serializable]
  public class UsesCommonRules : BusinessBase<UsesCommonRules>
  {
    private static PropertyInfo<int> DataProperty = RegisterProperty<int>(c => c.Data, null, 10);
    public int Data
    {
      get { return GetProperty(DataProperty); }
      set { SetProperty(DataProperty, value); }
    }

    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new Csla.Rules.CommonRules.MinValue<int>(DataProperty, 5));
      BusinessRules.AddRule(new Csla.Rules.CommonRules.MaxValue<int>(DataProperty, 15));
    }
  }

Looks like yours... And here's the test code:

    [TestMethod]
    public void MinMaxValue()
    {
      var context = GetContext();
      var root = Csla.DataPortal.Create<UsesCommonRules>();
      context.Assert.IsFalse(root.IsValid);

      context.Assert.AreEqual(10, root.Data);

      root.Data = 0;
      context.Assert.IsFalse(root.IsValid);

      root.Data = 20;
      context.Assert.IsFalse(root.IsValid);

      root.Data = 15;
      context.Assert.IsTrue(root.IsValid);

      context.Assert.Success();
      context.Complete();
    }
  }

So it triggers both rules, and they both do the right thing.

k2so replied on Tuesday, May 11, 2010

Hi Rocky,

The IsValid and IsSelfValid of the objects are correct for me too.

The only problem is the description in the Broken Rules.

 

I traced down to the CSLA source and found the context.InputPropertyValues[PrimaryProperty] value is null triggered right after DataPortal_Create. Any idea?

 

  public class MinValue<T> : BusinessRule
    where T : IComparable
  {
    /// <summary>
    /// Gets the min value.
    /// </summary>
    public T Min { get; private set; }
    /// <summary>
    /// Gets or sets the format string used
    /// to format the Min value.
    /// </summary>
    public string Format { get; set; }

    /// <summary>
    /// Creates an instance of the rule.
    /// </summary>
    /// <param name="primaryProperty">Property to which the rule applies.</param>
    /// <param name="min">Min length value.</param>
    public MinValue(Csla.Core.IPropertyInfo primaryProperty, T min)
      : base(primaryProperty)
    {
      Min = min;
      this.RuleUri.AddQueryParameter("min", min.ToString());
      InputProperties = new List<Core.IPropertyInfo> { primaryProperty };
    }

    /// <summary>
    /// Rule implementation.
    /// </summary>
    /// <param name="context">Rule context.</param>
    protected override void Execute(RuleContext context)
    {
      T value = (T)context.InputPropertyValues[PrimaryProperty];  // PrimaryProperty key exists, but value is null
      int result = value.CompareTo(Min);
      if (result <= -1)
      {
        string outValue;
        if (string.IsNullOrEmpty(Format))
          outValue = Min.ToString();
        else
          outValue = string.Format(string.Format("{{0:{0}}}", Format), Min);
        context.AddErrorResult(string.Format(Resources.MinValueRule, PrimaryProperty.FriendlyName, outValue));
      }
    }
  }

RockfordLhotka replied on Tuesday, May 11, 2010

Good - thanks for your help digging into this - I see what's going on now. Or at least I see what you see - I still don't know exactly why it is happening yet :)

RockfordLhotka replied on Tuesday, May 11, 2010

Huh. So this is due to a bug in BusinessBase.ReadProperty(), which isn't actually new. I suspect nobody has encountered or reported the bug because the non-generic ReadProperty() wasn't that widely used before - but now the rules system uses it.

So the problem is with your (and now my) use of a default value in the RegisterProperty() for the property, and the fact that the non-generic ReadProperty() wasn't properly using the default value to lazy initialize the property value. I've fixed it now, so it should work in the next CSLA 4 drop. You can grab it from svn if you need it faster - the affected files are listed in the bug:

http://www.lhotka.net/cslabugs/edit_bug.aspx?id=747

k2so replied on Wednesday, May 12, 2010

Thanks Rocky, I've tried the change from svn, working fine now.

Copyright (c) Marimer LLC