代码之家  ›  专栏  ›  技术社区  ›  TheJediCowboy

WCF DataContracts-如何将单个DataContract与复杂对象一起用于WCF SOAP和REST服务?

  •  2
  • TheJediCowboy  · 技术社区  · 12 年前

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

    我在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端点上,我想发送以下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)的情况下,实现这一点的最佳方法是什么。

    提前谢谢

    1 回复  |  直到 12 年前
        1
  •  2
  •   carlosfigueira    12 年前

    对于第一项:您还需要定义 [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();
        }
    }