Async UniqueID Rule CSLA 4

Async UniqueID Rule CSLA 4

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


FEHODGSON posted on Friday, July 22, 2011

I am having a terrible time getting a UniqueID rule (which uses a command object to go to the database)  working in CSLA 4. I created a uniqueId class which inherits from BusinessRule and am using the dataportal to execute my UniqueID command object. I know it is a threading isue but can not get my hands on it. Does any one have an example?

Thanks,

Frank Hodgson

JonnyBee replied on Saturday, July 23, 2011

This is the coding style that I prefer:

The key is to use async DataPortal to do asynchronous data access in the command or readonly object.

Rule:

    public class LookupCustomer : Csla.Rules.LookupRule
    {
        public IPropertyInfo NameProperty { getset; }
        public LookupCustomer(IPropertyInfo primaryPropertyIPropertyInfo nameProperty)
            : base(primaryProperty)
        {
            NameProperty = nameProperty;
            InputProperties = new List<IPropertyInfo>() {primaryProperty};
            AffectedProperties.Add(NameProperty);
        }
 
        protected override void Execute(RuleContext context)
        {
            var customerId = (intcontext.InputPropertyValues[PrimaryProperty];
 
            LookupCustomerCommand.BeginExecute(customerId, 
             (o,e) =>
                      {
                          if (e.Error != null)
                          {
                              context.AddErrorResult(e.Error.Message);
                          }
                          else
                          {
                              context.AddOutValue(NamePropertye.Object.Name);
                          }
                          context.Complete();
                      });
        }
    }

Async command:

    [Serializable]
    public class LookupCustomerCommand : CommandBase<LookupCustomerCommand>
    {
        #region DTO properties 
 
        public static readonly PropertyInfo<intCustomerIdProperty = RegisterProperty<int>(c => c.CustomerId);
        public int CustomerId   
        {
            get { return ReadProperty(CustomerIdProperty); }
            private set { LoadProperty(CustomerIdPropertyvalue); }
        }
 
        public static readonly PropertyInfo<stringNameProperty = RegisterProperty<string>(c => c.Name);
        public string Name
        {
            get { return ReadProperty(NameProperty); }
            private set { LoadProperty(NamePropertyvalue); }
        }
        #endregion
 
        #region Factory Methods
 
        public static void BeginExecute(int customerIdEventHandler<DataPortalResult<LookupCustomerCommand>> callback)
        {
            LookupCustomerCommand cmd = new LookupCustomerCommand();
            cmd.CustomerId = customerId;
            DataPortal.BeginExecute<LookupCustomerCommand>(cmdcallback);
        }
 
        #endregion
 
        #region Server-side Code
 
        protected override void DataPortal_Execute()
        {
            switch  (CustomerId)
            {
                case 1:
                    Name = "Rocky Lhotka";
                    break;
                default:
                    Name = string.Format("Customer_{0}"CustomerId);
                    break;
            }      
        }
 
        #endregion
    }

 

 

FEHODGSON replied on Saturday, July 23, 2011

Hi JonnyBee,

Thanks for your quick response. This is almost exactly what I had with one BIG difference. I was handling the call back in the Dataportal.BeginExecute<LookupCustomer>(cmd, (o,e) => etc...... where I would test for an error. Once I removed that and just passed the callback it worked. I did have to include the IsAsync=True to get the csla propertyStatus to work, without the IsAsync=True the property status does not show up. What I have also noticed which I have not put a finger on yet is that after I do a rebuild solution and start a debug sesion in my local VS development server the changes do not show up. I have to do a publish to our external test webserver for the rules to actualy work. on my local machine. I guess some funny thing going on with VS10.

Thank You.

Frank

FEHODGSON replied on Sunday, August 21, 2011

Hi JonnyBee,

I am again having some problems with rules. I have a situation where I create an object async in code behind ( sorry it is VB )

Tap

 

 

.NewTap(AddressOf SetupTap)

 

 

 then in the call back I set certain properties and do a beginsave.

 

 

 

 

 

 

 

If

 

 

e.Object.IsBusy Then

 

 

 

Dim wait As New System.Threading.AutoResetEvent(False)

 

 

 

AddHandler e.Object.BusyChanged, Sub(p, f)

 

 

 

If Not e.Object.IsBusy Then

wait.Set()

 

 

 

End If

 

 

 

End Sub

wait.WaitOne()

 

 

 

End If

e.Object.BeginSave(

 

AddressOf TapSaved)

 Without the rules this works, with the rules the object stays bussy and ofcourse keeps waiting. I checked my rules and I am setting the context.complete and I am also only setting the

IsAsync = (

 

ApplicationContext.ExecutionLocation <> ExecutionLocations.Server)

If I use

IsAsync = (ApplicationContext.ExecutionLocation = ExecutionLocations.Client)

Then it works but then the csla:PropertyStatus in my MVVM forms does not fire up when there is a broken rule.

What has me kind of confused is that ApplicationContext has three locations, Client, silverlight and server. I was under the impression that Silverlight and the client were the same.

 

 

Do you have any idea why this might be happening?

 

Frank

JonnyBee replied on Monday, August 22, 2011

Hi Frank,

What seems to be the problem is somehow your new object is Busy but doesn't raise the BusyChanged event. I can't identify any problem with the code that you posted - but could you provide a small "problem" solution in a zip file?

I'm assuming that you are using Csla 4.1?

I have made some updates to Csla.Xaml.PropertyStatus for Csla 4.2 (bugs where the PropertyStatus may be unable to hook into or properly show the PropertyStatus info) so you could make sure to grab the latest code for PropertyStatus in the Csla repository.

Silverlight and Client are different values but in terms of rules running async or not you should be doing fine with having a test for ExecutionLocations.Server.

I assume that one of the properties that you set before you save triggers the async rule?

 

 

FEHODGSON replied on Tuesday, August 23, 2011

Hi JonnyBee,

I stripped the whole project to 12MB zipped. Where Can I send this to?

Frank

 

JonnyBee replied on Tuesday, August 23, 2011

Hi Frank,

You can send it to: jonny.bekkum(a)gmail.com

 

JonnyBee replied on Tuesday, August 23, 2011

Hi,

I  assume you are both using Csla 4.0.* and I know we had a bug in there that culd cause to object to stay busy and was fixed for Csla 4.1.
If you are using 4.0.* then I'd recommend you to upgrade to Csla 4.1

Csla.Rules.BusinessRules.RunRules (in csla 4.0.*):

Is:

        var context = new RuleContext((r) =>
          {
            if (rule.IsAsync)

Should be:

        var context = new RuleContext((r) =>
          {
            if (r.Rule.IsAsync)

 

t.kehl replied on Wednesday, August 24, 2011

Hi JonnyBee

 

Thank you very much for your answer.
I use CSLA 4.1.0.0 - But I have looked into RunRules and see, that there is still rule.IsAsync. When I changed this to r.Rule.IsAsync it is working perfect.

Best Regards, Thomas

JonnyBee replied on Wednesday, August 24, 2011

Hmmm,

I checked Csla 4.1 download again and it is correct in the 4.1.0 release from 110818.
http://www.lhotka.net/files/csla40/CslaSource-4.1.0-110118.zip

 

FEHODGSON replied on Friday, August 26, 2011

Hi Thomas,

What did you change to get your rule working? I still can not get mine to work.

Thanks,

Frank

t.kehl replied on Friday, August 26, 2011

Hi Frank

 

I have changed this from JonnyBee:

 

Hi,

I  assume you are both using Csla 4.0.* and I know we had a bug in there that culd cause to object to stay busy and was fixed for Csla 4.1.
If you are using 4.0.* then I'd recommend you to upgrade to Csla 4.1

Csla.Rules.BusinessRules.RunRules (in csla 4.0.*):

Is:

        var context = new RuleContext((r) =>
          {
            if (rule.IsAsync)

Should be:

        var context = new RuleContext((r) =>
          {
            if (r.Rule.IsAsync)

FEHODGSON replied on Friday, August 26, 2011

Hi Thomas,

Do you have an example of where you changed this in your rule that you posted? I can not seem to figure out where this has to be changed.

Frank

t.kehl replied on Friday, August 26, 2011

Hi Frank

 

This is in the Class Csla.Rules.BusinessRules

 

Thomas

FEHODGSON replied on Friday, August 26, 2011

Hi Thomas,

Now I get it you are changing the CSLA source. Wow I never did that I just download the latest version. I have 4.2 and looked at the source and it seems fixed in that version but my object still stays bussy. So I guess my problem must be something else. Thanks anyway.

Frank

t.kehl replied on Tuesday, August 23, 2011

Hi Frank

I have the same problem. My Businessobject is also still busy when I use Async Rule to check, if a value is unique. I use the fallowing:

BusinessRule:

    public sealed class CheckUniqueAsync<T> : Csla.Rules.BusinessRule where T : BusinessBase<T>, IPrayonBusinessBase {
        private static readonly ILog log = LogManager.GetLogger(typeof(CheckUniqueAsync<T>));

        public CheckUniqueAsync(IPropertyInfo uniqueProperty, params IPropertyInfo[] affectedProperties)
            : base(uniqueProperty) {
            this.Priority = 1;
            this.IsAsync = true;
            this.ProvideTargetWhenAsync = true;
            this.AffectedProperties.AddRange(affectedProperties);
        }

        protected override void Execute(Csla.Rules.RuleContext context) {
            var bb = context.Target as PrayonBusinessBase<T>;
            if (bb != null) {
                bb.CheckUniqueAsync((s, e) =>
                {
                    if (e.Error != null) {
                        log.Error("CheckUnique completed with Error.", e.Error);
                    } else {
                        if (!e.Object.IsUnique) {
                            context.AddErrorResult(PrimaryProperty, string.Format(Resources.ObjectUniqueRule, PrimaryProperty.FriendlyName));
                        }
                    }
                    context.Complete();
                });
            } else {
                log.Error("No Businessobject available. Set Context completed.");
                context.Complete();
            }
        }
    }

        internal void CheckUniqueAsync(EventHandler<DataPortalResult<CheckUniqueCommand>> callback) {
            CheckUniqueCommand.BeginExecute(this.CheckUniqueRepositoryCall(), callback);
        }

    [Serializable]
    internal class CheckUniqueCommand : CommandBase<CheckUniqueCommand> {
        private readonly Func<bool> checkUniqueRepositoryCall;
        public bool IsUnique { get; private set; }

        public static bool Execute(Func<bool> checkUniqueRepositoryCall) {
            var cmd = new CheckUniqueCommand(checkUniqueRepositoryCall);
            cmd = DataPortal.Execute(cmd);
            return cmd.IsUnique;
        }

        public static void BeginExecute(Func<bool> checkUniqueRepositoryCall, EventHandler<DataPortalResult<CheckUniqueCommand>> callback) {
            var cmd = new CheckUniqueCommand(checkUniqueRepositoryCall);
            DataPortal.BeginExecute(cmd, callback);
        }

        private CheckUniqueCommand(Func<bool> checkUniqueRepositoryCall) {
            this.checkUniqueRepositoryCall = checkUniqueRepositoryCall;
        }

        protected override void DataPortal_Execute() {
            this.IsUnique = checkUniqueRepositoryCall();
        }
    }

Func to Check in the BussinessObject:

 

        protected override Func<bool> CheckUniqueRepositoryCall() {
            return () => {
                using (var communicationTypeRepository = new CommunicationTypeRepository()) {
                    var id = this.ReadProperty(IdProperty);
                    return communicationTypeRepository.GetAll().Where(x => x.Id != id && x.Name == this.ReadProperty(NameProperty)).FirstOrDefault() == null;
                }
            };
        }

Add BusinessRule to the BusinessObject:

            this.BusinessRules.AddRule(new CheckUniqueAsync<CommunicationType>(NameProperty));

Did you find the reason in your code, why the BO will still be Busy?

 

I hope we can find the reason together.

Thanks and best regards, Thomas

Copyright (c) Marimer LLC