Dear All:
I am a junior in CSLA framework. There's a doubt when i reading the "Using CSLA 4" about the business rule: It says "the target property can only be used on the ui thread and any use of this property on a background thread will lead to cross-thread exceptions or nasty threading bugs"
My doubt:
1. this rule is only suitable for async rule or both sync/async rule?
2.if i don't have the ui thread, and it just a service to expose interface to others,in this case, can i use the target ?
3. Dose everyone could illustrate some detail example to explain what problem will occur when use target in background thread?
Thanks.
Hi,
Typically - rules is triggered by a user editing a property in the UI. So it is likely that the rules Execute method will be called on the UI thread on an object that is databound. You will get cross thread exception when you try to set properties on an object in a background thread (actually when the OnPropertyChanged event is called and there is eventlisteners on another thread).
When rules have IsAsync = true this tells the RuleEngine to not call context.Complete automatically. It is then your code´s responsibility to call context.Complete when the async operation is completed.
When you use ASP.NET or WCF you will typically load data into the object and call BusinessRules.CheckRules(). This is more efficient than calling rules on every property setter.
So your rules may be sync or async. Async rules may only get the contextTarget when ProvideTargetWhenAsync = true and you explicitly take reposibility for interaction with context.Target.
What is the rule expected to do?
Thanks, JonnyBee
i think i can descripe this rule as below:
Target(BusinessBase) is not thread safety, There will be a problem when multi-thread access the target and its properties
That's mean when i guarantee the target is thread safety, there won't be a problem.
Right?
Hio,
Most of .NET framework is NOT thread safe so I'm afraid I do not quite understand how you can make target thread safe.
For CSLA part it is mostly UI Events that causes problems when they occur from a thred that is NOT the UI Thread.
This will cause cross-thread exception to be thrown.
I stil do not understand what the purpose of the rule is.
OK, don't worry about that. It just the way of my understanding(i don't want to bring the UI/Background thread to it) . Maybe it's very hard to make the target thread safe.
So, my understanding is right?
Thanks Jonney.
Here is an example of running a rule both on the current UI thread and using an async call on another thread. The UI thread just checks to see that the name isn't empty. Then a command is called to check the database that the name is unique and doesn't duplicate another name in the database:
using Csla.Core;
using Csla.Rules;
/// <summary>
/// The group validate name rule.
/// </summary>
public class GroupGroupValidateNameRule : BusinessRule
{
public GroupGroupValidateNameRule(IPropertyInfo primaryProperty)
: base(primaryProperty)
{
ProvideTargetWhenAsync = true;
IsAsync = true;
AffectedProperties.Add(GroupGroupItem.NameProperty);
}
protected override void Execute(RuleContext context)
{
GroupGroupItem target = (GroupGroupItem)context.Target;
if (target.Name == string.Empty)
{
context.AddErrorResult("Group Name Cannot Be Empty.");
context.Complete();
}
else
{
// Check for duplicated name
GroupGroupValidateNameCommand.BeginExecute(target.Name, target.Id, (o, e) =>
{
if (e.Error != null)
{
throw e.Error;
}
else
{
if (e.Object.Duplicate)
{
context.AddErrorResult("Group Name Is Already In Use. Please Choose Another Name.");
context.Complete();
}
else
{
context.AddSuccessResult(true);
context.Complete();
}
}
});
}
}
}
Hope this will help find what you are looking for.
Todd
Hi Todd,
Your rule is not correct. You MUST make sure to always call context.Complete in the callback - or else the object/property will remain "busy" and you will not get the ValidationCompleted event.
So - you should not throw the exception - rather add the error message as AddErrorMessage and call context.Complete().
Thanks, Jonny. One of the boiler-plate cut and paste things... Here is a new copy:
using Csla.Core;
using Csla.Rules;
/// <summary>
/// The group group validate name rule.
/// </summary>
public class GroupGroupValidateNameRule : BusinessRule
{
public GroupGroupValidateNameRule(IPropertyInfo primaryProperty)
: base(primaryProperty)
{
ProvideTargetWhenAsync = true;
IsAsync = true;
AffectedProperties.Add(GroupGroupItem.NameProperty);
}
protected override void Execute(RuleContext context)
{
GroupGroupItem target = (GroupGroupItem)context.Target;
if (target.Name == string.Empty)
{
context.AddErrorResult("Group Name Cannot Be Empty.");
context.Complete();
}
else
{
// Check for duplicated name
GroupGroupValidateNameCommand.BeginExecute(target.Name, target.Id, (o, e) =>
{
if (e.Error != null)
{
context.AddErrorResult(e.Error.Message);
context.Complete();
}
else
{
if (e.Object.Duplicate)
{
context.AddErrorResult("Group Name Is Already In Use. Please Choose Another Name.");
context.Complete();
}
else
{
context.AddSuccessResult(true);
context.Complete();
}
}
});
}
}
}
Todd
If i change properties of target in the call back method, It will be a problem? If there's no other thread access the target such as "OnPropertiesChanged",it will still be a problem ?
Hi
My general recommendation is to NEVER set an objects properties directly by the property setter in a rule Execute method. This will trigger the rule engine to run from within another rule execute method and may cause infinite loops.
In order to update property values on an object you may use
and make sure that all properties to be updated is added to AffectedProperties list in the constructor method of the rule.
The rule engine will then run rules for the affected properties and call OnPropertyChanged for you.
Thanks.
Sam Lin,
Here is an example of setting properties in a rule:
class DataEntrySetIssueItemSlaRule : BusinessRule
{
public DataEntrySetIssueItemSlaRule(IPropertyInfo primaryProperty)
: base(primaryProperty)
{
InputProperties = new List<IPropertyInfo> { PrimaryProperty };
AffectedProperties.Add(DataEntryIssueItem.SlaDueByProperty);
AffectedProperties.Add(DataEntryIssueItem.ShowSlaProperty);
}
protected override void Execute(RuleContext context)
{
var target = (DataEntryIssueItem)context.Target;
if (target.CustomerStdHrs > TimeSpan.Zero || target.CustomerCrtHrs > TimeSpan.Zero)
{
DateTime? slaDate = null;
// Critical Part and Critical Problem gets Critical Hrs
if (target.PartCritical && target.Problem.Critical && target.CustomerCrtHrs > TimeSpan.Zero)
{
// Calculate SLA date
slaDate = target.CalendarEventList.CalculateDate(target.Added, target.CustomerCrtHrs);
}
else
{
if (target.CustomerStdHrs > TimeSpan.Zero)
{
slaDate = target.CalendarEventList.CalculateDate(target.Added, target.CustomerStdHrs);
}
}
context.AddOutValue(DataEntryIssueItem.SlaDueByProperty, slaDate);
context.AddOutValue(DataEntryIssueItem.ShowSlaProperty, slaDate != null);
context.Complete();
}
}
}
Todd
Copyright (c) Marimer LLC