次の方法で共有


方法: Managed-Code DCOM を WCF に移行する

Windows Communication Foundation (WCF) は、分散環境のサーバーとクライアント間のマネージド コード呼び出しに対して、分散コンポーネント オブジェクト モデル (DCOM) よりも推奨され、セキュリティで保護された選択肢です。 この記事では、次のシナリオでコードを DCOM から WCF に移行する方法について説明します。

  • リモート サービスは、オブジェクトをバリューの形式でクライアントに返します。

  • クライアントがオブジェクトを値別にリモート サービスに送信する

  • リモート サービスは、クライアントへの参照によってオブジェクトを返します。

セキュリティ上の理由から、クライアントからサービスへの参照によるオブジェクトの送信は WCF では許可されません。 クライアントとサーバー間の会話を必要とするシナリオは、双方向サービスを使用して WCF で実現できます。 双方向サービスの詳細については、「 双方向サービス」を参照してください。

これらのサービスの WCF サービスとクライアントの作成の詳細については、「 基本的な WCF プログラミングサービスの設計と実装、および クライアントの構築」を参照してください。

DCOM コード例

これらのシナリオでは、WCF を使用して示されている DCOM インターフェイスには、次の構造があります。

[ComVisible(true)]
[Guid("AA9C4CDB-55EA-4413-90D2-843F1A49E6E6")]
public interface IRemoteService
{
   Customer GetObjectByValue();
   IRemoteObject GetObjectByReference();
   void SendObjectByValue(Customer customer);
}

[ComVisible(true)]
[Guid("A12C98DE-B6A1-463D-8C24-81E4BBC4351B")]
public interface IRemoteObject
{
}

public class Customer
{
}

サービスは値によるオブジェクトを返します

このシナリオでは、サービスを呼び出すと、サーバーからクライアントに値として渡されるオブジェクトが返されます。 このシナリオは、次の COM 呼び出しを表します。

public interface IRemoteService
{
    Customer GetObjectByValue();
}

このシナリオでは、クライアントはリモート サービスからオブジェクトの逆シリアル化されたコピーを受け取ります。 クライアントは、サービスに呼び戻すことなく、このローカル コピーと対話できます。 つまり、クライアントは、ローカル コピーのメソッドが呼び出されたときにサービスが一切関与しないことが保証されます。 WCF では常に値によってサービスからオブジェクトが返されるため、次の手順では通常の WCF サービスの作成について説明します。

手順 1: WCF サービス インターフェイスを定義する

WCF サービスのパブリック インターフェイスを定義し、[ServiceContractAttribute] 属性でマークします。 クライアントに公開するメソッドを [OperationContractAttribute] 属性でマークします。 次の例は、これらの属性を使用して、クライアントが呼び出すことができるサーバー側インターフェイスとインターフェイス メソッドを識別する方法を示しています。 このシナリオで使用されるメソッドは太字で示されています。

using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
. . .
[ServiceContract]
public interface ICustomerManager
{
    [OperationContract]
    void StoreCustomer(Customer customer);

    [OperationContract]     Customer GetCustomer(string firstName, string lastName);

}

手順 2: データ コントラクトを定義する

次に、サービスのデータ コントラクトを作成する必要があります。サービスとそのクライアント間でのデータの交換方法について説明します。 データ コントラクトで記述されているクラスは、[DataContractAttribute] 属性でマークする必要があります。 クライアントとサーバーの両方に表示する個々のプロパティまたはフィールドは、[DataMemberAttribute] 属性でマークする必要があります。 データ コントラクト内のクラスから派生した型を許可する場合は、[KnownTypeAttribute] 属性を使用して型を識別する必要があります。 WCF は、サービス インターフェイス内の型と既知の型として識別される型のみをシリアル化または逆シリアル化します。 既知の型ではない型を使用しようとすると、例外が発生します。

データ コントラクトの詳細については、「データ コントラクト」を参照してください。

[DataContract]
[KnownType(typeof(PremiumCustomer))]
public class Customer
{
    [DataMember]
    public string Firstname;
    [DataMember]
    public string Lastname;
    [DataMember]
    public Address DefaultDeliveryAddress;
    [DataMember]
    public Address DefaultBillingAddress;
}
 [DataContract]
public class PremiumCustomer : Customer
{
    [DataMember]
    public int AccountID;
}

 [DataContract]
public class Address
{
    [DataMember]
    public string Street;
    [DataMember]
    public string Zipcode;
    [DataMember]
    public string City;
    [DataMember]
    public string State;
    [DataMember]
    public string Country;
}

