locked
Proxy invoke of WCF service returns string array, but is missing on client side RRS feed

  • Question

  • I have a WCF service using basicHttpBinding with a method that returns a string array:

    interface IAttributeMapper
    {
            [OperationContract(Action = "http://tempuri.org/GetKeyFields")]
            string[] GetKeyFields();
    }

    I also have a proxy client class that invokes this method:

    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/GetKeyFields", RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    public string[] GetKeyFields()
    {
           object[] results = this.Invoke("GetKeyFields", new object[0]);
           return ((string[])(results[0]));
    }

    I have a breakpoint set on both the WCF service and the client. The client successfully invokes the service method, which returns an array of five strings. However, the client gets an empty string array. From what I've read, there shouldn't be any problems serializing a string array; this seems like it should be a pretty straightforward thing. Can anyone tell me what I'm doing wrong?

    Thanks
    Wednesday, September 23, 2009 1:28 PM

Answers

  • Hi,

    Can you try changing your method in NameServiceProxy as below. I have decorated the method with XmlArrayItemAttribute attribute.


            [SoapDocumentMethod("http://tempuri.org/GetNames", RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
            [return: System.Xml.Serialization.XmlArrayItemAttribute(Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
            public string[] GetNames()
            {
               //object[] results = this.Invoke("GetNames", new object[0]);
                //return ((string[])(results[0]));
                return (string[])Invoke("GetNames", new object[0])[0];
            }
    • Marked as answer by Henchman 24 Thursday, September 24, 2009 12:51 PM
    Thursday, September 24, 2009 1:52 AM
  • In addition, if you really couldn't change the proxy code at all (even by adding the attributes as suggested above), you could change your WCF service to use XmlSerializer instead of the default DataContractSerializer.  XmlSerializer offers greater fidelity with ASP.Net web services and clients.  All you need to do to achieve this is to annotate your methods with XmlSerializerFormatAttribute as shown below.

        [ServiceContract]
        public interface INameService
        {
            [OperationContract(Action = "http://tempuri.org/GetNames")]
            [XmlSerializerFormat]
            string[] GetNames();
        }
    
        public class NameService : INameService
        {
            public string[] GetNames()
            {
                return new string[] { "Alice", "Bob", "Carol" };
            }
        }

    Senior Program Manager, Windows Worfklow Foundation and Windows Communication Foundation
    Tuesday, October 6, 2009 2:53 AM

All replies

  • Well i was able to get my service to work with the service contract mentioned above. You can try tracing and logging  the request and response and investigating that. Also can you post your service contract your client config and service config? One more question why did you add the service reference as a "Web reference" when you can add it as a "Service reference". For more information on message logging please check this - http://msdn2.microsoft.com/en-us/library/ms751526.aspx

    Wednesday, September 23, 2009 3:14 PM
  • Below is a condensed version of the service config. My client application doesn't use a service reference or a web reference; it's a .NET 2.0 client that uses that proxy class to invoke an ASMX service. I want to replace that old ASMX service with this new WCF one, yet keep the same interface exposed to the client.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.serviceModel>
            <bindings>
                <webHttpBinding>
                    <binding name="WebConfiguration" maxBufferSize="65536" maxReceivedMessageSize="2147483647" transferMode="Streamed" />
                </webHttpBinding>
            </bindings>
            <behaviors>
                <serviceBehaviors>
                    <behavior name="Metadata">
                        <serviceMetadata httpGetEnabled="true"/>
                    </behavior>
                </serviceBehaviors>
            </behaviors>
            <services>
                <service behaviorConfiguration="Metadata" name="I.I.ServiceLibrary.IService">
                    <clear />
                    <endpoint address="AttributeLookup" binding="basicHttpBinding"
                     name="AttributeLookup" contract="I.I.ServiceLibrary.ServiceContracts.IAttributeMapper"
                     listenUriMode="Explicit">
                        <identity>
                            <dns value="localhost" />
                            <certificateReference storeName="My" storeLocation="LocalMachine"
                             x509FindType="FindBySubjectDistinguishedName" />
                        </identity>
                    </endpoint>
                    <host>
                        <baseAddresses>
                            <add baseAddress="http://localhost:33333/webservices" />
                        </baseAddresses>
                    </host>
                </service>
            </services>
        </system.serviceModel>

        <system.web>
            <httpRuntime maxRequestLength="2147483647"/>
        </system.web>
    </configuration>
    Wednesday, September 23, 2009 4:01 PM
  • I used a default CompositeType service to return array of strings as below from the detalt WCF project template:
    public string[] GetDataUsingDataContract(CompositeType composite)
    {
    	string[] str = { "str1","str2","str3"};
    	return str;
    }
    

    Simple basicHttpBinding.

    Added web reference to .Net 2.0 based console client, which looks like-

        <configSections>
            <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
                <section name="ConsoleApplication1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
            </sectionGroup>
        </configSections>
        <applicationSettings>
            <ConsoleApplication1.Properties.Settings>
                <setting name="ConsoleApplication1_localhost_Service1" serializeAs="String">
                    <value>http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary2/Service1/</value>
                </setting>
            </ConsoleApplication1.Properties.Settings>
        </applicationSettings>


    And the console C# code is:-

                localhost.Service1 svc1 = new ConsoleApplication1.localhost.Service1();
                localhost.CompositeType ct = new ConsoleApplication1.localhost.CompositeType();
    
                Console.WriteLine("hello");
    
                string[] str = svc1.GetDataUsingDataContract(ct);
    
                Console.WriteLine(str[0] + "-----------" + str[1] + "-----------" + str[2]);
    And it works like a charm.

    Couple of things- ensure that its basicHttpBinding without security- certificates etc. If you are intending to use security then we have to WSE on the client side because server would expect the certificate. Try a simple sample like what I did and see if it works.


    hth,

    Thanks,
    -Phani
    Wednesday, September 23, 2009 5:08 PM
  • I can't modify the existing client code by adding a web reference; I need to provide a WCF-based SOAP service with the same interface as the old ASMX.

    I was able to reproduce this problem with the two simple projects below, in a way that emulates my production code. I can verify with the debugger that the Library.GetNames method is getting invoked as expected, but the "names" array that the client app receives has zero strings.

    1. Library (WCF service library) source:

    namespace Library
    {
        [ServiceContract]
        public interface INameService
        {
            [OperationContract(Action = "http://tempuri.org/GetNames")]
            string[] GetNames();
        }

        public class NameService : INameService
        {
            public string[] GetNames()
            {
                return new string[] { "Alice", "Bob", "Carol" };
            }
        }
    }

    app.config:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.serviceModel>
            <behaviors>
                <serviceBehaviors>
                    <behavior name="Metadata">
                        <serviceMetadata httpGetEnabled="true"/>
                    </behavior>
                </serviceBehaviors>
            </behaviors>
            <services>
                <service behaviorConfiguration="Metadata" name="Library.NameService">
                    <endpoint address="NameService" binding="basicHttpBinding" contract="Library.INameService"></endpoint>
                    <host>
                        <baseAddresses>
                            <add baseAddress="http://localhost:55555/" />
                        </baseAddresses>
                    </host>
                </service>
            </services>
        </system.serviceModel>
    </configuration>


    2. Client (.NET 2.0 console app) source:

    namespace Client
    {
        class Program
        {
            [WebServiceBindingAttribute(Namespace = "http://tempuri.org/")]
            class NameServiceProxy : SoapHttpClientProtocol
            {
                public NameServiceProxy() { this.Url = new Uri("http://localhost:55555/NameService").AbsoluteUri; }

                [SoapDocumentMethod("http://tempuri.org/GetNames", RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
                public string[] GetNames()
                {
                    return (string [])Invoke("GetNames", new object[0])[0];
                }
            }

            static void Main(string[] args)
            {
                string[] names = new NameServiceProxy().GetNames();
                foreach (string name in names)
                    Console.WriteLine(name);
            }
        }
    }
    Wednesday, September 23, 2009 6:39 PM
  • When I run the test app with tracing enabled, I found this Message Log Trace that appears to correspond to the return value of the GetNames method... it looks like all the values are there.

    <E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
    	<System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
    		<EventID>0</EventID>
    		<Type>3</Type>
    		<SubType Name="Information">0</SubType>
    		<Level>8</Level>
    		<TimeCreated SystemTime="2009-09-23T19:22:51.0185957Z" />
    		<Source Name="System.ServiceModel.MessageLogging" />
    		<Correlation ActivityID="{8fc36e56-ec5e-4560-b7eb-93788ff88357}" />
    		<Execution ProcessName="WcfSvcHost" ProcessID="4780" ThreadID="6" />
    		<Channel />
    		<Computer>DEV</Computer>
    	</System>
    	<ApplicationData>
    		<TraceData>
    			<DataItem>
    				<MessageLogTraceRecord Time="2009-09-23T15:22:51.0185957-04:00" Source="TransportSend" Type="System.ServiceModel.Dispatcher.OperationFormatter+OperationFormatterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
    					<Addressing>
    						<Action>http://tempuri.org/INameService/GetNamesResponse</Action>
    					</Addressing>
    					<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    						<s:Header>
    							<ActivityId CorrelationId="29940116-d73a-4554-b15d-7feffbdb9bce" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">8fc36e56-ec5e-4560-b7eb-93788ff88357</ActivityId>
    						</s:Header>
    						<s:Body>
    							<GetNamesResponse xmlns="http://tempuri.org/">
    								<GetNamesResult xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    									<a:string>Alice</a:string>
    									<a:string>Bob</a:string>
    									<a:string>Carol</a:string>
    								</GetNamesResult>
    							</GetNamesResponse>
    						</s:Body>
    					</s:Envelope>
    				</MessageLogTraceRecord>
    			</DataItem>
    		</TraceData>
    	</ApplicationData>
    </E2ETraceEvent>

    • Edited by Henchman 24 Wednesday, September 23, 2009 7:27 PM Trace log
    Wednesday, September 23, 2009 7:04 PM
  • Hi,

    Can you try changing your method in NameServiceProxy as below. I have decorated the method with XmlArrayItemAttribute attribute.


            [SoapDocumentMethod("http://tempuri.org/GetNames", RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
            [return: System.Xml.Serialization.XmlArrayItemAttribute(Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
            public string[] GetNames()
            {
               //object[] results = this.Invoke("GetNames", new object[0]);
                //return ((string[])(results[0]));
                return (string[])Invoke("GetNames", new object[0])[0];
            }
    • Marked as answer by Henchman 24 Thursday, September 24, 2009 12:51 PM
    Thursday, September 24, 2009 1:52 AM
  • Brilliant! That worked perfectly.

    Please forgive my ignorance, but why is it necessary to specify that namespace? I see that same namespace appears in the trace, but this hasn't been necessary for all my other methods - just the one that returned a string array.

    Thank you very much!
    Thursday, September 24, 2009 12:58 PM
  • In addition, if you really couldn't change the proxy code at all (even by adding the attributes as suggested above), you could change your WCF service to use XmlSerializer instead of the default DataContractSerializer.  XmlSerializer offers greater fidelity with ASP.Net web services and clients.  All you need to do to achieve this is to annotate your methods with XmlSerializerFormatAttribute as shown below.

        [ServiceContract]
        public interface INameService
        {
            [OperationContract(Action = "http://tempuri.org/GetNames")]
            [XmlSerializerFormat]
            string[] GetNames();
        }
    
        public class NameService : INameService
        {
            public string[] GetNames()
            {
                return new string[] { "Alice", "Bob", "Carol" };
            }
        }

    Senior Program Manager, Windows Worfklow Foundation and Windows Communication Foundation
    Tuesday, October 6, 2009 2:53 AM