CSLA4 Async Ruel not set BusinessRules.RunningAsyncRules on time

CSLA4 Async Ruel not set BusinessRules.RunningAsyncRules on time

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


HK.Lee posted on Friday, May 14, 2010

Hi Rocky,

My business objects often do BusinessRules.CheckRules() on DataPotal Fetch after retrieving data. The problem is if async rule is included, it looks not set  BusinessRules.RunningAsyncRules on time when async rule's context.Complete() is called.

 What I'm saying on time is if it runs through debug mode line by line,RunningAsyncRules is set 'false' correctly,but if runs fast, it says 'true' in BusinessRules.CheckRule.   I only guess it needs time to complete RuleContext.Complete method's handler, but don't know in detail. Because of this, BB's IsBusy always set true so ViewModel's Can_ properties goes wrong so enable/disable state of view's buttons are not set as desired.

Thank you in advance.

HK.Lee

RockfordLhotka replied on Friday, May 14, 2010

If you have async rules that are going to run on the server (like in DataPortal_Fetch()) then you must ensure that the server call doesn't complete before the rules are done running.

The way to do this is to use a thread synchronization primitive to block the main thread, and to handle ValidationComplete to unblock that main thread. (that technique won't work in SL because threading is handled differently, but it works fine in ASP.NET).

So in DP_Fetch (typed from memory, excuse minor typos):

var waiter = new System.Threading.AutoResetEvent(false);
this.ValidationComplete += (o, e) => waiter.Set();

// load object and do your normal stuff
// ...

waiter.WaitOne();

This blocks the main server thread until all validation is complete, at which point the server thread continues and the call is returned to the client.

RockfordLhotka replied on Friday, May 14, 2010

I wonder though, if maybe there should be a way to tell BusinessRules to treat all async rules like sync rules...

That way when you check the rules on a server (like within the data portal) you wouldn't have to worry about this issue - you'd just have the rules run synchronously and be done with it.

In fact, that could be the default when the logical execution location is 'server'. If you really want the async behavior on the server then you'd have to override the default - and add the code to handle the threading issues.

I think I'll have to try this - I like the idea. Thanks! Smile

HK.Lee replied on Friday, May 14, 2010

Hi Rocky,

I appreciate your kind reply and will try to your idea to treat async rules like sync rules.

Thanks.

HK.Lee

RockfordLhotka replied on Monday, May 17, 2010

RockfordLhotka

I wonder though, if maybe there should be a way to tell BusinessRules to treat all async rules like sync rules...

That way when you check the rules on a server (like within the data portal) you wouldn't have to worry about this issue - you'd just have the rules run synchronously and be done with it.

In fact, that could be the default when the logical execution location is 'server'. If you really want the async behavior on the server then you'd have to override the default - and add the code to handle the threading issues.

I thought about this further, and it really isn't that easy. The reason it isn't easy is that the rules themselves are async. It isn't like CSLA invokes them on a background thread, it doesn't. It is the rule itself that runs things on a background thread.

So the only real solution, I think, is to make the rule itself smart enough to run synchronously on the server, and async on the client - probably using the logical execution location property from Csla.ApplicationContext.

HK.Lee replied on Tuesday, May 18, 2010

Hi Rocky,

I tried LogicalExecutionLocation as follows and it seems to work. My business rule is a kind of 'Exist' rule of PTtracker, which calls SP to see same name exists in a field of table.

  public class DupeNameExistRule : Csla.Rules.BusinessRule
    {
        public DupeNameExistRule(Csla.Core.IPropertyInfo primariProp, Csla.Core.IPropertyInfo chkProperty, DupeNameCriteria criteria)
            : base(primariProp)
        {
            // IsAsync = true;
            IsAsync = (Csla.ApplicationContext.LogicalExecutionLocation == ApplicationContext.LogicalExecutionLocations.Client);
            _chkProperty = chkProperty;
            _criteria = criteria;
            InputProperties = new System.Collections.Generic.List<Csla.Core.IPropertyInfo> { primariProp, _chkProperty };
        }

        DupeNameCriteria _criteria;
        Csla.Core.IPropertyInfo _chkProperty;

        protected override void Execute(RuleContext context)
        {
            // get current field value
            string curr = context.InputPropertyValues[PrimaryProperty].ToString();
            int chkId = 0;
            if (context.InputPropertyValues[_chkProperty] != null)
               chkId = (int)context.InputPropertyValues[_chkProperty];
            _criteria.SetValue(curr, chkId);

            if (IsAsync)
            {
                DupeNameExists.DupeNameCheck(_criteria, (o, e) =>
                {
                    if (e.Error != null)
                        context.AddErrorResult(e.Error.Message);
                    else if (e.Object != null && e.Object.Exist)
                        AddErrorResult(context);
                    context.Complete();
                });
            }
            else
            {
#if !SILVERLIGHT
                var result = DataPortal.Fetch<DupeNameExists>(_criteria);
                if (result.Exist) AddErrorResult(context);
#endif
            }
        }

        private void AddErrorResult(RuleContext context)
        {
            context.AddErrorResult(String.Format(Properties.Resources.AlreadyExtstFieldName,
                                                         _criteria.FieldValue, PrimaryProperty.FriendlyName));
        }
    }

When run this code, proper method is called along with current execution location and all seems working fine. Before more refactoring be taken, I'd like to listen any advice or something I missed.

HK.Lee

 

RockfordLhotka replied on Tuesday, May 18, 2010

That is what I was hoping. I do think there's one issue, and that is with how you set IsAsync. I wonder if I don't need to make IsAsync a virtual property so you can override its getter.

The reason I think this, is that in a 2-tier physical deployment (like with a WPF app or something), everything runs in one AppDomain. So the rule would be sync or async based on where it was initialized, and that would be true for the entire app. When really you'd want it to switch between sync and async depending on whether it was run from the logical "client" or logical "server".

HK.Lee replied on Tuesday, May 18, 2010

Hi Rocky,

I didn't assume 2 Tier deployment and your advice is correct.

Hope to see more fine grained way in the RTM.

Thank you in advance.

HK.Lee

 

OscarLauroba replied on Wednesday, February 23, 2011

Hello HK.Lee

I'm not clear about the issue that Rocky says.

Is your code correct or needs any modification in reference to what Rocky says about 2 tier deployment. I see your code is using Csla.ApplicationContext.LogicalExecutionLocation in the constructor. Is this the right place or it should be in the Execute method?

 Oscar.

JonnyBee replied on Wednesday, February 23, 2011

HI Oscar,

The code should test for ApplicationContext.ExecutionLocation rather than ApplicationContext.LogicalEexecutionLocation.

ApplicationContext.ExexutionLocation == ExecutionLocations.Server only when code is executed on a server,

The rule.IsAsync property must be set in the rule constructor  and is used by the rule engine to determine how to call the Execute method. Remember that all properties of a rule should be immutable and only be set in the constructor.  You should NEVER change the IsAsync property in the Execute method.

 

 

OscarLauroba replied on Thursday, February 24, 2011

Hello Jonny,

I'm testing my silverlight application and to determine if my ProjectExists rule is Asychronous or not I'm doing this in the constructor as you recommend using ExecutionLocation: (sorry, it's VB.Net)

IsAsync = (Csla.ApplicationContext.ExecutionLocation <> ApplicationContext.ExecutionLocations.Server)


IsAsync will be true whenever ExecutionLocation is not Server. But when the application runs it executes twice the rule constructor: first time ExecutionLocation is Client, second time ExecutionLocation is Silverlight, so the rule is always asynchronous. In which case HK Lee's code doesn't work?

 

JonnyBee replied on Thursday, February 24, 2011

If you are running 3 tier app (ie not using LocalProxy ) the rule will be created on both Client and Server as separate instances.

Look at the callstack to determine when the rule constructor is called.

Copyright (c) Marimer LLC