Click here to Skip to main content
13,896,986 members
Click here to Skip to main content
Add your own
alternative version


124 bookmarked
Posted 19 Feb 2003

Exposing custom-made classes through a Webservice and binding them to a DataGrid.

, 26 Feb 2003
Rate this:
Please Sign up or sign in to vote.
When you want to expose your custom made business objects through a webservice interface, and you want them to bind with a DataGrid, you have a problem that the generated proxy class exposes fields instead of properties. A possible solution is to generate at runtime, a wrapper for your proxy class.

Sample Image - CodeProjectMonitor.jpg

Table of contents

  1. Revision history
  2. Introduction
  3. Explaining the problem
    1. The business layer
    2. The services layer (a.k.a. facade)
    3. The client application
    4. Exploring the problem
  4. A solution!
  5. Designing the solution
    1. Dynamic proxy class generation and compilation
    2. Caching compiled types for performance
    3. Providing thread safety
  6. Demo application: CodeProjectMonitor
  7. Recommended reading


Creating and consuming XML Web services in Visual Studio.NET is really easy. But when you want to expose your custom build business entities through a Web service you will experience some problems (or at least I did), when you want to bind your objects to a DataGrid. This article shows a possible solution to overcome this by generating at runtime a wrapper class that encapsulates the original proxy class. Maybe this sounds like rocket science, but with the CodeDom namespace, this is pretty easy.

The first part of the article describes how to setup a solution to simulate this behavior and to explain what the problem exactly is. The second part is the most interesting part, here the solution code is explained. If you don't want to dive into the technical stuff, and just use the solution, the full working source code, and a compiled DLL can be downloaded.

Explaining the problem

To explain the problem, I will use a very simple example. Suppose I have to build a solution that provides information about the customers in a company. The solution needs to expose a services layer (facade) that uses web services to provide the needed functionality. The application architecture is a standard 3-tier architecture. For more information see the links at then end of the article. As documented in MSDN, the structure of such an application would look like this:

The business layer

In the business layer you will define your business entity classes. In this example (for keepings things simple), we will only need 1 business entity, our Customer class. This class will be our data carrier and contains some business logic. Besides this business entity, we will need a container that can contain multiple Customer objects, that will be the CustomerCollection. So the classes needed in the business layer can be coded like this:

Public Class Customer
    Private _name As String
    Private _email As String

    Public Property Name As String
            Return _name
        End Get
        Set (value As String)
            _name = value
        End Set
    End Property

    Public Property Email As String
            Return _email
        End Get
        Set (value As String)
            _email = value
        End Set
    End Property

    Public Readonly Property Display As String
            'Return a string for displaying data: a very simple business rule
            Return Name & " (" & Email & ")"
        End Get
    End Property
End Class

Public Class CustomerCollection
    Inherits CollectionBase

    Public Sub New()
    End Sub

    Public Sub Add(customer As Customer)
    End Sub

    Public Default Property Item(index As Integer) As Customer
            Return MyBase.InnerList(index)
        End Get
        Set (value As Customer)
            _MyBase.InnerList(index) = value
        End Set
    End Property        
End Class

The services layer (a.k.a. facade)

This layer will be used by other solutions to access our data. We will create a WebService class that has functionality to do the standard operations on our Customer object: retrieve, save, ... . For retrieval, we have 2 choices, first we can retrieve 1 customer (return value is a Customer object), secondly we can retrieve multiple customers (return value is a CustomerCollection object). For simplicity of this example I will provide only 2 methods that can be used to for retrieval. In a real life solution, there would be some methods to add and alter customers.

'In our WebService class:
<WebMethod()> Public Function GetCustomer(name As String) As Customer
    'In real life, you would retrieve the customer from a database,
    'for now I will return always the same customer...
    Dim c As New Customer
    c.Name = "Jan Tielens"
    c.Email = ""
    Return c
End Function

Public Function GetCustomerCollection() As CustomerCollection
    'In real life, there would be passed 1 or
    'more paramters that would be used
    'to retrieve a set of Customers from the database.
    Dim custColl As New CustomerCollection

    'Construct some sample customers
    Dim c1 As New Customer
    c1.Name = "Jan Tielens"
    c1.Email = ""
    Dim c2 As New Customer
    c2.Name = "Jan Tielens"
    c2.Email = ""

    'Add them to the database
    custColl.Add c1
    custColl.Add c2
