【问题标题】:WCF DataContracts - How can I use a single DataContract with complex object for both WCF SOAP and REST services?WCF DataContracts - 如何将单个 DataContract 与复杂对象一起用于 WCF SOAP 和 REST 服务?
【发布时间】:2013-03-06 16:38:00
【问题描述】:

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

我在 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)的情况下,最好的方法是什么?

提前谢谢

【问题讨论】:

  • 有趣。您是否尝试过在每个数据成员上显式指定命名空间?
  • 是的,它不起作用:/ 我也搜索了几个参考,但都没有成功。我目前正在尝试以下解决方案,但它导致合同的 SOAP 端点序列化出现问题。 social.msdn.microsoft.com/Forums/en-CA/wcf/thread/…

标签: c# wcf rest soap datacontract


【解决方案1】:

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

对于第二项,您在消息合同方面处于正确的轨道。如果要删除“包装”元素,则需要使用 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();
    }
}

【讨论】:

  • 这是否意味着我需要使用两份单独的合同,一份用于 REST,一份用于 SOAP?
  • 没有。在上面的示例中,只有一个合同,用于两者。您还可以在 REST 端点中使用消息协定,如此代码所示。
  • Carlos...我尝试将上述解决方案应用于我的项目,但我必须切换 IsWrapped=true 才能使其工作?这有意义吗?
  • 好吧,如果它对你有用,那就太好了:)。我试图做的是完全使用您提供的 XML - 如我的示例的 Test 方法所示 - 为此我需要一个 IsWrapped=false 消息协定。但是,如果在您的情况下签订合同有效,那么这对您有效。
  • 这是 IsWrapped 所做的:如果为真,那么 XML 的根将是 [MC] 类的名称(或任何 WrapperName,如果设置在属性上)。如果为 false,则根将是(单个)[MessageBodyMember] 元素。我在request 中使用了IsWrapped = false,因为这就是您的问题。由于您没有指定您需要什么,因此在回复中,我只是选择了任何值...
猜你喜欢
  • 1970-01-01
  • 2011-01-04
  • 2019-08-15
  • 2014-03-16
  • 2013-11-06
  • 2011-03-26
  • 2014-01-06
  • 1970-01-01
  • 2013-01-24
相关资源
最近更新 更多