
Table of contents
- Revision history
- Introduction
- Explaining the problem
- The business layer
- The services layer (a.k.a. facade)
- The client application
- Exploring the problem
- A solution!
- Designing the solution
- Dynamic proxy class generation and compilation
- Caching compiled types for performance
- Providing thread safety
- Demo application: CodeProjectMonitor
- Recommended reading
Introduction
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
Get
Return _name
End Get
Set (value As String)
_name = value
End Set
End Property
Public Property Email As String
Get
Return _email
End Get
Set (value As String)
_email = value
End Set
End Property
Public Readonly Property Display As String
Get
Return Name & " (" & Email & ")"
End Get
End Property
End Class
Public Class CustomerCollection
Inherits CollectionBase
Public Sub New()
Mybase.New
End Sub
Public Sub Add(customer As Customer)
MyBase.InnerList.Add(customer)
End Sub
Public Default Property Item(index As Integer) As Customer
Get
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.
<WebMethod()> Public Function GetCustomer(name As String) As Customer
Dim c As New Customer
c.Name = "Jan Tielens"
c.Email = "jan@leadit.be"
Return c
End Function
<WebMethod()>
Public Function GetCustomerCollection() As CustomerCollection
Dim custColl As New CustomerCollection
Dim c1 As New Customer
c1.Name = "Jan Tielens"
c1.Email = "jan@leadit.be"
Dim c2 As New Customer
c2.Name = "Jan Tielens"
c2.Email = "jan@leadit.be"
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.
Dim eng As New Facade.CustomerEngine
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:

Public Class CustomerWrapper
Private _customer As Facade.Customer
Public Sub SetInternalObject(customer As Facade.Customer)
_customer = customer
End Sub
Public Property Name As String
Get
Return _customer.Name
End Get
Set (value As String)
_customer.Name = value
End Set
End Property
Public Property Email As String
Get
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:
DataGrid2.DataSource = Leadit.Utils.WebServices._
WebServiceWrapper.GetArrayList(customers)
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):
Dim code As New CodeTypeDeclaration(typeToWrap.Name & "WithProperties")
code.IsClass = True
Dim constructor As New CodeConstructor()
constructor.Attributes = MemberAttributes.Public
code.Members.Add(constructor)
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.
Dim declaration As New _
CodeMemberField(typeToWrap, "_" & _
LCase(typeToWrap.Name))
declaration.Attributes = MemberAttributes.Private
code.Members.Add(declaration)
Dim setInnerObject As New CodeMemberMethod()
setInnerObject.Name = "SetInnerObject"
setInnerObject.Parameters.Add(_
New CodeParameterDeclarationExpression(typeToWrap,_
LCase(typeToWrap.Name)))
setInnerObject.Statements.Add(New CodeAssignStatement(_
New CodeVariableReferenceExpression("_" & _
LCase(typeToWrap.Name)), _
New CodeVariableReferenceExpression_
(LCase(typeToWrap.Name))))
setInnerObject.Attributes = MemberAttributes.Public
code.Members.Add(setInnerObject)
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
code.Members.Add(getInnerObject)
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.
Dim fieldinfo As System.Reflection.FieldInfo
For Each fieldinfo In typeToWrap.GetFields()
Dim prop As New CodeMemberProperty()
prop.Name = fieldinfo.Name
prop.Attributes = MemberAttributes.Public
prop.HasGet = True
prop.HasSet = True
prop.Type = New System.CodeDom._
CodeTypeReference(fieldinfo.FieldType)
Dim getter As New CodeMethodReturnStatement_
(New CodeVariableReferenceExpression(_
"_" & typeToWrap.Name & _
"." & fieldinfo.Name))
prop.GetStatements.Add(getter)
Dim setter As New CodeAssignStatement(_
New CodeVariableReferenceExpression("_" & _
typeToWrap.Name & "." & fieldinfo.Name), _
New CodeVariableReferenceExpression("value"))
prop.SetStatements.Add(setter)
code.Members.Add(prop)
Next
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:
Dim ns As New CodeNamespace(typeToWrap.Name & "Wrapper")
ns.Types.Add(code)
Dim codeUnit As New System.CodeDom.CodeCompileUnit()
codeUnit.Namespaces.Add(ns)
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):
Dim compilerParams As New compiler.CompilerParameters()
compilerParams.GenerateInMemory = True
Dim assemblyFileName As String = Reflection.Assembly._
GetEntryAssembly.GetName.CodeBase
If assemblyFileName.IndexOf("file:///") > -1 Then
compilerParams.ReferencedAssemblies.Add_
(assemblyFileName.Substring(Len("file:///")))
End If
Dim compiler As compiler.ICodeCompiler = New _
VBCodeProvider().CreateCompiler
Dim results As compiler.CompilerResults = _
compiler.CompileAssemblyFromDom(compilerParams, _
codeUnit)
If results.Errors.HasErrors Then
Dim errorString As String = _
"Compilation errors: " & vbCrLf
Dim err As System.CodeDom.Compiler.CompilerError
For Each err In results.Errors
errorString = err.ToString & vbCrLf
Next
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
Dim oldObj As Object
Dim newType As Type = GetWrapperType_
(arrayToConvert(0).GetType)
Dim newArray As New ArrayList()
For Each oldObj In arrayToConvert
Dim newObj As Object = Activator_
.CreateInstance(newType)
newObj.SetInnerObject(oldObj)
newArray.Add(newObj)
Next
Return newArray
Else
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()
DataGrid1.DataSource = latestBrief.GetLatestArticleBrief(10)
DataGrid2.DataSource = WebServiceWrapper._
GetArrayList(latestBrief.GetLatestArticleBrief(10))
End Sub
Conclusion
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:
DataGrid1.DataSource = wsResult
DataGrid2.DataSource = Leadit.Utils.WebServices_
.WebServiceWrapper.GetArrayList(wsResult)
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