End Function

The client application

Our sample client application will have only 1 function: retrieve a collection of customers and display them in a DataGrid. Here will our problem start! We have a simple Windows Form with 1 Button and 1 DataGrid on it. When the Button is pressed, the client app. connects to the services interface (our WebService facade) and retrieves some customers.

'Code called when the button is pressed:
Dim eng As New Facade.CustomerEngine
'Facade is the name we gave our WebSerice reference
'CustomerEngine is the name of the WebService class (see above)

DataGrid1.DataSource = CustomerEngine.GetCustomerCollection

Exploring the problem

When you try to execute the steps as described above, or take a look at the included demo application, you will notice that the DataGrid does not display the data we wanted to display. In fact, it only shows the number of rows that there should be, but the Name and Email column are missing. Why is this??

The reason for this behavior is quite easy to understand if you know where to look for it. The result of the call to the web service (CustomerEngine.GetCustomerCollection) gives us as a return value an array of Customer objects. So far, so good, arrays can be displayed by DataGrids. So there must be something wrong with our Customer objects?

That's right, when you made a web reference (in the client app.) to the Webservice, Visual Studio generated a proxy class for your Customer class, based on the description available in the WSDL of the WebService. The code for that proxy class can be found in the Reference.vb file. When you explore the proxy class code, you will notice that your generated proxy class basically looks like this:

Public Class Customer
    Public Name As String
    Public Email As String
End Class

The proxy class exposes our properties as fields: Public Property Name ... End Property is replaced with Public Name As String. That is why the DataGrid does not show the Name and Email columns! The DataGrid only shows properties, so for our proxy class, there aren't any...

A solution!

Ok, the problem is explained. It's time for a possible solution. We need to convert those fields back to properties. It's possible to alter the Reference.vb file, and change the fields back to properties (for a link see the end of the article), but this gives you the drawback, that every time the Webservice changes, and you refresh your Web reference, the changes will be deleted and replaced by a new generated proxy class.

Programmers are lazy, so this is not a very handy solution. We could write a wrapper class that has Name and Email properties and internally has a Customer object (instance of the proxy class). Then it would be possible to build an array of instances of the wrapper class (with instances of proxy classes inside of them), and pass that array to the DataGrid. Like this, the DataGrid would be able to display the Name and Email columns and retrieve the values form the inner proxy class instance. Such a proxy class could look like this:

UML Diagram

Public Class CustomerWrapper
    'The original instance of the wrapper class
    Private _customer As Facade.Customer 

    Public Sub SetInternalObject(customer As Facade.Customer)
        _customer = customer
    End Sub

    Public Property Name As String
            Return _customer.Name
        End Get
        Set (value As String)
            _customer.Name = value
        End Set
    End Property

    Public Property Email As String
            Return _customer.Email
        End Get
        Set (value As String)
            _customer.Email = value
        End Set
    End Property
End Class

This will work, but as you see, doing this manually for each proxy class would be not only quite some work, but also quite boring and repetitive work. Knowing that programmers are lazy, this is a bad thing. So why not let .NET take care of this? The .NET framework has some built-in functionality to build code at runtime and even compile that code at runtime, so those classes could be used immediately! The technology used is the CodeDom.

As you can see in the schema, when you want to display a collection of customers in a DataGrid, the first thing is making a call to the Webservice. The result is an array of Customer object (from the generated proxy class). Then you would use the WebServiceWrapper that provides for each Customer in the array, a wrapper, and puts them in a new array of CustomerWrapper objects. This array finally can be passed to a DataGrid that will show all the properties correctly. This sounds pretty complicated, but when you want to use it, it's only one line of code:

'Suppose customers is the array of Customer objects
DataGrid2.DataSource = Leadit.Utils.WebServices._

Designing the solution

And now, finally, the fun part: coding the WebServiceWrapper class!! For an overview of the complete code, I suggest you download and take a look and the source code. For now, I will explain the most important parts of the WebServiceWrapper class.

