Generic list gets converted into an ArrayList when sent through web services

 

Hello, i have a customer object like this:

public class Customer
{
public Customer()
{

}

private string customername;
public string CustomerName
{
get { return customername; }
set { customername = value; }
}

}


And a generic Customer collection that can contain customer objects like this:

public class CustomerList : List<Customer>
{
public CustomerList()
{

}

}


I populate a CustomerList with some Customer objects and send the CustomerList through a web service:

[WebMethod]
public CustomerList GetCustomerList()
{

CustomerList myCustomerList = new CustomerList();
Customer myCustomer = new Customer();
myCustomer.CustomerName =
"Hello this is a test";
myCustomerList.Add(myCustomer);

return myCustomerList;
}


But  on the client end, the windows application that is going to consume the web service, this generic CustomerList has turned into an
Array of Customers Customer[] Customers=new Customers[]... Why is that, what am i doing wrong , i want to the CustomerList to "stay" a CustomerList even though im sending it through a web service, but it gets converted into an array , Im using the August CTP..

And if i invoke this WebMethod the XML look like this:


< xml version="1.0" encoding="utf-8" >

- <ArrayOfCustomer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
- <Customer>
  <CustomerName>Hello this is a test</CustomerName>
  </Customer>
  </ArrayOfCustomer>


As you can see, the CustomerList has turned into an Array of Customers ..


Answer this question

