I’ve been using CSLA for a while now but am now just starting to build Silverlight applications using it. I am having a problem with asynchronous rule methods that update the UI.
I am using CSLA 4.1 and have the following objects:
BannedCarrier – Child Business Base Object
BannedCarriers – Editable Business List Base
The Banned Carrier Object has 3 properties: Code, Name, and Level
The code and level properties are editable and the name property is read only and displays the name value of the code property based on a lookup from a reference list that is cached on the first fetch. During fetch the names are loaded synchronously but I have an asynchronous business rule that populates it when a code is changed or a new carrier ban is added.
The problem I am running into is that when that asynchronous rule is called the first time it does not update the UI and for some reason makes my list un-savable. If I change the code property in another row the rule fires and the name property is updated in the UI for that row but my list still remains un-savable. If I try to go back and update the first row again there is still no change. If I do a CancelEdit and try again that same child continues to have the problem. It seems as if that first object gets stuck somehow for the life of the list. When I remove the rule I can add and update without issue.
Here is part of the code for my child object
Public Shared ReadOnly CarrierCodeProperty As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.CarrierCode, "Carrier Code")
Public Property CarrierCode() As String
Get
Return GetProperty(CarrierCodeProperty)
End Get
Set(ByVal value As String)
SetProperty(CarrierCodeProperty, value)
End Set
End Property
Public Shared ReadOnly CarrierNameProperty As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.CarrierName)
Public ReadOnly Property CarrierName() As String
Get
Return ReadProperty(CarrierNameProperty)
End Get
End Property
Protected Overrides Sub AddBusinessRules()
BusinessRules.AddRule(New Rules.CommonRules.Required(CarrierCodeProperty))
BusinessRules.AddRule(New Rules.CommonRules.MaxLength(CarrierCodeProperty, 2))
BusinessRules.AddRule(New SetCarrierName(CarrierCodeProperty, CarrierNameProperty))
End Sub
Private Class SetCarrierName
Inherits Rules.BusinessRule
Public Property CarrierName As IPropertyInfo
Public Sub New(ByVal carrierCodeProperty As IPropertyInfo,
ByVal carrierNameProperty As IPropertyInfo)
MyBase.New(carrierCodeProperty)
CarrierName = carrierNameProperty
InputProperties = New List(Of IPropertyInfo) From {PrimaryProperty}
AffectedProperties.Add(CarrierName)
IsAsync = True
End Sub
Protected Overrides Sub Execute(ByVal context As Csla.Rules.RuleContext)
Dim code = CType(context.InputPropertyValues(PrimaryProperty), String)
Dim name As String = String.Empty
Reference.CarrierList.GetList(Sub(sender As Object, e As DataPortalResult(Of Reference.CarrierList))
name = e.Object.GetName(code)
context.AddOutValue(CarrierNameProperty, name)
context.Complete()
End Sub)
End Sub
End Class
Any help would be greatly appreciated.
Thank you in advance.
Are you calling CheckRules in your DataPortal_Fetch?
Thanks for the quick reply!
No I'm not calling CheckRules in fetch, here is my fetch code.
For the list just your basic L2S call
Private Overloads Sub DataPortal_Fetch()
Me.RaiseListChangedEvents = False
Using ctx = ContextManager(Of Dal.DBDataContext).GetManager(Dal.Database.DBConnection)
Dim data = From c In ctx.DataContext.BannedCarriers
Order By c.Carrier Descending
Select (BannedCarrier.GetBannedCarrier(c))
Me.AddRange(data)
End Using
Me.RaiseListChangedEvents = True
End Sub
And then in the child object just loading in the individual values
Private Sub Child_Fetch(ByVal data As Dal.BannedCarrier)
LoadProperty(IdProperty, data.Id)
LoadProperty(CarrierCodeProperty, data.Carrier)
LoadProperty(LevelProperty, data.Level)
LoadProperty(CarrierNameProperty, Reference.CarrierList.GetList.GetName(data.Carrier))
End Sub
I think I figured out at least part of the problem, still not sure why it is happening though or how to fix it. After reading another post regarding not being able to save an object that is busy I added a check to display the IsBusy and IsSelfBusy properties for all the child objects in the collection. The first object that runs that rule gets it's IsBusy and IsSelfBusy properties to True and they get stuck with that status. I changed subsequent children that will fire that rule and the UI updates like it should and their busy status' revert back to False immediately, but that initial obect stays "Busy" until I close the view. Since that one object is always busy I believe that is why UI will not update and ceratinly the reason my list is never saveable. It still perplexes me as to why that first edited object gets stuck and all the others do not.
It sounds like your async rule is not ever being completed.
Is one of your objects a New object (and is base.DataPortal_Create) called.
base.DataPortal_Create will call BusinessRules.CheckRules and may make the object busy and return it to the client if running in an N-tier configuration.
No, the hangup occurs on the first object that is edited. It will happen whether a "New" item has been added to the collection or just the current item list is being updated. At first I had also thought that the rule was not completing, but that does not seem to be the case since it works as designed for any subsequent objects edited in the same way.
What if you get an Exception in the data access / tranport?
In the async callback - the first thing your code should check for is if e.Error <> Null then context.AddErrorResult with exception message.
I figured it out. It was an error in my lookup list class. I'm caching the results and forgot to return the callback in the fetch completed handler when it actually needs to go get data. So the rule was waiting for a callback that never came back the first time but since the list did get cached it was coming back for the second or third objects. But I did add the error handling to the rule as well, thanks for the tip.
Public Shared Sub GetList(ByVal callback As EventHandler(Of DataPortalResult(Of CarrierList)))
If mList Is Nothing Then
Dim dp As New DataPortal(Of CarrierList)
AddHandler dp.FetchCompleted, Sub(sender As Object, e As DataPortalResult(Of CarrierList))
If e.Error Is Nothing Then
mList = e.Object
callback(sender,e)
End If
End Sub
dp.BeginFetch()
Else
callback(Nothing, New DataPortalResult(Of CarrierList)(mList, Nothing, Nothing))
End If
End Sub
Hey,
You must always call the callback event handler in Fetch completed. Otherwise you'll end up in that same situation if Fetch failed.
And you would maybe also add logic so that you will not start async fetch more than one times - remeber this is called from an Async rule and may be triggered many times until the first FetchCompleted is called.
So - as a minimum like this:
Public Shared Sub GetList(ByVal callback As EventHandler(Of DataPortalResult(Of CarrierList)))
If mList Is Nothing Then
Dim dp As New DataPortal(Of CarrierList)
AddHandler dp.FetchCompleted, Sub(sender As Object, e As DataPortalResult(Of CarrierList))
If e.Error Is Nothing Then
mList = e.Object
End If
callback(sender,e)
End Sub
dp.BeginFetch()
Else
callback(Nothing, New DataPortalResult(Of CarrierList)(mList, Nothing, Nothing))
End If
End Sub
Great, I will make that update. Thanks for all your help!
Copyright (c) Marimer LLC