In the current application that I am building, I came across this scenario where I had to add properties to an object during runtime.
Basically it is like this:
I had to display some data of a parent object on a grid – but the number of columns to display depended on the number of child objects the parent had. I usually build a façade object as a standard practice to bind any such modified/tweaked/manipulated data objects.
In order to explain the problem clearer, let me give an example.
Say we have “Product”, “ProductVersion” and “Order”. There could be multiple versions for the product and the relationship is one-to-many. “Order” and “ProductVersion” have a foreign key constraint.
In the middle tier we have Product class and ProductVersion class that map directly to each table. The requirement is to display in grid on the Products page that shows the following –
· A side by side comparison of each version’s cost breakdown
· Display is based on the Order Status
Product’s Order Status | Version 1 | Version 2 | Version 3 |
Shipped | $1024 | $689 | $60 |
Processing | $488 | $843 | $593 |
Pending Payment | $734 | $1009 | $978 |
One way is to get this whole thing done in a stored procedure. But just to make it happen in the middle tier, I dived into the world of TypeDescriptionProvider, partly because on an earlier project, when we encountered a similar problem, we chickened out of it by forcing the user to change the functionality ( only due to lack of time).
Anyways, here goes the solution. You can get the actual code in zipped format here.
I am just explaining the most important parts:
The Class that I am going to build to bind with the grid is called – “ProductVersionFaçade”
The properties it will have – OrderStatus, and the rest of the properties would be Dynamically added.
Mark the façade class to use the TypeDescriptionProvider which we are about to build
Public Class ProductVersionFacade
Let us have a dictionary inside this to hold our dynamic properties. You will see its usage later.
We then create our TypeDescriptionProvider as follows:
Public Class ProductVersionFacadeTypeDescriptionProvider
Inherits TypeDescriptionProvider
And override the GetTypeDescriptor function, which will return a CustmTypeDescriptor, which is also something we are going to build:
Public Class ProductVersionFacadeTypeDescriptor
Inherits CustomTypeDescriptor
This is the class where the most important GetProperties function will be overridden and inside that we add our new bunch of properties like this:
With propCollection
Dim attArr() As Attribute
ReDim attArr(MyBase.GetAttributes.Count - 1)
MyBase.GetAttributes.CopyTo(attArr, 0)
If TypeOf (_instance) Is ProductVersionFacade Then
f = DirectCast(_instance, ProductVersionFacade)
For Each i As Integer In f.DetailHourDict.Keys
keyString = String.Format("ProductVersion_id{0}Cost", i)
.Add(DirectCast(New ProductVersionFacadePropertyDescriptor( _
keyString, GetType(Integer), attArr), PropertyDescriptor))
Next
End If
End With
This is where we add the properties to the instance’s dictionary. And if you note we are adding a custom PropertyTypeDescriptor, we need that too. So we build the ProductVersionFacadePropertyDescriptor which among many other things overrides the GetValue function to return the value for your newly added properties.
Back to the Façade class, we overload the default property of it as follows:
Default Public Property Item(ByVal fieldName As String) As Object
Get
Dim value As Object = Nothing
If _customPropsDict.ContainsKey(fieldName) Then
value = _customPropsDict(fieldName)
End If
Return value
End Get
Set(ByVal value As Object)
_customPropsDict(fieldName) = value
End Set
End Property
And a sample from the Page where it is bound:
Private Sub BindFacades()
Dim list As List(Of ProductVersionFacade)
list = ProductVersionFacadeSvc.BuildFacades(Me.Product)
Dim keyString As String = String.Empty
Dim idString As String = String.Empty
For Each f As ProductVersionFacade In list
For Each i As Integer In f.ProductVersionCostDict.Keys
keyString = String.Format("ProductVersion_id{0}Cost", i)
idString = String.Format("ProductVersion_id{0}", i)
If f._customPropsDict.ContainsKey(keyString) Then
f._customPropsDict(keyString) += f.ProductVersionCostDict(i)
Else
f._customPropsDict(keyString) = f.ProductVersionCostDict(i)
End If
f._customPropsDict(idString) = i
Next
Next
Me.FormatFacadeGrid(Me.ProductVersion)
With Me.ProductVersionFacadesGridViewCtl
.DataSource = list
.DataBind()
End With
End Sub
3 comments:
Awesome work!!!
May I know which gridview control u used? Why I cannot bind with my gridview. or can u provide the executable source.
thanks.
@CruZ
It was a simple ASP GridView control on an aspx page. The markup should be something like this:
<asp:GridView ID="ProductVersionFacadesGridViewCtl" CssClass="gridViewTable" AutoGenerateColumns="false" runat="server"/>
If you call the BindFacades() method(code already given), ideally from the Page_Load event, it should be able to bind the list returned by the FacadeSvc... Let me know if any further issues.
Post a Comment