Generic list gets converted into an ArrayList when sent through web services

  • Dave Lynx

    Good thread.

    I plan to use this method. Any criticisms appreciated.

    These are my basic classes:

    //SOAP Object, and base for business object. Exposed by Web Service in WSDL (see later) for

    //environments that just want the basics. See collection later.

    public class BaseCustomer

    {

    private int _customerName;

    public int CustomerName

    {

    get { return _customerName; }

    set { _customerName = value; }

    }

    }

    //Business Object. Could be used at the other end if wsdl.exe generated proxy can

    //be used - e.g. .NET consumer.

    public class Customer : BaseCustomer

    {

    //Methods and other properties used in business object but not required across the wire

    //WSDL won't expose methods (behaviour)

    public string MyBusinessFunction()

    {

    return "";

    }

    //WSDL won't expose readonly

    public string MyReadOnlyBusinessProp

    {

    get { return ""; }

    }

    This is the web service - include whichever method is appropriate.

    //Web Service - without attributes

    public class MyWebService

    {

    //Use this to expose only those properties of Customer and collection that you want exposed

    public BaseCustomer[] GetCustomerArray()

    {

    //Get CustomerList from DB

    CustomerList cl = new CustomerList();

    return cl.CustomerArray;

    }

    //This won't expose methods and readonly properties, but will expose other properties of Customer

    public CustomerList GetCustomerList()

    {

    //Get CustomerList from DB

    CustomerList cl = new CustomerList();

    return cl;

    }

    }

    //Though this is a public property it won't be exposed since this object is exposed as an array.

    public string MyBusinessProp

    {

    get { return ""; }

    set { }

    }

    }

    Then this collection (see Web Service below for use):

    //Business object list of Customers

    public class CustomerList : List<Customer>

    {

    //Used to return array for SOAP

    public BaseCustomer[] CustomerArray()

    {

    return (BaseCustomer[])this.ToArray;

    }

    //Other business object properties and methods

    //...

    //Don't want this exposed

    public string SomeBusinessProp

    {

    get { return ""; }

    set { }

    }

    }

    This is the Web Service. Include whichever method is appropriate:

    //Web Service - without attributes

    public class MyWebService

    {

    //Use this to expose only those properties of Customer and collection that you want exposed

    public BaseCustomer[] GetCustomerArray()

    {

    //Get CustomerList from DB

    CustomerList cl = new CustomerList();

    return cl.CustomerArray;

    }

    //This won't expose methods and readonly properties, but will expose other properties

    public CustomerList GetCustomerList()

    {

    //Get CustomerList from DB

    CustomerList cl = new CustomerList();

    return cl;

    }

    }

    WSDL. Notes:

    - both explicit array and collection are exposed as arrays

    - CustomerList.SomeBusinessProp not exposed. However, it does expose Customer.MyProp.

    1) GetCustomerArray WSDL Types - hides all business object specific stuff:

    <wsdl:types>

    <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">

    <s:element name="GetCustomerArray">

    <s:complexType />

    </s:element>

    <s:element name="GetCustomerArrayResponse">

    <s:complexType>

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="GetCustomerArrayResult" type="tns:ArrayOfBaseCustomer" />

    </s:sequence>

    </s:complexType>

    </s:element>

    <s:complexType name="ArrayOfBaseCustomer">

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="unbounded" name="BaseCustomer" nillable="true" type="tns:BaseCustomer" />

    </s:sequence>

    </s:complexType>

    <s:complexType name="BaseCustomer">

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string" />

    </s:sequence>

    </s:complexType>

    </s:schema>

    </wsdl:types>

    1) GetCustomerList WSDL Types - CustomerList.SomeBusinessProp not exposed. However, it does expose Customer.MyProp and any other Customer properties.

    <wsdl:types>

    <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">

    <s:element name="GetCustomerList">

    <s:complexType />

    </s:element>

    <s:element name="GetCustomerListResponse">

    <s:complexType>

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="GetCustomerListResult" type="tns:ArrayOfCustomer" />

    </s:sequence>

    </s:complexType>

    </s:element>

    <s:complexType name="ArrayOfCustomer">

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="unbounded" name="Customer" nillable="true" type="tns:Customer" />

    </s:sequence>

    </s:complexType>

    <s:complexType name="Customer">

    <s:complexContent mixed="false">

    <s:extension base="tns:BaseCustomer">

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="MyProp" type="s:string" />

    </s:sequence>

    </s:extension>

    </s:complexContent>

    </s:complexType>

    <s:complexType name="BaseCustomer">

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string" />

    </s:sequence>

    </s:complexType>

    </s:schema>

    </wsdl:types>


  • The PocketTV Team

    Maybe I just don't know enough about XML schema, but, I still don't understand why on the client side, the proxy generator doesn't just create a List<T> or an ArrayList instead of an array As a developer how many times do you use arrays and not lists In my experience, I use lists far more often because the number of items that I'm working with is almost never fixed in size. I can see why you would want to list it as an array in the XML schema for compatibility, but, I think the proxy generator, unless the array is marked as fixed in size (in the XML schema), should instead generate a List<T> or an ArrayList. It would make it a lot easier to work with and would it really cost much of anything in terms of efficiency To me it seems like more of an issue of what it's doing on the client side than on the server side. I was hoping that this would change in WCF, but, it doesn't look like that's the case, at least not by default. On the plus side, at least generic ILists work in WCF which don't in ASP.NET Web Services.

    BTW, JAX-WS 2.0 does exactly what I'm talking about. i.e. it uses lists instead of arrays. Why should .NET web service proxies be any different


  • Luxspes

    I'm sure that's wrong in so many ways...

  • mcutchin

    The metadata infrastructure special-cases things like DataSets and DataTables.



  • Deb Magsam

    Wow, replies by Steve Maine and Eugene Osovetsky.

    But if you still need other reasons, you might want to check out this post by Matt Berther, "Web service and custom serialization" http://www.mattberther.com/2006/05/000755.html. In that post, Matt is trying hard not to have " two copies of the objects", one for XML serialization and one for the business entity. My response was:

    I'd suggest that you have 2 copies of the "objects", even though you think it stinks. You have to remember that even though the "objects" have the same name, they are actually built to accomplish 2 different goals. The class that you have now, it is a business object, and thus has to conform to your business rules (which is part of the reason why you don't have a default constructor). Business classes contain the business data and the business behaviors. On the other hand, you have this representation of your business object that you wish to pass along the wire. The representation is a message, and not a business object. This class will contain a representation of the data contained within the business object, but as it is a message, it contains no business rules.

    Once you decouple the message from the business object, you will have an easier time distinguishing between your core business rules, and your message rehydration rules. In do so, you are also better able to handle versioning of your public facing contracts (in web services this is done via WSDL and XML Schema). You can now update and change your business objects as needed, and only add any new data to your message when that new functionality is required. By going down this path you will also be able to utilize the more enterprise friendly Web Services Contract First approach to building web services.

    I know that there are a lot of people that will disagree with me, and feel as you do, that by splitting things into messages and business objects, you are just adding to the bloat of a system, but I’ve learned that as long as you don’t hit the architectural issues that you discuss here, that yes, the message based methodology is overkill for your particular project. But once you start to see that there is differences between the way you want to implement the business object, and the way you want to represent it on the wire, then you need to start to think about going down the message path.

    A comprising between these 2 designs is to implement the IXmlSerializable interface, on your business classes, but eventually this will also become too cumbersome and you will have to go the route of the message methodology, especially when you have to handle multiple minor versions of your message coming thru the same web service.

    www.donxml.com



  • Carlos De Matos

    Also, look at the XsdObjectGen tool (http://www.microsoft.com/downloads/details.aspx FamilyID=89e6b1e5-f66c-4a4d-933b-46222bb01eb0&DisplayLang=en and http://apps.gotdotnet.com/xmltools/xsdobjgen/readme.htm) to get collections instead of arrays, and look into the possibility of using Schema Importer Extensions to control how schema is imported.

    But the previous posters were right - XML serialization and schema import/export is not about roundtripping the exact CLR types - it is all about the message on the wire and not the programming model. The wire (and schema) representation of List<Customer> and Customer[] is the same, and thus it is completely valid to have one on the service and another on the client. Arrays may not be the most _usable_ programming model, of course... In the Windows Communication Foundation schema import mechanism we will actually let you specify the type of collection you want to generate.

  • Lishen

    I've got the same problem. The strange thing is that I am running the same app (web service returning List<T> and a client to consume that service) on two computers. On the first computer everything runs it's supposed to and client gets its List<T>. But on the other box it gets T[]. Is there any reason for such inconsistence Could somebody explain it to me Thanks.
  • Jeff Chrisope

    I'm sure this is by design.

    Because web services are platform-agnostic, they need to use the most primitive data types. Just imagine what would happen if your web service would be consumed by a .NET 1.x application, where no generics are available.

    An array is the lowest common denominator.

  • Thomas Hecht

    Since I didn't know how to implement the custom proxy generator (and am a bit lazy to do so), I juste did this on the generated classes (object must be excluded since generic collections of it isn't serializable, also replacing byte[] is obviously wrong) :

    string result = Regex.Replace(s, @"( <=\W)([\w.]+)( <!object|byte)\[\]", @"System.Collections.Generic.List<$1>");


  • Syed Mahmood Ali

    Please give me a Link or some reasons, why shouldn't I use serialized objects but Messages in my Webmethods

    Thank you


  • Jose Garcia-Cordero

    First off, make sure that you send messages, not serialized versions of your objects.  Then, you split the classes that make up your messages into to separate projects.  For the things that get serialized as complex data types (in schema) and reused in multiple messages, put them in in a dll project called YourProject.Messages.DataTypes.  For the actual messages, place them in another dll project called YourProject.Messages.  For both projects, all the class need to be publically creatable and have property get/set (since they need to conform to XML Serialization requirements, and are only parts of messages anyway).

    Now, on the client side, you can refer to your message dll, and use it, instead of the classes generated by the Web Reference tool.  To do this you will have to go in and edit the Reference.cs (or .vb) file that was generated by the Web Reference tool.  In the functions that relate back to the WebMethod, change the return valued type to the one in you message lib.  Then scroll down and delete all the classes that the Web Reference tool generated (aka the ones you are replacing with the Message dll).

    Just remember that if you refresh the WebReference, you will need to go back and do this again.



  • Frederic Aubry

    Thanks for your answer. But that is pretty strange because i can send a typed datatable without any problem and that is a collection too, how come
  • Generic list gets converted into an ArrayList when sent through web services