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
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 { get; set; } public LookupCustomer(IPropertyInfo primaryProperty, IPropertyInfo nameProperty) : base(primaryProperty) { NameProperty = nameProperty; InputProperties = new List<IPropertyInfo>() {primaryProperty}; AffectedProperties.Add(NameProperty); } protected override void Execute(RuleContext context) { var customerId = (int) context.InputPropertyValues[PrimaryProperty]; LookupCustomerCommand.BeginExecute(customerId, (o,e) => { if (e.Error != null) { context.AddErrorResult(e.Error.Message); } else { context.AddOutValue(NameProperty, e.Object.Name); } context.Complete(); }); } }
Async command:
[Serializable] public class LookupCustomerCommand : CommandBase<LookupCustomerCommand> { #region DTO properties public static readonly PropertyInfo<int> CustomerIdProperty = RegisterProperty<int>(c => c.CustomerId); public int CustomerId { get { return ReadProperty(CustomerIdProperty); } private set { LoadProperty(CustomerIdProperty, value); } } public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name); public string Name { get { return ReadProperty(NameProperty); } private set { LoadProperty(NameProperty, value); } } #endregion #region Factory Methods public static void BeginExecute(int customerId, EventHandler<DataPortalResult<LookupCustomerCommand>> callback) { LookupCustomerCommand cmd = new LookupCustomerCommand(); cmd.CustomerId = customerId; DataPortal.BeginExecute<LookupCustomerCommand>(cmd, callback); } #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 }
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
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
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?
Hi JonnyBee,
I stripped the whole project to 12MB zipped. Where Can I send this to?
Frank
Hi Frank,
You can send it to: jonny.bekkum(a)gmail.com
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)
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
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
Hi Thomas,
What did you change to get your rule working? I still can not get mine to work.
Thanks,
Frank
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)
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
Hi Frank
This is in the Class Csla.Rules.BusinessRules
Thomas
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
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