2

我有一个令人沮丧的问题,我一直在努力克服,但似乎无法弄清楚。

我在 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 request),我想发送以下 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.Bare,但我仍然遇到以下两个问题:

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.

在不指定两个单独的 DataContract(一个用于 REST,一个用于 SOAP)的情况下完成此操作的最佳方法是什么。

提前谢谢

4

1 回答 1

2

对于第一项:您还需要[OperationContract]在数据合同中将命名空间定义为相同的命名空间,这样您就有了一致的命名空间故事。

对于第二个项目,您在消息合同方面处于正确的轨道。如果要删除“包装”元素,则需要使用未包装的消息协定。

下面的代码显示了如何实现这一点。

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();
    }
}
于 2013-03-07T06:33:02.077 回答