I have a frustrating issue that I have been trying too overcome but can't seem to figure out.
I have services that I am exposing over both SOAP and REST endpoints in WCF. To avoid duplicate object code, I want to reuse the contract objects between the two services.FYI,I am using two separate interfaces because there are many API calls that I have that are different between the two endpoints. A simple example of these services is shown below:
/*REST Implementation*/
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebInvoke]
TestResponse Test(TestRequest request);
[OperationContract]
[WebGet]
int GetTest(int testId);
}
/*SOAP Implementation*/
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebInvoke]
TestResponse Test(TestRequest request);
[OperationContract]
[WebInvoke]
int GetTest(GetTestRequest request);
}
[DataContract(Namespace="http://www.mysite.com/Test")]
public class TestRequest
{
[DataMember]
public int ID {get;set;}
[DataMember]
public InnerTestRequest InnerTestRequest {get;set;}
}
[DataContract(Namespace="http://www.mysite.com/Test")]
public class InnerTestRequest
{
[DataMember]
public int ID {get;set;}
}
THE PROBLEM The problem I am running into is I would like the contract payload for both of the endpoints to use the same XML structure (within the SOAP envelope for the SOAP endpoint).
For example, making a call to the Test(TestRequest request) on the REST endpoint, I would like to send the following XML:
<TestRequest xmlns="http://www.mysite.com/Test">
<InnerTestRequest>
<ID>2</ID>
</InnerTestRequest>
<ID>4</ID>
</TestRequest>
and for the SOAP endpoint, I would expect to be able to send the following:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<TestRequest xmlns="http://www.mysite.com/Test">
<InnerTestRequest>
<ID>2</ID>
</InnerTestRequest>
<ID>4</ID>
</TestRequest>
</s:Body>
I would also like the responses to be in the same format with the same contract payload. I have tried multiple methods to accomplish this including using the [MessageContractAttribute] and specifying namespace, as well as setting the BodyStyle to BodyStyle.Bare, but I am still running into the two following problems:
1. The http://www.mysite.com/Test namespace does not trickle down to the members of its class.
2. SOAP requests "wrap" the contract, and it changes the structure of the XML.
What is the best way to accomplish this without specifying two separate DataContracts, one for REST and one for SOAP.
Thanks ahead of time
For the first item: you also need to define the [OperationContract]
namespace as the same one in the data contracts, this way you have a consistent namespace story.
For the second item, you were in the right track with message contracts. You'll need to use an unwrapped message contract if you want to remove the "wrapping" element.
The code below shows how this can be accomplished.
public class StackOverflow_15252991
{
[DataContract(Name = "TestRequest", Namespace = "http://www.mysite.com/Test")]
public class TestRequest
{
[DataMember(Order = 2)]
public int ID { get; set; }
[DataMember(Order = 1)]
public InnerTestRequest InnerTestRequest { get; set; }
}
[DataContract(Name = "InnerTestRequest", Namespace = "http://www.mysite.com/Test")]
public class InnerTestRequest
{
[DataMember]
public int ID { get; set; }
}
[DataContract(Namespace = "http://www.mysite.com/Test", Name = "TestResponse")]
public class TestResponse
{
[DataMember]
public int ID { get; set; }
}
[ServiceContract(Namespace = "http://www.mysite.com/Test")]
public interface ITestService
{
[OperationContract]
[WebInvoke]
TestResponseContract Test(TestRequestContract request);
}
[MessageContract(IsWrapped = false)]
public class TestRequestContract
{
[MessageBodyMember]
public TestRequest TestRequest { get; set; }
}
[MessageContract(IsWrapped = false)]
public class TestResponseContract
{
[MessageBodyMember]
public TestResponse TestResponse { get; set; }
}
public class Service : ITestService
{
public TestResponseContract Test(TestRequestContract request)
{
return new TestResponseContract { TestResponse = new TestResponse { ID = request.TestRequest.ID } };
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITestService), new BasicHttpBinding(), "soap");
host.AddServiceEndpoint(typeof(ITestService), new WebHttpBinding(), "rest").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
var factory = new ChannelFactory<ITestService>(new BasicHttpBinding(), new EndpointAddress(baseAddress + "/soap"));
var proxy = factory.CreateChannel();
var input = new TestRequestContract { TestRequest = new TestRequest { InnerTestRequest = new InnerTestRequest { ID = 2 }, ID = 4 } };
Console.WriteLine(proxy.Test(input).TestResponse.ID);
((IClientChannel)proxy).Close();
factory.Close();
factory = new ChannelFactory<ITestService>(new WebHttpBinding(), new EndpointAddress(baseAddress + "/rest"));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
proxy = factory.CreateChannel();
Console.WriteLine(proxy.Test(input).TestResponse.ID);
((IClientChannel)proxy).Close();
factory.Close();
Console.WriteLine();
Console.WriteLine("Now using the inputs from the OP");
foreach (bool useSoap in new bool[] { true, false })
{
WebClient c = new WebClient();
c.Headers[HttpRequestHeader.ContentType] = "text/xml";
if (useSoap)
{
c.Headers["SOAPAction"] = "http://www.mysite.com/Test/ITestService/Test";
}
string uri = useSoap ?
baseAddress + "/soap" :
baseAddress + "/rest/Test";
Console.WriteLine("Request to {0}", uri);
string body = @"<TestRequest xmlns=""http://www.mysite.com/Test"">
<InnerTestRequest>
<ID>2</ID>
</InnerTestRequest>
<ID>4</ID>
</TestRequest>";
if (useSoap)
{
body = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body>" +
body +
"</s:Body></s:Envelope>";
}
Console.WriteLine(c.UploadString(uri, body));
Console.WriteLine();
}
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}