Update a Combobox With Async Rule

Update a Combobox With Async Rule

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


MadGerbil posted on Thursday, November 03, 2011

I've two comboboxes on a screen.   One is a list of groups and the other is a list of users.  I'd like the list of users to asynchronously update based upon what the user selects for the groups combobox.  That is, each group may have a different list of users from which to select.  I've written the following:

 

         #region Validation
        protected override void AddBusinessRules()
        {
            base.AddBusinessRules();

            //Update Lists   
            BusinessRules.AddRule(new UpdateUserList(UserListsProperty, DefaultGroupIdProperty));
            BusinessRules.AddRule(new Dependency(DefaultGroupIdProperty, UserListsProperty));
        }

        class UpdateUserList : Csla.Rules.BusinessRule
        {
            public Csla.Core.IPropertyInfo DefaultGroupIdProperty { get; set; }

            public UpdateUserList(Csla.Core.IPropertyInfo primaryProperty, Csla.Core.IPropertyInfo secondaryProperty)
                : base(primaryProperty)
            {
                IsAsync = (Csla.ApplicationContext.ExecutionLocation != ApplicationContext.ExecutionLocations.Server);
                InputProperties = new List<Csla.Core.IPropertyInfo>();
                DefaultGroupIdProperty = secondaryProperty;
                InputProperties.Add(primaryProperty);
                InputProperties.Add(secondaryProperty);

                //CanRunAsAffectedProperty = false;
                //CanRunInCheckRules = !IsAsync;
                //CanRunOnServer = !IsAsync;
            }

            protected override void Execute(Csla.Rules.RuleContext context)
            {
                var groupId = (Guid)context.InputPropertyValues[DefaultGroupIdProperty];

                if (IsAsync)
                {
                    UserLists.BeginGetUserLists(groupId, (o, e) =>
                    {
                        if (e.Error != null)
                            context.AddErrorResult(e.Error.Message);
                        else
                            context.AddOutValue(UserListsProperty, e.Object);
                       
                        context.Complete();
                    });
                }
            }
        }
        #endregion

1: This is a regular Csla.Rules.BusinessRule as the ComonRules version of this would never fire.

2: The thing works, my list does get updated.

3: The only bug is that if a user list is empty I get an exception (null exception) if I click on the combobox in the UI.  I'm not sure why an empty combobox would error if attached to an empty collection but it does in this case.

4: The problem I now face is if a group is selected and the group does not contain the value in the user combobox.  Is there a way to just set the user to the first user in the list?  I'm trying MVVM here so I'd like to avoid code behind.

5: Is there a better way to handle this?  I'm sure there is, this is kinda hacked together. 

StefanCop replied on Thursday, November 03, 2011

Sorry, no solution.

5: Are you sure you want the list in your business object rather than in the view model? Of course, in the view model you can't use business rules. And you have to wire the PropertyChanged event of DefaultGroupIdProperty, to load the list async . Not simple if the BO can change, i.e. due to a Save().

3: Maybe a problem that the selected item cannot be found in an empty list, and then the selected item becomes null?

MadGerbil replied on Friday, November 04, 2011

I guess what I need is an asynchronous rule that when property X is changed it kicks off a rule so the value for property Y can be fetched asynchronously from the database.  I've tried using dependency rules to do this but it appears that Csla.Rules.CommonRules.CommonBusinessRule doesn't support dependency properties.

I like the concept of asynchronous processing but it's a bummer getting up to speed on it.

MadGerbil replied on Friday, November 04, 2011

Fixed it.  :D

I found the joy of affected properties.  This stuff is freakin' amazing.  Thanks again.

StefanCop replied on Friday, November 04, 2011

Did you add the "DefaultGroupIdProperty" to the affected properties?

MadGerbil replied on Friday, November 04, 2011

Here is what I did and it works well:

 

         #region Validation
        protected override void AddBusinessRules()
        {
            base.AddBusinessRules();

            //Update Lists   
            BusinessRules.AddRule(new UpdateUserList(DefaultGroupIdProperty, UserListsProperty, DefaultUserIdProperty) { Priority = 0 });
        }

        class UpdateUserList : Csla.Rules.CommonRules.CommonBusinessRule
        {
            public Csla.Core.IPropertyInfo UserListsProperty { get; set; }
            public Csla.Core.IPropertyInfo DefaultUserIdProperty { get; set; }

            public UpdateUserList(Csla.Core.IPropertyInfo primaryProperty, Csla.Core.IPropertyInfo secondaryProperty, Csla.Core.IPropertyInfo tertiaryProperty)
                : base(primaryProperty)
            {
                IsAsync = (Csla.ApplicationContext.ExecutionLocation != ApplicationContext.ExecutionLocations.Server);
                InputProperties = new List<Csla.Core.IPropertyInfo>();
                UserListsProperty = secondaryProperty;
                DefaultUserIdProperty = tertiaryProperty;

                AffectedProperties.Add(UserListsProperty);
                AffectedProperties.Add(DefaultUserIdProperty);
               
                InputProperties.Add(primaryProperty);
                InputProperties.Add(secondaryProperty);
                InputProperties.Add(tertiaryProperty);

                CanRunAsAffectedProperty = false;
                CanRunInCheckRules = !IsAsync;
                CanRunOnServer = !IsAsync;
            }

            protected override void Execute(Csla.Rules.RuleContext context)
            {
                var groupId = (Guid)context.InputPropertyValues[DefaultGroupIdProperty];

                if (IsAsync)
                {
                    UserLists.BeginGetUserLists(groupId, (o, e) =>
                    {
                        if (e.Error != null)
                            context.AddErrorResult(e.Error.Message);
                        else
                        {
                            context.AddOutValue(UserListsProperty, e.Object);
                            context.AddOutValue(DefaultUserIdProperty, e.Object[0].Id);
                        }

                        context.Complete();
                    });
                }
            }
        }
        #endregion

NOTES:

1: I've a combobox that represents groups.

2: I've a combobox that represents users.

3: When the combobox that represents groups is changed to a different group the rule runs out and gets the list of users that are contained in that group and updates the users combobox with that list of users.

4: The usercombobox has it's key value set to the first item in the user list.

5: On the fetch routine (for users list) if the collection is empty a "blank" user is added to guarantee that the list of users contains at least one item.

 

StefanCop replied on Friday, November 04, 2011

Oh, that's a very nice example! Thanks for sharing your solution.

Copyright (c) Marimer LLC