手順 3: WCF サービスを実装する

次に、前の手順で定義したインターフェイスを実装する WCF サービス クラスを実装する必要があります。

public class CustomerService: ICustomerManager
{
    public void StoreCustomer(Customer customer)
    {
        // write to a database
    }
    public Customer GetCustomer(string firstName, string lastName)
    {
        // read from a database
    }
}

手順 4: サービスとクライアントを構成する

WCF サービスを実行するには、特定の WCF バインディングを使用して、特定の URL でそのサービス インターフェイスを公開するエンドポイントを宣言する必要があります。 バインディングは、クライアントとサーバーが通信するためのトランスポート、エンコード、プロトコルの詳細を指定します。 通常は、サービス プロジェクトの構成ファイル (web.config) にバインドを追加します。 サービス例のバインド エントリを次に示します。

<configuration>
  <system.serviceModel>
    <services>
      <service name="Server.CustomerService">
        <endpoint address="http://localhost:8083/CustomerManager"
                  binding="basicHttpBinding"
                  contract="Shared.ICustomerManager" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

次に、サービスで指定されたバインディング情報と一致するようにクライアントを構成する必要があります。 これを行うには、クライアントのアプリケーション構成 (app.config) ファイルに次を追加します。

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="customermanager"
                address="http://localhost:8083/CustomerManager"
                binding="basicHttpBinding"
                contract="Shared.ICustomerManager"/>
    </client>
  </system.serviceModel>
</configuration>

手順 5: サービスを実行する

最後に、サービス アプリに次の行を追加し、アプリを起動することで、コンソール アプリケーションで自己ホストできます。 WCF サービス アプリケーションをホストするその他の方法の詳細については、 ホスティング サービスを参照してください。

ServiceHost customerServiceHost = new ServiceHost(typeof(CustomerService));
customerServiceHost.Open();

手順 6: クライアントからサービスを呼び出す

クライアントからサービスを呼び出すには、サービスのチャネル ファクトリを作成し、チャネルを要求する必要があります。チャネルを使用すると、 GetCustomer メソッドをクライアントから直接呼び出できるようになります。 チャネルはサービスのインターフェイスを実装し、基になる要求/応答ロジックを自動的に処理します。 そのメソッド呼び出しからの戻り値は、サービス応答の逆シリアル化されたコピーです。

ChannelFactory<ICustomerManager> factory =
     new ChannelFactory<ICustomerManager>("customermanager");
ICustomerManager service = factory.CreateChannel();
Customer customer = service.GetCustomer("Mary", "Smith");

クライアントが値によるオブジェクトをサーバーに送信する

このシナリオでは、クライアントはオブジェクトをサーバーに値別に送信します。 つまり、サーバーはオブジェクトの逆シリアル化されたコピーを受け取ります。 サーバーはそのコピーでメソッドを呼び出し、クライアント コードへのコールバックがないことを保証できます。 前述のように、データの通常の WCF 交換は値によって行われます。 これにより、これらのオブジェクトのいずれかのメソッドの呼び出しはローカルでのみ実行されます。クライアントでコードが呼び出されることはありません。

このシナリオは、次の COM メソッド呼び出しを表します。

public interface IRemoteService
{
    void SendObjectByValue(Customer customer);
}

このシナリオでは、最初の例に示すように、同じサービス インターフェイスとデータ コントラクトを使用します。 さらに、クライアントとサービスは同じ方法で構成されます。 この例では、オブジェクトを送信し、同じ方法で実行するチャネルが作成されます。 ただし、この例では、オブジェクトを値渡ししてサービスを呼び出すクライアントを作成します。 クライアントがサービス コントラクトで呼び出すサービス メソッドは、太字で表示されます。

[ServiceContract]
public interface ICustomerManager
{
    [OperationContract]     void StoreCustomer(Customer customer);

    [OperationContract]
    Customer GetCustomer(string firstName, string lastName);
}

値別オブジェクトを送信するコードをクライアントに追加する

次のコードは、クライアントが新しい値別顧客オブジェクトを作成し、 ICustomerManager サービスと通信するチャネルを作成し、顧客オブジェクトをそれに送信する方法を示しています。

