2

私はあまりにも克服しようとしてきたが、理解できないように見えるイライラする問題を抱えています。

WCF の SOAP エンドポイントと REST エンドポイントの両方で公開しているサービスがあります。オブジェクト コードの重複を避けるために、2 つのサービス間でコントラクト オブジェクトを再利用したいと考えています。これらのサービスの簡単な例を以下に示します。

/*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.Bare に設定するなど、これを達成するために複数の方法を試しましたが、まだ次の 2 つの問題が発生しています。

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用の2つの個別のDataContractを指定せずにこれを達成する最善の方法は何ですか?

事前に感謝

4

1 に答える 1

2

最初の項目について: 名前空間をデータ コントラクトと同じものとして定義する必要もあります[OperationContract]。これにより、一貫した名前空間のストーリーが得られます。

2 番目の項目については、メッセージ コントラクトの正しい軌道に乗っていました。「ラッピング」要素を削除する場合は、ラップされていないメッセージ コントラクトを使用する必要があります。

以下のコードは、これを実現する方法を示しています。

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 に答える