views:

586

answers:

2

Consider the following .Net ASMX web service with two web methods.

using System;
using System.Runtime.Remoting.Messaging;
using System.Web.Services;
using System.Xml.Serialization;

namespace DemoWebService
{
 public class
 ArrayItem
 {
  public int
  Value1;

  public int
  Value2;
 };

 [WebService(Namespace = "http://begen.name/xml/namespace/2009/09/demo-web-service/")]
 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
 public class DemoService :
  WebService
 {
  [WebMethod]
  [return: XmlArray("Items")]
  [return: XmlArrayItem("Item")]
  public ArrayItem []
  GetArray()
  {
   return BuildArray();
  }

  [WebMethod]
  public IAsyncResult
  BeginGetArrayAsync(AsyncCallback callback, object callbackData)
  {
   BuildArrayDelegate fn = BuildArray;
   return fn.BeginInvoke(callback, callbackData);
  }

  [WebMethod]
  [return: XmlArray("Items")]
  [return: XmlArrayItem("Item")]
  public ArrayItem []
  EndGetArrayAsync(IAsyncResult result)
  {
   BuildArrayDelegate fn = (BuildArrayDelegate)((AsyncResult)result).AsyncDelegate;
   return fn.EndInvoke(result);
  }

  private delegate ArrayItem []
  BuildArrayDelegate();

  private ArrayItem []
  BuildArray()
  {
   ArrayItem [] retval = new ArrayItem[2];
   retval[0] = new ArrayItem();
   retval[0].Value1 = 1;
   retval[0].Value2 = 2;
   retval[1] = new ArrayItem();
   retval[1].Value1 = 3;
   retval[1].Value2 = 4;

   return retval;
  }
 }
}

The GetArray and GetArrayAsync web methods both return an array of ArrayItems. The GetArrayAsync web method, however, is implemented using the asynchronous programming model.

Through the use of the [XmlArray] and [XmlArrayItem] attributes applied to the return value of the GetArray() method, I've been able to control the XML elements .Net uses to serialize the return value and get a result like this:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"&gt;
  <soap:Body>
    <GetArrayResponse xmlns="http://begen.name/xml/namespace/2009/09/demo-web-service/"&gt;
      <Items>
        <Item>
          <Value1>int</Value1>
          <Value2>int</Value2>
        </Item>
        <Item>
          <Value1>int</Value1>
          <Value2>int</Value2>
        </Item>
      </Items>
    </GetArrayResponse>
  </soap:Body>
</soap:Envelope>

Notice how the return value has the XML element name of "Items" and each item in the array has the XML element name of "Item".

I have applied the same attributes to the EndGetArrayAsync() method, but they don't seem to affect the response the way they do for the synchronous GetArray() method. In this case the response looks like this:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"&gt;
  <soap:Body>
    <GetArrayAsyncResponse xmlns="http://begen.name/xml/namespace/2009/09/demo-web-service/"&gt;
      <GetArrayAsyncResult>
        <ArrayItem>
          <Value1>int</Value1>
          <Value2>int</Value2>
        </ArrayItem>
        <ArrayItem>
          <Value1>int</Value1>
          <Value2>int</Value2>
        </ArrayItem>
      </GetArrayAsyncResult>
    </GetArrayAsyncResponse>
  </soap:Body>
</soap:Envelope>

In this case, the return value was given the default XML element name of "GetArrayAsyncResult" and the array items have the XML element name coming from the C# class name, "ArrayItem".

Is there any way to get an asynchronous web method to respect the [XmlArray] and [XmlArrayItem] attributes applied to the return value of the End* method?

A: 

See this question...

You need to add the XmlType() attribute to the class used in the array and then specify only the XmlArray() attribute on the class member defining the array.

csharptest.net
Putting an [XmlType("Item")] attribute on the class used in the array, ArrayItem in my example, does change the XML element name for the items in the array to "Item". Removing the [XmlArrayItem] attribute from the EndGetArrayAsync() method to leave only the [XmlArray] attribute did not, however, get the XML element name for the array to change; it was still the default <GetArrayAsyncResult>.Also, I am specifically trying to avoid having to define the attributes at the ArrayItem class, preferring to have control at each web method.Thank you for the suggestion, though.
GBegen
A: 

I believe this to be a bug in the .Net framework as the only difference between the two web methods in my example is that one is asynchronous and the two [return: *] attributes are placed on the End*() method of the asynchronous implementation.

I have found a work-around that makes the WSDL essentially identical for the synchronous and asynchronous versions of the web method, and thus makes the SOAP response use the XML element names I want for the array.

I need to change the End*() method to not have a return value and instead use an output parameter to return the array. The [XmlArrayItem] attribute is then placed on that parameter instead of the return value of the method. Here is the revised EndGetArrayAsync() method that does what I want.

 [WebMethod]
 public void
 EndGetArrayAsync(IAsyncResult result, [XmlArrayItem("Item")] out ArrayItem [] Items)
 {
  BuildArrayDelegate fn = (BuildArrayDelegate)((AsyncResult)result).AsyncDelegate;
  Items = fn.EndInvoke(result);
 }
GBegen