顧客オブジェクトはシリアル化され、サービスに送信され、サービスによってそのオブジェクトの新しいコピーに逆シリアル化されます。 このオブジェクトに対してサービスが呼び出すメソッドは、サーバー上でのみローカルに実行されます。 このコードは、派生型 (PremiumCustomer) の送信を示しています。 サービス コントラクトは Customer オブジェクトを受け取りますが、サービス データ コントラクトは [KnownTypeAttribute] 属性を使用して、 PremiumCustomer も許可されていることを示します。 WCF は、このサービス インターフェイスを介して他の型のシリアル化または逆シリアル化の試行に失敗します。

PremiumCustomer customer = new PremiumCustomer();
customer.Firstname = "John";
customer.Lastname = "Doe";
customer.DefaultBillingAddress = new Address();
customer.DefaultBillingAddress.Street = "One Microsoft Way";
customer.DefaultDeliveryAddress = customer.DefaultBillingAddress;
customer.AccountID = 42;

ChannelFactory<ICustomerManager> factory =
   new ChannelFactory<ICustomerManager>("customermanager");
ICustomerManager customerManager = factory.CreateChannel();
customerManager.StoreCustomer(customer);

サービスは参照によってオブジェクトを返します

このシナリオでは、クライアント アプリはリモート サービスを呼び出し、メソッドはオブジェクトを返します。このオブジェクトは、サービスからクライアントに参照渡しされます。

前述のように、WCF サービスは常に値によってオブジェクトを返します。 ただし、 EndpointAddress10 クラスを使用して同様の結果を得ることができます。 EndpointAddress10はシリアル化可能な値渡しオブジェクトであり、クライアントがサーバー上のセッションフルな参照渡しオブジェクトを取得するために使用できます。

このシナリオで示されている WCF の参照によるオブジェクトの動作は、DCOM とは異なります。 DCOM では、サーバーは参照オブジェクトをクライアントに直接返すことができます。クライアントは、サーバー上で実行されるそのオブジェクトのメソッドを呼び出すことができます。 ただし、WCF では、返されるオブジェクトは常に値によって返されます。 クライアントは、 EndpointAddress10 によって表される値によるオブジェクトを受け取り、それを使用して独自のセッション型参照オブジェクトを作成する必要があります。 セッションフル オブジェクトのクライアント メソッド呼び出しは、サーバー上で実行されます。言い換えると、WCF のこの参照によるオブジェクトは、セッションフルに構成されている通常の WCF サービスです。

WCF では、セッションは、2 つのエンドポイント間で送信される複数のメッセージを関連付ける方法です。 つまり、クライアントがこのサービスへの接続を取得すると、クライアントとサーバーの間にセッションが確立されます。 クライアントは、この単一セッション内のすべての対話に、サーバー側オブジェクトの 1 つの一意のインスタンスを使用します。 セッションフル WCF コントラクトは、接続指向のネットワーク要求/応答パターンに似ています。

このシナリオは、次の DCOM メソッドで表されます。

public interface IRemoteService
{
    IRemoteObject GetObjectByReference();
}

手順 1: セッションフル WCF サービス インターフェイスと実装を定義する

まず、セッションフル オブジェクトを含む WCF サービス インターフェイスを定義します。

このコードでは、セッションフル オブジェクトは ServiceContract 属性でマークされ、通常の WCF サービス インターフェイスとして識別されます。 さらに、 SessionMode プロパティは、セッションフル サービスであることを示すように設定されます。

