WCF数据契约——如何在WCF SOAP和REST服务中使用具有复杂对象的单个数据契约?
本文关键字:WCF 契约 数据 复杂 单个 对象 服务 SOAP REST | 更新日期: 2023-09-27 18:02:47
我有一个令人沮丧的问题,我一直试图克服,但似乎无法解决。
我有在WCF中的SOAP和REST端点上公开的服务。为了避免重复的对象代码,我想重用两个服务之间的契约对象。仅供参考,我正在使用两个单独的接口,因为我在两个端点之间有许多不同的API调用。下面是这些服务的一个简单示例:
/*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;}
}
问题我遇到的问题是,我希望两个端点的契约有效载荷使用相同的XML结构(在SOAP端点的SOAP信封内)。
例如,在REST端点上调用Test(TestRequest请求),我想发送以下XML: <TestRequest xmlns="http://www.mysite.com/Test">
<InnerTestRequest>
<ID>2</ID>
</InnerTestRequest>
<ID>4</ID>
</TestRequest>
对于SOAP端点,我希望能够发送以下内容:
<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>
我还希望响应具有相同的合同有效载荷的相同格式。我尝试了多种方法来实现这一点,包括使用[MessageContractAttribute]和指定命名空间,以及将BodyStyle设置为BodyStyle。但是我还是遇到了以下两个问题:
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.
在不指定两个单独的数据契约(一个用于REST,一个用于SOAP)的情况下,完成此任务的最佳方法是什么?
提前感谢
对于第一项:您还需要将[OperationContract]
名称空间定义为数据契约中的相同名称空间,这样您就有了一致的名称空间故事。
对于第二项,您对消息契约的理解是正确的。如果你想要移除" wrapped "元素,你需要使用unwrapped消息契约。
下面的代码展示了这是如何实现的。
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();
}
}