Retuning Custom Collections (Complex Typed Objects) Over .Net Web Services.
My personal preference is to use custom typed collections (read portable types) as oppose to datasets, especially when trying to achieve an SOA. However this is easier said than done when working with .Net web services…or should I say .Net web references. Let me explain…
I have a class that looks like this…
///
/// Holds a collection of's.
///
[Serializable()]
[XmlRoot("LayerOptionChoiceCollection")]
public class LayerOptionChoiceCollection : BaseEntityCollection
{
public virtual void Add(LayerOptionChoiceEntity item)
{
List.Add(item);
}
public virtual void AddRange(LayerOptionChoiceEntity[] items)
{
foreach (LayerOptionChoiceEntity item in items)
{
List.Add(item);
}
}
public virtual LayerOptionChoiceEntity this[int index]
{
get
{
return (LayerOptionChoiceEntity)List[index];
}
set
{
List[index] = value;
}
}
}
… BaseEntityCollection inherits from CollectionBase giving it an ArrayList like functionality with its ‘List’ member, as well as some simple data binding capabilities.
cmbQuoteView.DataSource = this.LayerOptionChoiceCollection
cmbQuoteView.ValueMember = "DuckCreekQuoteID"
cmbQuoteView.DisplayMember = "DisplayText"
LayerOptionChoiceEntity is simply a class with private intrinsic members and public get/set properties to expose them.
I simply wanted to pass this typed collection over my web service to be able to bind it to a drop down in the UI. I created the following web method.
_
Public Function GetAllLayerOptions(ByVal programID As Integer) As LayerOptionChoiceCollection
Dim svc As New LayerOptionService()
Return svc.GetLayerOptionChioces(programID)
End Function
I added a web reference from my service façade, and I figured I would be on my way. I compile the app and I get the first error in my service façade class.
CS: Cannot implicitly convert type 'Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[]' to 'Endurance.Duckcreek.Data.LayerOptionChoiceCollection'
VB: Value of type '1-dimensional array of Endurance.WorkBench.ServiceFacade.DuckcreekWS.LayerOptionChoiceEntity' cannot be converted to 'Endurance.Duckcreek.Data.LayerOptionChoiceCollection'.
My first question, what is a Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[]?
Looking at the reference.cs file generated by wsdl.exe I see the following signature has been created for my method.
public LayerOptionChoiceEntity[] GetAllLayerOptions(int programID) {
object[] results = this.Invoke("GetAllLayerOptions", new object[] {
programID});
return ((LayerOptionChoiceEntity[])(results[0]));
}
Very interesting, so it wants to return an array of my entities instead of my collection type. Apparently this is by design. See the post from 2006-02-14, 3:59 am http://www.codecomments.com/archive321-2006-2-805600.html.
“…the client webservice proxy generate the types
according to the webservice's WSDL document. And WSDL document use XML
schema's standard types to represent different object types used in
webservice methods, this is for interop consideration since the webservice
will be consumed by different client platform (c++, java, php.....). And
for list/collection like type, WSDL will always use SOAP Array to represent
it, thus the .NET client proxy will generate the Array for such
objects(parameter or return type). If you do need to use .NET specific
type, you need to manually change the client genernated proxy code…”
The only exception to the rule, is, ironically, the DataSet (from the 2006-02-15, 3:58 am post).
“…So the further question you mentioned all aims at make the autogenerated
client proxy use our custom class type(.NET specific). I'm afraid this is
not available through the buildin webservice client proxy genenration
tool(wsdl.exe). The DataSet class you mentioned is a particular class(which
is somewhat hardcoded in the wsdl.exe tool's generation logic since there
is not particular attributes in description for DataSet as we can see).
I've tried DataTable class also which can not be recognized.
I think for your scenario, it is more likely that you're going to build a
custom webservice client proxy generation tool rather than extend the
current one. Anyway, currently the simplest means is to modify the
autogenerated proxy class code…”
Ok so, for my purposes I make the decision that I will change the generated web reference.
I dive into the reference.cs file and change all instances of the LayerOptionChoiceEntity[] to return my collection of LayerOptionChoiceCollection, and add the necessary namespace.
Build succeeded. Now the return type of my web service and web reference match. I run the application, an exception occurs. I check my event viewer and I see the following list of nested exceptions:
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Method DuckCreekService.GetAllLayerOptions can not be reflected.
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : There was an error reflecting 'GetAllLayerOptionsResult'.
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : There was an error reflecting type 'Endurance.Duckcreek.Data.LayerOptionChoiceEntity'.
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Types 'Endurance.Duckcreek.Data.LayerOptionChoiceEntity' and 'Endurance.WorkBench.ServiceFacade.DuckcreekWS.LayerOptionChoiceEntity' both use the XML type name, 'LayerOptionChoiceEntity', from namespace 'http://bm.endurance/Endurance.WorkBench.WebService/DuckCreekService'. Use XML attributes to specify a unique XML name and/or namespace for the type.
“My first question, what is a Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[]?”
Well, I have an answer. You have to remember that part of idea, if not most, behind a web service is that it can be consumed by another platform in-specific application. This works in reverse as well, when we create a web reference and wsdl.exe generates a proxy class, it assumes no knowledge of the ‘type’ (read assembly and class) that it is consuming. So it generates a class definition based on the xml it is consuming and it places this definition in the References.cs (or vb) file. And that is what a Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[] is. My assembly and class definition was Endurance.WorkBench.Duckcreek.Data.LayerOptionChoiceEntity, wsdl.exe generated a new definition Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity, using the name of the web reference.
The solution: delete this type from the reference.cs (or vb) class and no more namespace collision.
Point of interest: wsdl.exe appears to generate its types using xsd.exe. http://softwareink.blogspot.com/2006/05/xsdexe.html
Article of interest http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service07162002.asp
I have a class that looks like this…
///
/// Holds a collection of
///
[Serializable()]
[XmlRoot("LayerOptionChoiceCollection")]
public class LayerOptionChoiceCollection : BaseEntityCollection
{
public virtual void Add(LayerOptionChoiceEntity item)
{
List.Add(item);
}
public virtual void AddRange(LayerOptionChoiceEntity[] items)
{
foreach (LayerOptionChoiceEntity item in items)
{
List.Add(item);
}
}
public virtual LayerOptionChoiceEntity this[int index]
{
get
{
return (LayerOptionChoiceEntity)List[index];
}
set
{
List[index] = value;
}
}
}
… BaseEntityCollection inherits from CollectionBase giving it an ArrayList like functionality with its ‘List’ member, as well as some simple data binding capabilities.
cmbQuoteView.DataSource = this.LayerOptionChoiceCollection
cmbQuoteView.ValueMember = "DuckCreekQuoteID"
cmbQuoteView.DisplayMember = "DisplayText"
LayerOptionChoiceEntity is simply a class with private intrinsic members and public get/set properties to expose them.
I simply wanted to pass this typed collection over my web service to be able to bind it to a drop down in the UI. I created the following web method.
Public Function GetAllLayerOptions(ByVal programID As Integer) As LayerOptionChoiceCollection
Dim svc As New LayerOptionService()
Return svc.GetLayerOptionChioces(programID)
End Function
I added a web reference from my service façade, and I figured I would be on my way. I compile the app and I get the first error in my service façade class.
CS: Cannot implicitly convert type 'Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[]' to 'Endurance.Duckcreek.Data.LayerOptionChoiceCollection'
VB: Value of type '1-dimensional array of Endurance.WorkBench.ServiceFacade.DuckcreekWS.LayerOptionChoiceEntity' cannot be converted to 'Endurance.Duckcreek.Data.LayerOptionChoiceCollection'.
My first question, what is a Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[]?
Looking at the reference.cs file generated by wsdl.exe I see the following signature has been created for my method.
public LayerOptionChoiceEntity[] GetAllLayerOptions(int programID) {
object[] results = this.Invoke("GetAllLayerOptions", new object[] {
programID});
return ((LayerOptionChoiceEntity[])(results[0]));
}
Very interesting, so it wants to return an array of my entities instead of my collection type. Apparently this is by design. See the post from 2006-02-14, 3:59 am http://www.codecomments.com/archive321-2006-2-805600.html.
“…the client webservice proxy generate the types
according to the webservice's WSDL document. And WSDL document use XML
schema's standard types to represent different object types used in
webservice methods, this is for interop consideration since the webservice
will be consumed by different client platform (c++, java, php.....). And
for list/collection like type, WSDL will always use SOAP Array to represent
it, thus the .NET client proxy will generate the Array for such
objects(parameter or return type). If you do need to use .NET specific
type, you need to manually change the client genernated proxy code…”
The only exception to the rule, is, ironically, the DataSet (from the 2006-02-15, 3:58 am post).
“…So the further question you mentioned all aims at make the autogenerated
client proxy use our custom class type(.NET specific). I'm afraid this is
not available through the buildin webservice client proxy genenration
tool(wsdl.exe). The DataSet class you mentioned is a particular class(which
is somewhat hardcoded in the wsdl.exe tool's generation logic since there
is not particular attributes in description for DataSet as we can see).
I've tried DataTable class also which can not be recognized.
I think for your scenario, it is more likely that you're going to build a
custom webservice client proxy generation tool rather than extend the
current one. Anyway, currently the simplest means is to modify the
autogenerated proxy class code…”
Ok so, for my purposes I make the decision that I will change the generated web reference.
I dive into the reference.cs file and change all instances of the LayerOptionChoiceEntity[] to return my collection of LayerOptionChoiceCollection, and add the necessary namespace.
Build succeeded. Now the return type of my web service and web reference match. I run the application, an exception occurs. I check my event viewer and I see the following list of nested exceptions:
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Method DuckCreekService.GetAllLayerOptions can not be reflected.
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : There was an error reflecting 'GetAllLayerOptionsResult'.
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : There was an error reflecting type 'Endurance.Duckcreek.Data.LayerOptionChoiceEntity'.
Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Types 'Endurance.Duckcreek.Data.LayerOptionChoiceEntity' and 'Endurance.WorkBench.ServiceFacade.DuckcreekWS.LayerOptionChoiceEntity' both use the XML type name, 'LayerOptionChoiceEntity', from namespace 'http://bm.endurance/Endurance.WorkBench.WebService/DuckCreekService'. Use XML attributes to specify a unique XML name and/or namespace for the type.
“My first question, what is a Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[]?”
Well, I have an answer. You have to remember that part of idea, if not most, behind a web service is that it can be consumed by another platform in-specific application. This works in reverse as well, when we create a web reference and wsdl.exe generates a proxy class, it assumes no knowledge of the ‘type’ (read assembly and class) that it is consuming. So it generates a class definition based on the xml it is consuming and it places this definition in the References.cs (or vb) file. And that is what a Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity[] is. My assembly and class definition was Endurance.WorkBench.Duckcreek.Data.LayerOptionChoiceEntity, wsdl.exe generated a new definition Endurance.WorkBench.WebService.Test.DuckcreekWS.LayerOptionChoiceEntity, using the name of the web reference.
The solution: delete this type from the reference.cs (or vb) class and no more namespace collision.
Point of interest: wsdl.exe appears to generate its types using xsd.exe. http://softwareink.blogspot.com/2006/05/xsdexe.html
Article of interest http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service07162002.asp
As a huge fan of Nunit I was quite excited to give VSTS unit testing a try. I figured I would start out with a simple test to verify that one my data access methods was returning information correctly. To do this you can simply right click on your method, or class, and go to ‘Create Unit Tests…’.

I can then easily create objects based on an XML doc, and serialize and deserialize them into typed classes. My primary goals are to (1. In one click, have the ability to generate the schema from an xml doc and output the custom classes. (2. Have the ability to output the schema to the input window, modify, and regenerate the classes from the modified schema. (3. Be able to specify a namespace. (4. Be bale to specify an output location. (5. be able to specify the location of Xsd.exe itself. Long term goals are to have C# (Yeah!) and VB.Net (Boo!) support, as well as custom class and dataset generation options. NUnit…or actually VS2005 Unit Testing, fully NDoc commented, and submitted as an Avanade Intellectual Asset. Toolbar instead of buttons and a status bar. As well as the ability to use the 