[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface ISessionBoundObject
{
    [OperationContract]
    string GetCurrentValue();

    [OperationContract]
    void SetCurrentValue(string value);
}

次のコードは、サービスの実装を示しています。

サービスは [ServiceBehavior] 属性でマークされ、その InstanceContextMode プロパティは InstanceContextMode.PerSessions に設定され、セッションごとにこの型の一意のインスタンスを作成する必要があることを示します。

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class MySessionBoundObject : ISessionBoundObject
    {
        private string _value;

        public string GetCurrentValue()
        {
            return _value;
        }

        public void SetCurrentValue(string val)
        {
            _value = val;
        }

    }

手順 2: セッションフル オブジェクトの WCF ファクトリ サービスを定義する

セッションフル オブジェクトを作成するサービスを定義して実装する必要があります。 次のコードは、これを行う方法を示しています。 このコードは、 EndpointAddress10 オブジェクトを返す別の WCF サービスを作成します。 これは、セッション全体のオブジェクトを作成するために使用できるエンドポイントのシリアル化可能な形式です。

[ServiceContract]
    public interface ISessionBoundFactory
    {
        [OperationContract]
        EndpointAddress10 GetInstanceAddress();
    }

このサービスの実装を次に示します。 この実装では、セッションフル オブジェクトを作成するためのシングルトン チャネル ファクトリが維持されます。 GetInstanceAddressが呼び出されると、チャネルが作成され、このチャネルに関連付けられているリモート アドレスを指すEndpointAddress10 オブジェクトが作成されます。 EndpointAddress10 は、クライアントに値で返すことができるデータ型です。

public class SessionBoundFactory : ISessionBoundFactory
    {
        public static ChannelFactory<ISessionBoundObject> _factory =
            new ChannelFactory<ISessionBoundObject>("sessionbound");

        public SessionBoundFactory()
        {
        }

        public EndpointAddress10 GetInstanceAddress()
        {
            IClientChannel channel = (IClientChannel)_factory.CreateChannel();
            return EndpointAddress10.FromEndpointAddress(channel.RemoteAddress);
        }
    }

手順 3: WCF サービスを構成して開始する

これらのサービスをホストするには、サーバーの構成ファイル (web.config) に次の追加を行う必要があります。

  1. セッションフル オブジェクトのエンドポイントを説明する <client> セクションを追加します。 このシナリオでは、サーバーはクライアントとしても機能し、これを有効にするように構成する必要があります。

  2. <services> セクションで、ファクトリ オブジェクトとセッションフル オブジェクトのサービス エンドポイントを宣言します。 これにより、クライアントはサービス エンドポイントと通信し、 EndpointAddress10 を取得し、セッションフル チャネルを作成できます。

これらの設定を含む構成ファイルの例を次に示します。

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="sessionbound"
                address="net.tcp://localhost:8081/SessionBoundObject"
                binding="netTcpBinding"
                contract="Shared.ISessionBoundObject"/>
    </client>

    <services>
      <service name="Server.MySessionBoundObject">
        <endpoint address="net.tcp://localhost:8081/SessionBoundObject"
                  binding="netTcpBinding"
                  contract="Shared.ISessionBoundObject" />
      </service>
      <service name="Server.SessionBoundFactory">
        <endpoint address="net.tcp://localhost:8081/SessionBoundFactory"
                  binding="netTcpBinding"
                  contract="Shared.ISessionBoundFactory" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

コンソール アプリケーションに次の行を追加して、サービスをセルフホストし、アプリを起動します。

ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory));
factoryHost.Open();

ServiceHost sessionBoundServiceHost = new ServiceHost(
typeof(MySessionBoundObject));
sessionBoundServiceHost.Open();

手順 4: クライアントを構成し、サービスを呼び出す

プロジェクトのアプリケーション構成ファイル (app.config) に次のエントリを作成して、WCF サービスと通信するようにクライアントを構成します。

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="sessionbound"
                address="net.tcp://localhost:8081/SessionBoundObject"
                binding="netTcpBinding"
                contract="Shared.ISessionBoundObject"/>
      <endpoint name="factory"
                address="net.tcp://localhost:8081/SessionBoundFactory"
                binding="netTcpBinding"
                contract="Shared.ISessionBoundFactory"/>
    </client>
  </system.serviceModel>
</configuration>

サービスを呼び出すには、次の操作を行うコードをクライアントに追加します。

  1. ISessionBoundFactory サービスへのチャネルを作成します。

  2. チャネルを使用して ISessionBoundFactory サービスを呼び出し、 EndpointAddress10 オブジェクトを取得します。

  3. EndpointAddress10を使用して、セッションフル オブジェクトを取得するチャネルを作成します。

  4. SetCurrentValueメソッドとGetCurrentValue メソッドを呼び出して、複数の呼び出しで同じオブジェクト インスタンスが使用されていることを示します。

ChannelFactory<ISessionBoundFactory> factory =
        new ChannelFactory<ISessionBoundFactory>("factory");

ISessionBoundFactory sessionBoundFactory = factory.CreateChannel();

EndpointAddress10 address = sessionBoundFactory.GetInstanceAddress();

ChannelFactory<ISessionBoundObject> sessionBoundObjectFactory =
    new ChannelFactory<ISessionBoundObject>(
        new NetTcpBinding(),
        address.ToEndpointAddress());

ISessionBoundObject sessionBoundObject =
        sessionBoundObjectFactory.CreateChannel();

sessionBoundObject.SetCurrentValue("Hello");
if (sessionBoundObject.GetCurrentValue() == "Hello")
{
    Console.WriteLine("Session-full instance management works as expected");
}

こちらも参照ください