In my web app I use DataDynamics Active Reports with CSLA.
It works great! I designed a framework for my reports where a ReadOnly BO is always fetched for header data and a ROC is used for the detail data. Then I can set the report datasource to this BO and have it run until the collection has reached its last record.
As I recall the main issue was that the BO had to already be in sorted order. The report designer/engine would not sort it for you.
Each report then had to fetch its own BO and since they had a common interface, the reports all ran with very little additional code outside the base classes of my framework.
Joe
So you put all the parameter, title, desc, etc. logic in the ROB
and then it has a basically lazy loaded ROC property with the data?
Do you have anything that is easy to share or pm me? Only if it
is simple for you.
Thanks
jack
From: JoeFallon1 [mailto:cslanet@lhotka.net]
Sent: October-12-09 10:15 AM
To: jaddington@alexandergracie.com
Subject: Re: [CSLA .NET] DataDynamics Report/Analysis Suite
In my web app I use DataDynamics Active Reports with CSLA.
It works great! I designed a framework for my reports where a ReadOnly BO is
always fetched for header data and a ROC is used for the detail data. Then I
can set the report datasource to this BO and have it run until the collection
has reached its last record.
As I recall the main issue was that the BO had to already be in sorted
order. The report designer/engine would not sort it for you.
Each report then had to fetch its own BO and since they had a common
interface, the reports all ran with very little additional code outside the
base classes of my framework.
Joe
You have the basic idea. I have a base class that does most of the work for each report.
Some base class variables are:
Protected mIndex As IntegerSome methods include:
Public Overridable Function GetReadOnlyBO() As IMyReadOnlyObjectEach report then overrides GetROC and GetSQLStatement to gather up the data for that report.
I use reflection to match the text box names to the BO property values with the assumption that txtPropertyName is how each text box is named.
Public Overridable Function GetDetailFieldList() As List(Of String)'Only find the TextBox members in the Report. Assumes they all start with "txt".
Dim rptMembers As MemberInfo() = Me.GetType.FindMembers(MemberTypes.Property, flags Or BindingFlags.NonPublic, Type.FilterNameIgnoreCase, "txt*")
For Each info As MemberInfo In rptMembers
'Since we know that we found only MemberTypes.Property we can safely cast the MemberInfo to a PropertyInfo.
rptProp = CType(info, PropertyInfo)
'verify that we are in fact dealing with a TextBox and not a mis-named control.
If rptProp.PropertyType.Name = "TextBox" Then
currentTextBox = CType(rptProp.GetValue(Me, Nothing), DataDynamics.ActiveReports.TextBox)
'find all text boxes that are NOT in the page header or page footer.
If Not (currentTextBox.Parent.GetType Is GetType(DataDynamics.ActiveReports.PageHeader) OrElse currentTextBox.Parent.GetType Is GetType(DataDynamics.ActiveReports.PageFooter)) Then
txtBoxName = rptProp.Name
'assumes all TextBox controls have a 3 letter prefix of txt and that the rest of the Name is the same as the BO Property. Case sensitive!
BOPropName = txtBoxName.Substring(3)
result.Add(BOPropName)
End If
End If
Next
Return result
End Function
Private
ReadOnly Property Props() As List(Of PropertyInfo)#Region
" Active Reports Methods " Protected Sub rpt_ReportStart(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ReportStart'define Detail fields collection
For Each prop As PropertyInfo In Props
Me.Fields.Add(prop.Name)
Next
End Sub
Joe
Joe
Thank you very much. One additional question ... was that a
pain for testing the formatting / outline of the report? Without a direct
database connection how would you preview the report while design it? I could
see with a simple grid report it wouldn't be a big deal but if you had a large
report with grouping and nested reports etc. it would be cumbersome to
deploy/run/test over and over???
Thanks
jack
From: JoeFallon1 [mailto:cslanet@lhotka.net]
Sent: October-14-09 8:03 AM
To: jaddington@alexandergracie.com
Subject: Re: [CSLA .NET] RE: DataDynamics Report/Analysis Suite
You have the basic idea. I have a base class that does most of the work for
each report.
Some base class variables are:
Protected mIndex As Integer
Protected mReadOnlyBO As
IPNIReadOnlyObject
Protected mROC As
IReadOnlyList
Protected flags As
BindingFlags = BindingFlags.Public Or
BindingFlags.IgnoreCase Or
BindingFlags.Instance
Protected mProps As
List(Of PropertyInfo)
Protected mRptTitle As
String = ""
Protected mRptCode As String = ""
Protected mFromTo As String = ""
Protected mDateStart As String = ""
Protected mDateEnd As String = ""
Some methods include:
Public Overridable Function GetReadOnlyBO() As
IMyReadOnlyObject
Dim mReportHdrRO As ReportHdrRO
mReportHdrRO = ReportHdrRO.NewReportHdrRO
mReportHdrRO.RptCode = Me.RptCode
mReportHdrRO.RptTitle = Me.RptTitle
mReportHdrRO.FromTo = Me.FromTo
Return mReportHdrRO
End Function
Public Overridable Function GetROC() As
IReadOnlyList
Return Nothing
End Function
Protected Overridable Function GetSQLStatement() As
String
Return ""
End Function
Each report then overrides GetROC and GetSQLStatement to gather up the data
for that report.
I use reflection to match the text box names to the BO property values with
the assumption that txtPropertyName is how each text box is named.
Public Overridable Function GetDetailFieldList() As List(Of String)
Dim result As New List(Of String)
Dim txtBoxName As
String
Dim BOPropName As String
Dim currentTextBox As
DataDynamics.ActiveReports.TextBox
Dim rptProp As
PropertyInfo
'Only find the TextBox members in
the Report. Assumes they all start with "txt".
Dim rptMembers As
MemberInfo() = Me.GetType.FindMembers(MemberTypes.Property,
flags Or BindingFlags.NonPublic,
Type.FilterNameIgnoreCase, "txt*")
For Each info As
MemberInfo In rptMembers
'Since we know that we found only
MemberTypes.Property we can safely cast the MemberInfo to a PropertyInfo.
rptProp = CType(info, PropertyInfo)
'verify that we are in fact dealing with a TextBox
and not a mis-named control.
If rptProp.PropertyType.Name = "TextBox" Then
currentTextBox = CType(rptProp.GetValue(Me, Nothing),
DataDynamics.ActiveReports.TextBox)
'find all text boxes that are NOT in the page
header or page footer.
If Not
(currentTextBox.Parent.GetType Is < FONT color=#0000ff size=2>GetType(DataDynamics.ActiveReports.PageHeader)
OrElse currentTextBox.Parent.GetType Is GetType(DataDynamics.ActiveReports.PageFooter))
Then
txtBoxName = rptProp.Name
'assumes all TextBox controls have a 3 letter prefix
of txt and that the rest of the Name is the same as the BO Property. Case
sensitive!
BOPropName = txtBoxName.Substring(3)
result.Add(BOPropName)
End If
End If
Next
Return result
End Function
Private ReadOnly Property Props() As List(Of PropertyInfo)
Get
If mProps Is Nothing Then
mProps = New List(Of PropertyInfo)
Dim infoType As Type = mROC.GetType().GetProperty("Item", New Type()
{GetType(Integer)}).PropertyType
'get an array of all Properties
of the ROC (could be 50 or more)
D im tempProps() As
PropertyInfo = infoType.GetProperties(flags)
'get the list of fields in
the specific report detail section. (typically, can be less than 10)
Dim detailFields As List(Of
String) = GetDetailFieldList()
If detailFields Is Nothing Then
'if the developer does not
provide a list of fields then add all Properties to mProps.
For Each prop As PropertyInfo
In tempProps
mProps.Add(prop)
Next
Else
For Each prop As PropertyInfo In tempProps
'This is case sensitive. So
the BO Property names must be spelled the same way in the GetDetailFieldList
method.
If
detailFields.Contains(prop.Name) Then
mProps.Add(prop)
End If
Next
End If
End If
Return mProps
End Get
End Property
Then write code for each of the Active Report methods:
#Region " Active Reports
Methods "
Protected Sub rpt_ReportStart(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Me.ReportStart
mIndex = 0
mHoldString = String.Empty
mFirstLine = True
'passes criteria values from speciality
ReportBaseFrame to ReportHeaderRO
'for data binding to Page Header/Footer bands
mReadOnlyBO = GetReadOnlyBO()
'to populate ROC using SQL Statement
mROC = GetROC()
End Sub
'set txtTextBox.Value for all
sections except the Detail section which is set in rpt_FetchData.
Protected Sub
rpt_DataInitialize(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.DataInitialize
Dim txtBoxName As
String
Dim BOPropName As String
Dim BOType As Type = mReadOnlyBO.GetType
Dim currentTextBox As
DataDynam ics.ActiveReports.TextBox
Dim rptProp As
PropertyInfo
'Only find the TextBox members in the Report. Assumes
they all start with "txt".
Dim rptMembers As
MemberInfo() = Me.GetType.FindMembers(MemberTypes.Property,
flags Or BindingFlags.NonPublic, Type.FilterNameIgnoreCase,
"txt*")
For Each info As
MemberInfo In rptMembers
'Since we know that we found only
MemberTypes.Property we can safely cast the MemberInfo to a PropertyInfo.
rptProp = CType(info, PropertyInfo)
'verify that we are in fact dealing with a TextBox
and not a mis-named control.
If rptProp.PropertyType.Name = "TextBox" Then
currentTextBox = CType(rptProp.GetValue(Me, Nothing),
DataDynamics.ActiveReports.TextBox)
'set currentTextBox.Value for PageHeader and
PageFooter only.
'If currentTextBox.Parent.GetType
IsNot GetType(DataDynamics.ActiveReports.Detail) Then
If (currentTextBox.Parent.GetType Is GetType(DataDynamics.ActiveReports.PageHeader)
OrElse currentTextBox.Parent.GetType Is GetType(DataDynamics.ActiveReports.PageFooter))
Then
txtBoxName = rptProp.Name
'assumes all TextBox controls have a 3 letter prefix
of txt and that the rest of the Name is the same as the BO Property.
BOPropName = txtBoxName.Substring(3)
Dim BOprop As
PropertyInfo = BOType.GetProperty(BOPropName, flags)
If BOprop IsNot
Nothing Then
'the BO and the TextBox w/o the prefix are the
same name so set TextBox.Value = BO.Property.
currentTextBox .Value = BOprop.GetValue(mReadOnlyBO, Nothing)
End If
End If
End If
Next
'see if there is a Picture in the report
Header. If there is, set the Image property to the BO.Logo value
'which is a System.Drawing.Image created from an array of Bytes.
'Assumes the name of the control has a prefix of "img".
rptMembers = Me.GetType.FindMembers(MemberTypes.Property,
flags Or BindingFlags.NonPublic,
Type.FilterNameIgnoreCase, "img*")
For Each info As
MemberInfo In rptMembers
rptProp = CType(info, PropertyInfo)
'verify that we are in fact dealing with a Picture
and not a mis-named control.
If rptProp.PropertyType.Name = "Picture" Then
Dim currentControl As
DataDynamics.ActiveReports.Picture = CType(rptProp.GetValue(Me, Nothing),
DataDynamics.ActiveReports.Picture)
'set currentControl.Image for all
sections except the Detail section which is set in rpt_FetchData.
If currentControl.Parent.GetType IsNot GetType(DataDynamics.ActiveReports.Detail)
Then
txtBoxName = rptProp.Name
'assumes all Picture controls have a 3 letter prefix
of img and that the rest of the Name is the same as the BO Property.
'In this case the name is
imgLogo.
BOPropName = txtBoxName.Substring(3)
Dim BOprop As
PropertyInfo = BOType.GetProperty(BOPropName, flags)
If BOprop IsNot
Nothing Then
'the BO and the Picture control w/o the prefix
are the same name so set Control.Image = BO.Property.
currentControl.Image = CType(BOprop.GetValue(mReadOnlyBO,
Nothing), Drawing.Image)
End If
End If
End If
Next
'define Detail fields collection
For Each prop As PropertyInfo In
Props
Me.Fields.Add(prop.Name)
Next
End Sub
'Add data to the Fields in the
Detail section using 1 row at a time from the ROC.
'mIndex is incremented each time FetchData runs.
'This will fetch the next row from the ROC.
'It will run until we set eArgs.EOF = True.
Protected Sub rpt_FetchData(ByVal sender As Object, ByVal eArgs As
DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs) Handles Me.FetchData
Dim target As Csla.Core.IReadOnlyObject
Dim holdValue As
String = String.Empty
If mIndex = mROC.Count Then
'stops firing this event
eArgs.EOF = True
Else
target = mROC.Item(mIndex, True)
For Each prop As PropertyInfo In
Props
Me.Fields(prop.Name).Value =
prop.GetValue(target, Nothing)
Next
'increment the index for the ROC.
mIndex += 1
'continue to fire rpt_FetchData event
eArgs.EOF = False
End If
End Sub
#End Region
Joe
I built the framework and turned it over to someone else to build all the reports. A lot of them were written in another language so they had to be translated. But the layouts were all pretty simple - a few columns and some subtotals. Some reports were more complex but they all ended up working this way.
BTW - the code I shared is alot of the Base class but not all of it. There are other things that need to be done too. But that should get you started diwn the right path of using CSLA and Active Reports together in a general way.
Joe
Copyright (c) Marimer LLC