Search code examples
.netwcfsoapmessagecontract

Can a WCF Operation that takes a single array as a parameter use MessageContracts?


I am trying to replace an asmx WebService with a WCF service. My primary goal is to keep the SOAP message the same. The caller is not .NET, and would require significant re-work to take minor changes to the contract.

My pain point is that the web methods I am trying to replace webmethod uses the the following Attribute deceleration:

[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]

This removes an extra layer of XML elements around each parameter.

The only way I know about to simulate this with WCF is to use a MessageContracts instead of DataContracts, and use the WrappedName, and IsWrapped Property to control how paramaters are formatted.

This approach works for all of my methods except one, which takes a single Array of a POCO object as a parameter.

My conclusion is that I am out of options. I cannot upgrade this webservice and maintain the same contract.

My questions are:

1) Is the only way to replicate :

    [SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]

on a web method in WCF to use a MessageContract?

2) And is there a way to have a method take a single array as a parameter?

Here is a simple Example I have been working with to show what I am seeing/ Doing with [SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)] and Message Contract

The backing Service Code:

using System.Web.Services;
using System.Web.Services.Protocols;
using System.ServiceModel;
namespace WebApplication1{

    /// <summary>
    /// The Service Contract
    /// </summary>
    [ServiceContract]
    public interface ISimpleMathService
    {
        [OperationContract()]
        AddResp Add(AddReq add);
    }
    /// <summary>
    /// The Service Implementation
    /// </summary>
    public class simpleMath : ISimpleMathService
    {
        [WebMethod()] //Allows the Service to be exposed as a asmx Service
        [SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
        public AddResp Add(AddReq add)
        {
            return new AddResp {result = add.NumOne + add.NumTwo}; 
        }
    }
}

POCO Objects: (V1 with Data Contracts)

    using System.Runtime.Serialization;
using System.ServiceModel;

namespace WebApplication1
{
    [DataContract]
    public class AddReq
    {

        [DataMember]
        public int NumOne { get; set; }

        [DataMember]
        public int NumTwo { get; set; }
    }



    [DataContract]
    public class AddResp
    {

        [DataMember]

        public int result{ get; set; }


    }
}

ASMX SOAP

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:add>
         <tem:NumOne>12</tem:NumOne>
         <tem:NumTwo>12</tem:NumTwo>
      </tem:add>
   </soapenv:Body>
</soapenv:Envelope>

SOAP Request, with WCF Data Contract

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/" xmlns:web="http://schemas.datacontract.org/2004/07/WebApplication1">
   <soap:Header/>
   <soap:Body>
      <tem:Add>
         <tem:add>
            <web:NumOne>10</web:NumOne>
            <web:NumTwo>10</web:NumTwo>
         </tem:add>
      </tem:Add>
   </soap:Body>
</soap:Envelope>

Lets Use Message Contracts on our Arguments and return types: POCO Objects: (V2 with MessageContracts)

namespace WebApplication1
{
    [DataContract]
    [MessageContract(WrapperName="add", IsWrapped = true)] //Default Wrapper Name is "Add", not add
    public class AddReq
    {

        [DataMember]
        [MessageBodyMember]
        public int NumOne { get; set; }

        [DataMember]
        [MessageBodyMember]
        public int NumTwo { get; set; }
    }



    [DataContract]
    [MessageContract(IsWrapped = true)]
    public class AddResp
    {

        [DataMember]
        [MessageBodyMember]
        public int result{ get; set; }


    }
}

WCF Soap Request (V2):

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/">
   <soap:Header/>
   <soap:Body>
      <tem:add>
         <tem:NumOne>19</tem:NumOne>
         <tem:NumTwo>12</tem:NumTwo>
      </tem:add>
   </soap:Body>
</soap:Envelope>

That is what I am doing now, which meets 90% of what I need.

The problem is, I would like to Implement a method like this in WCFand keep the contract the same:

[WebMethod()]
[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
public AddResp AddArrays(AddReq [] addInput)
{
    AddResp resp= new AddResp{result=0}
    foreach (var addrequest in addInput)
    {
        resp.result += (addrequest.NumOne + addrequest.NumTwo);
    }
    return resp;
}

When I do this now, I get The following exception because AddReq [] is not a MessageContract. AddReq [] is of type System.Array, which I cannot alter.

The operation 'AddArrays' could not be loaded because it has a parameter or return type of type System.ServiceModel.Channels.Message or a type that has MessageContractAttribute and other parameters of different types. When using System.ServiceModel.Channels.Message or types with MessageContractAttribute, the method must not use any other types of parameters.

Thanks, Brian


Solution

  • It turns out, that you can add a "Host Class" with IsWrapped=false, and it works.

    From the sample in the original question, Here is what the wrapper class would look like:

    [DataContract,MessageContract(IsWrapped=false)]
    public class AddArraysReq
    {
        [DataMember]
        [MessageBodyMember]
        public AddReq[] AddReqs;
    
    }
    

    And this is what the Method would look like:

     public AddResp AddArrays(AddArraysReq addInput)
        {
            AddResp resp = new AddResp {result = 0};
            foreach (var addrequest in addInput.AddReqs)
            {
                resp.result += (addrequest.NumOne + addrequest.NumTwo);
            }
            return resp;
        }
    

    The resulting SOAP Request:

    <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
        xmlns:tem="http://tempuri.org/" 
        xmlns:web="http://schemas.datacontract.org/2004/07/WebApplication1">
       <soap:Header/>
       <soap:Body>
          <tem:AddReqs>        
             <web:AddReq>
                <web:NumOne>10</web:NumOne>
                <web:NumTwo>10</web:NumTwo>
             </web:AddReq>
             <web:AddReq>
                <web:NumOne>10</web:NumOne>
               <web:NumTwo>10</web:NumTwo>
             </web:AddReq>
            </tem:AddReqs>
       </soap:Body>
    </soap:Envelope>
    

    I did not realize the IsWrapped=false removed all repsentation of the class from the request.