Async business rule not updating

Async business rule not updating

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


griff posted on Friday, October 19, 2012

Ui is not being updated for this rule (everything else works okay)

 private class LookISBN : PropertyRule// Csla.Rules.BusinessRule
        {
            private readonly IPropertyInfo _schoolProperty;
            private readonly IPropertyInfo _authorProperty;
 
            public LookISBN(IPropertyInfo primaryProperty, IPropertyInfo schoolProperty, params IPropertyInfo[] affectedProperties)
                : base(primaryProperty)
            {
                IsAsync = true;
                _schoolProperty = schoolProperty;
                InputProperties = new List<IPropertyInfo>();
                InputProperties.Add(PrimaryProperty);
                InputProperties.Add(schoolProperty);
                AffectedProperties.AddRange(affectedProperties);
                _authorProperty = AffectedProperties[1];
 
                CanRunAsAffectedProperty = false;
                CanRunInCheckRules = false;
                CanRunOnServer = false;
            }
 
            protected override void Execute(RuleContext context)
            {
                if (context.InputPropertyValues[PrimaryProperty] != null)
                {
                    string isbn = context.InputPropertyValues[PrimaryProperty].ToString();
                    if (!string.IsNullOrWhiteSpace(isbn))
                    {
                        var schoolid = context.InputPropertyValues[_schoolProperty].ToString();
 
                        AddStockInfo.GetAddStockInfo(isbn, schoolid, "A", (o, e) =>
                            {
                                if (e.Object != null && e.Error == null)
                                {
//e.Object.Author is populated okay
                                    context.AddOutValue(_authorProperty, e.Object.Author);                                     context.AddOutValue(AffectedProperties[2], e.Object.Title);                                     context.AddOutValue(AffectedProperties[3], e.Object.SubjectIndexID);                                 }                                 context.Complete();                             });                                                  //context.AddOutValue(AffectedProperties[1], "author");                         //context.AddOutValue(AffectedProperties[2], "title");                         //context.AddOutValue(AffectedProperties[3], 112);                     }                 }             }         }

 Thanks for your help

griff replied on Friday, October 19, 2012

And this is how I call it

BusinessRules.AddRule(new LookISBN(ISBN13Property, SchoolIDProperty, AuthorProperty, TitleProperty, SubjectIndexIDProperty));

 

JonnyBee replied on Friday, October 19, 2012

You must ALWAYS MAKE SURE THAT ALL EXCEUTION FLOWS CALL Context.Complete in an async rules Execute method.

IE:

                if (context.InputPropertyValues[PrimaryProperty] != null)
                {
                    string isbn = context.InputPropertyValues[PrimaryProperty].ToString();
                    if (!string.IsNullOrWhiteSpace(isbn))
                    {

NONE of these if statements make sure to call Context.Complete and this is going to mess up the rule engine. 

Meaning that when you do call async method - always make sure to call Context.Complete()  (and your rule does)
Also - whenever your rule decides to NOT do an async method you MUST call Context.Complete() in order to let the rule engine know that it has completed.

 

JonnyBee replied on Friday, October 19, 2012

This the actual code in BusinessRules.RunRules:

              foreach (var item in r.Rule.AffectedProperties)
              {
                BusyProperties.Remove(item);
                _isBusy = BusyProperties.Count > 0;
                if (!BusyProperties.Contains(item))
                  _target.RuleComplete(item);
              }

and in BusinessBase

    void IHostRules.RuleComplete(IPropertyInfo property)
    {
      OnPropertyChanged(property);
      OnBusyChanged(new BusyChangedEventArgs(property.Name, false));
      MetaPropertyHasChanged("IsSelfValid");
      MetaPropertyHasChanged("IsValid");
      MetaPropertyHasChanged("IsSavable");
    }

Giving that ONLY WHEN THE PROPERTY IS NO LONGER BUSY - Then the RuleComnplete handler will call target.RuleComplet to notify UI.
So when  the rule fails to call Context.Complete the properties will always remain busy and no notification will be given.

FWIW: I would never us AffectedProperties like you do here. If they must be in a specific order then I would prefer to have these as separate parameters to the rule constructor. No-one will ever be able to understand the sequence from that constructors signature.

griff replied on Friday, October 19, 2012

Thanks - it did actually start working all of a sudden - however I will heed your good advice.

Is this what you mean [see bold above] (FWIW I would not use AffectedProperties....)?

 private class LookISBN : PropertyRule// Csla.Rules.BusinessRule
        {
            private readonly IPropertyInfo _schoolProperty;
            private readonly IPropertyInfo _prop1;
            private readonly IPropertyInfo _prop2;
            public LookISBN(IPropertyInfo primaryProperty, IPropertyInfo schoolProperty, IPropertyInfo prop1, IPropertyInfo prop2, etc )
                : base(primaryProperty)
            {
                IsAsync = true;
                _schoolProperty = schoolProperty;
                _prop1 = prop1;
                _prop2 = prop2;
                InputProperties = new List<IPropertyInfo>();                 InputProperties.Add(PrimaryProperty);                 InputProperties.Add(schoolProperty);                             Affectedproperties.Add( prop1);
               Affectedproperties.Add( prop2);

                CanRunAsAffectedProperty = false;
                CanRunInCheckRules = false;
                CanRunOnServer = false;
            }
 
            protected override void Execute(RuleContext context)
            {
                if (context.InputPropertyValues[PrimaryProperty] != null)
                {
                    string isbn = context.InputPropertyValues[PrimaryProperty].ToString();
                    if (!string.IsNullOrWhiteSpace(isbn))
                    {
                        var schoolid = context.InputPropertyValues[_schoolProperty].ToString();
 
                        AddStockInfo.GetAddStockInfo(isbn, schoolid, "A", (o, e) =>
                            {
                                if (e.Object != null && e.Error == null)
                                {
                                    context.AddOutValue(_prop1, e.Object.Author);
                                    context.AddOutValue(_prop2, e.Object.Title);
                                }
                                context.Complete();
                            });
                    }
                }
            }
        }


JonnyBee replied on Friday, October 19, 2012

I'd probably use strong typed parameters with proper names like:

public LookISBN(IPropertyInfo primaryProperty, PropertyInfo<string> schoolProperty, 
PropertInfo<string> authorProperty, PropertyInfo<int> subjectIndexIdProperty
)

This signature is quite self explaining.

And your coding style in Execute should be like:
               if (context.InputPropertyValues[PrimaryProperty] == null) {
context.Complete();
return;
}

               string isbn = context.InputPropertyValues[PrimaryProperty].ToString();                if (string.IsNullOrWhiteSpace(isbn)) {
context.Complete();
return;
}

// then do the async operation

griff replied on Saturday, October 20, 2012

Thanks Jonny

Copyright (c) Marimer LLC