Dynamic proxy class generation and compilation

As I said, the technology that we will use is the CodeDom, it's like an XMLDomDocument, but for code. If you want a tutorial for the CodeDom, I will provide some links at the end of the article to get you started.

The first thing we need is the base container: a CodeTypeDeclaration (root node in XML):

'Create new codetype declaration for the new wrapper class
Dim code As New CodeTypeDeclaration(typeToWrap.Name & "WithProperties")
code.IsClass = True

'Create constructor
Dim constructor As New CodeConstructor()
constructor.Attributes = MemberAttributes.Public

The typeToWrap variable contains the Type of the original Customer proxy class. The name of our new type will be this original type's name with WithProperties added. As you can see there is also a simple constructor provided.

'Create declaration for inner object
Dim declaration As New _
   CodeMemberField(typeToWrap, "_" & _
declaration.Attributes = MemberAttributes.Private

'Create function to set inner object
Dim setInnerObject As New CodeMemberMethod()
setInnerObject.Name = "SetInnerObject"
New CodeParameterDeclarationExpression(typeToWrap,_
setInnerObject.Statements.Add(New CodeAssignStatement(_
       New CodeVariableReferenceExpression("_" & _
       LCase(typeToWrap.Name)), _
       New CodeVariableReferenceExpression_
setInnerObject.Attributes = MemberAttributes.Public

'Create function to get inner object
Dim getInnerObject As New CodeMemberMethod()
getInnerObject.Name = "GetInnerObject"
getInnerObject.ReturnType = New CodeTypeReference(typeToWrap)
getInnerObject.Statements.Add(New CodeMethodReturnStatement(_
    New CodeVariableReferenceExpression("_" _
    & typeToWrap.Name)))
getInnerObject.Attributes = MemberAttributes.Public

The block code above, will add the SetInnerObject and GetInnerObject functions to our wrapper class. These functions are used to assign the original instances of the proxy class to instances of the wrapper class. There is also a declaration added to store an instance of the proxy class.

Now, we have a basic layout for our wrapper class, with functionality to get, set and store an instance of the proxy class. Next we will add for each field of the proxy class, a property to the new wrapper class. To loop through all the fields of the proxy class type, we use the GetFields method of the type.

'Add for each public field, a public property to the new class
Dim fieldinfo As System.Reflection.FieldInfo
For Each fieldinfo In typeToWrap.GetFields()
    'Create property
    Dim prop As New CodeMemberProperty()
    prop.Name = fieldinfo.Name
    prop.Attributes = MemberAttributes.Public
    prop.HasGet = True
    prop.HasSet = True
    prop.Type = New System.CodeDom._

    'Create getter for property
    Dim getter As New CodeMethodReturnStatement_
           (New CodeVariableReferenceExpression(_
           "_" & typeToWrap.Name & _
           "." & fieldinfo.Name))

    'Create setter for property
    Dim setter As New CodeAssignStatement(_
     New CodeVariableReferenceExpression("_" & _
        typeToWrap.Name & "." & fieldinfo.Name), _
        New CodeVariableReferenceExpression("value"))

    'Add the propery to the class

That's it! Our new wrapper class is defined in memory. To compile the class, so we can use it, we need to do the following steps:

'Create and add namespace
Dim ns As New CodeNamespace(typeToWrap.Name & "Wrapper")

'Create codeunit and add the namespace
Dim codeUnit As New System.CodeDom.CodeCompileUnit()


A new Namespace is declared and the class is added to that Namespace. Then a new CodeUnit is instantiated, and will be used to compile the class. The Namespace is added to the CodeUnit. So the CodeUnit contains the namespace with our wrapper class.

To compile the CodeUnit we have to use the VBCodeProvider (or you can choose for the CSharpCodeProvider):

'Set compiler parameters
Dim compilerParams As New compiler.CompilerParameters()
compilerParams.GenerateInMemory = True
Dim assemblyFileName As String = Reflection.Assembly._
If assemblyFileName.IndexOf("file:///") > -1 Then
End If

'Compile the codeunit
Dim compiler As compiler.ICodeCompiler = New _
Dim results As compiler.CompilerResults = _
    compiler.CompileAssemblyFromDom(compilerParams, _
If results.Errors.HasErrors Then
    'There went something wrong:
    'construct a nice error message
    Dim errorString As String = _
        "Compilation errors: " & vbCrLf
    Dim err As System.CodeDom.Compiler.CompilerError
    For Each err In results.Errors
        errorString = err.ToString & vbCrLf
    Throw New ApplicationException(errorString)
End If

First I set some compilerparameters and add a reference to our solution. This is done by getting the EntryAssembly's filename and adding it to the ReferencedAssemblies. Then we get a Compiler from the VBCodeProvider and use it to compile our CodeUnit. If something goes wrong, an error is constructed and thrown, that contains all the compilation errors. To use the compiled class we return the newly compiled type:

Return results.CompiledAssembly.GetType(typeToWrap.Name _
     & "Wrapper." & typeToWrap.Name & "WithProperties")

Now we have a fresh compiled type for our wrapper class, and we need to construct an array of wrapper instances that contain our original instances of the array of proxy classes. Therefore we will use following function:

Public Shared Function GetArrayList(ByVal _
      arrayToConvert() As Object) As ArrayList
    SyncLock GetType(Leadit.Utils.Webservices.WebServiceWrapper)
        If arrayToConvert.Length > 0 Then
            'Add instances of the new class to a new ArrayList
            Dim oldObj As Object
            Dim newType As Type = GetWrapperType_
            Dim newArray As New ArrayList()
            For Each oldObj In arrayToConvert
                Dim newObj As Object = Activator_

            'Return the constructed arraylist
            Return newArray
            Return Nothing
        End If
    End SyncLock
End Function

Don't worry about the SyncLock, I will explain it later. As you can see, we will use the Activator class to get instances of our compiled wrapper class. Also notice that the return type is an ArrayList that is build by looping through all of the elements of the arrayToConvert that contains the proxy class instances, and generating a wrapper class instance for each of them. That is the result we wanted!

Caching compiled types for performance

As you will see in the complete code, there is a simple caching mechanism. So once a wrapper class for a specific type is generated and compiled, it will be stored in a HashTable. If the WebServiceWrapper is called again, it first checks if the type was already compiled. If so, the wrapper class can be used immediately, from the cache. To provide the cache mechanism, the Singleton design pattern is used.

Providing thread safety

To implement the Singleton Design Pattern, and for the ease of use, most of the functions are implemented as Shared. By doing this, the danger exists that 2 threads access the the same function at the same time, and that is bad! To avoid this, a SyncLock is used to provide Thread safety.

Demo application: CodeProjectMonitor

If you will take a look at the code of this project, you will see that it's very simple... What does this application? Well, this application connects to the CodeProject webservice and retrieves the latest articles. The results are bound to 2 DataGrids, first without using the WebServiceWrapper, then using the WebServiceWrapper. As you will see (screen shot at the top of this article), in the second DataGrid, all the columns are displayed with all of the data. The first DataGrid only displays the number of rows, without the data columns.

When you press the button on the form, the following code will be executed. As you see, using the WebServiceWrapper is only a matter of adding a few statements!

Private Sub Button1_Click(ByVal sender As_
             System.Object, ByVal e As System.EventArgs)_
             Handles Button1.Click
    Dim latestBrief As New com.codeproject.www.LatestBrief()

    'Do not use the WebServiceWrapper
    DataGrid1.DataSource = latestBrief.GetLatestArticleBrief(10)

    'Use the WebServiceWrapper
    DataGrid2.DataSource = WebServiceWrapper._
End Sub


As you can see in the picture at the top of this article, by using the WebServiceWrapper, we get the functionality that was needed: the Name and Email properties are displayed in the DataGrid. The beauty of this solution is (in my opinion), that it's very easy to use, and provides great flexibility. As you can see below, using the WebServiceWrapper is very transparent:

'wsResult is the webservice result (array of objects)
'Do NOT use the wrapper
DataGrid1.DataSource = wsResult
'Do use the wrapper
DataGrid2.DataSource = Leadit.Utils.WebServices_

On the other hand all of the problems would be avoided if we use strong typed DataSets instead of our custom build business entities. If you live in a Microsoft only world, using strong typed DataSets, could be an advantage, but using custom build business entities gives you greater flexibility. A very good article about this is provided at the end of this article.

Recommended reading

Revision history

  • 20 Feb. 2003: First release
  • 20 Feb. 2003: Added sample project: CodeProjectMonitor
  • 27 Feb. 2003: Added TOC and UML diagram, some minor text changes


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Jan Tielens
Web Developer U2U
Saudi Arabia Saudi Arabia
No Biography provided

You may also be interested in...

Comments and Discussions

GeneralMy vote of 1 Pin
PeaceTiger7-Jan-11 2:27
memberPeaceTiger7-Jan-11 2:27 
GeneralMy vote of 5 Pin
WRHart18-Aug-10 10:40
memberWRHart18-Aug-10 10:40 
GeneralError Pin
Evidica Patrick14-Aug-08 4:21
memberEvidica Patrick14-Aug-08 4:21 
QuestionCan we use Datasets Pin
ksivasenthil15-Apr-08 22:48
memberksivasenthil15-Apr-08 22:48 
QuestionAren't we creating our business object once again..? Pin
sekhar_nit23-Oct-07 22:10
membersekhar_nit23-Oct-07 22:10 
QuestionTools required for visualizing illustrated images Pin
hohuy3-Jun-07 20:52
memberhohuy3-Jun-07 20:52 
GeneralThis solution looks likeit will be some help Pin
codegalaxy24-May-07 9:06
membercodegalaxy24-May-07 9:06 
GeneralVery good Work! Pin
Dahan Abdo27-Mar-07 8:38
memberDahan Abdo27-Mar-07 8:38 
GeneralHere's the C# equivelant Pin
Martijn5-Jul-06 7:13
memberMartijn5-Jul-06 7:13 
GeneralI want to return Custom Data From Web Service Pin
M. Saifullah5-Feb-06 19:56
memberM. Saifullah5-Feb-06 19:56 
GeneralRe: I want to return Custom Data From Web Service Pin
Emil Åström24-Mar-06 22:20
memberEmil Åström24-Mar-06 22:20 
GeneralRe: I want to return Custom Data From Web Service Pin
sekhar_nit23-Oct-07 22:13
membersekhar_nit23-Oct-07 22:13 
GeneralDataGrid not sorting anymore Pin
longrun20054-Feb-06 7:23
memberlongrun20054-Feb-06 7:23 
GeneralC# version Pin
Sam Dahan26-Aug-05 6:01
memberSam Dahan26-Aug-05 6:01 
GeneralOption Strict on Pin
john palmer6-May-05 8:56
memberjohn palmer6-May-05 8:56 
Generalfor each loop Pin
ztg10-Feb-05 12:00
memberztg10-Feb-05 12:00 
GeneralThere's another solution... Pin
Emil Åström14-Jan-05 0:36
memberEmil Åström14-Jan-05 0:36 
GeneralRe: There's another solution... Pin
Julian Slater24-Mar-06 15:51
memberJulian Slater24-Mar-06 15:51 
QuestionGreat Job.. but how can we access the inner class? Pin
physt_e19-May-04 6:51
memberphyst_e19-May-04 6:51 
QuestionHow would you change the HeaderText ? Pin
LBrettsinclair17-May-04 12:17
memberLBrettsinclair17-May-04 12:17 
GeneralAbout the problem in WEBService Pin
55515-Feb-04 22:03
member55515-Feb-04 22:03 
GeneralWebservicewrapper + DynwsLib Pin
NSieger30-Jan-04 21:53
memberNSieger30-Jan-04 21:53 
GeneralComments and Suggestions Pin
xianuz6-Jan-04 1:07
memberxianuz6-Jan-04 1:07 
GeneralRe: Comments and Suggestions Pin
Jan Tielens10-Jan-04 0:51
memberJan Tielens10-Jan-04 0:51 
Generalmy test failed! Pin
iceecho16-Jul-03 11:52
membericeecho16-Jul-03 11:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190306.1 | Last Updated 27 Feb 2003
Article Copyright 2003 by Jan Tielens
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid