次の方法で共有


Windows Communication Foundation (WCF) Web サービスを使う

WCF は、サービス指向アプリケーションを構築する Microsoft の統合フレームワークです。 これにより、開発者は、セキュリティで保護され、信頼性が高く、トランザクションが行われ、相互運用可能な分散アプリケーションを構築できます。 この記事では、Xamarin.Forms アプリケーションから WCF 簡易オブジェクト アクセス プロトコル (SOAP) サービスを使う方法について説明します。

WCF は、次のようなさまざまなコントラクトを含むサービスについて説明しています。

  • データ コントラクト - メッセージ内のコンテンツの基礎を形成するデータ構造を定義します。
  • メッセージ コントラクト - 既存のデータ コントラクトからメッセージを作成します。
  • エラー コントラクト - カスタム SOAP エラーを指定できます。
  • サービス コントラクト - サービスがサポートする操作と、各操作との対話に必要なメッセージを指定します。 また、各サービスの操作に関連付けることができるカスタムのエラー動作も指定します。

ASP.NET Web サービス (ASMX) と WCF の間には違いがありますが、WCF は ASMX が提供するものと同じ機能 (HTTP 経由の SOAP メッセージ) をサポートします。 ASMX サービスの使用の詳細については、「ASP.NET Web サービス (ASMX) を使用する」を参照してください。

重要

WCF に対する Xamarin プラットフォームのサポートは、BasicHttpBinding クラスを使った HTTP/HTTPS 経由のテキストエンコード SOAP メッセージに限定されています。

WCF のサポートでは、プロキシを生成して TodoWCFService をホストするために、Windows 環境でのみ使用できるツールを使う必要があります。 iOS アプリをビルドしてテストするには、Windows コンピューター上に、または Azure Web サービスとして TodoWCFService をデプロイする必要があります。

通常、Xamarin Forms ネイティブ アプリは、.NET Standard クラス ライブラリとコードを共有します。 ただし、現在、.NET Core は WCF をサポートしていないため、共有プロジェクトは従来のポータブル クラス ライブラリである必要があります。 .NET Core での WCF サポートの詳細については、サーバー アプリ用 .NET Core と .NET Framework の選択に関する記事を参照してください。

サンプル アプリケーション ソリューションには、ローカルで実行できる WCF サービスが含まれており、次のスクリーンショットに示されています。

サンプル アプリケーション

Note

iOS 9 以降では、App Transport Security (ATS) により、インターネット リソース (アプリのバックエンド サーバーなど) とアプリの間にセキュリティで保護された接続が適用されるため、機密情報の漏洩が防止されます。 iOS 9 用に構築されたアプリ内では ATS が既定で有効なため、すべての接続が ATS セキュリティ要件の対象となります。 接続がこれらの要件を満たさない場合は、例外を伴って失敗します。

HTTPS プロトコルを使用してインターネット リソースの通信をセキュリティで保護できない場合は、ATS をオプトアウトできます。 これは、アプリの Info.plist ファイルを更新することで実現できます。 詳細については、アプリ トランスポート セキュリティに関する記事を参照してください。

Web サービスを使用する

WCF サービスは、次の操作を提供します。

操作 説明 パラメーター
GetTodoItems To Do アイテムのリストの取得
CreateTodoItem 新しい To Do 項目を作成する XML シリアル化 TodoItem
EditTodoItem To Do アイテムの更新 XML シリアル化 TodoItem
DeleteTodoItem To Do アイテムの削除 XML シリアル化 TodoItem

アプリケーションで使われるデータ モデルの詳細については、データのモデリングに関する記事を参照してください。

WCF サービスを使うには、"プロキシ" を生成する必要があります。これでアプリケーションはサービスに接続できるようになります。 プロキシを構築するには、メソッドと、関連付けられたサービス構成を定義するサービス メタデータを使います。 このメタデータは、Web サービスによって生成される Web サービス記述言語 (WSDL) ドキュメントの形式で公開されています。 プロキシを構築するには、Visual Studio 2017 の Microsoft WCF Web Service Reference Provider を使い、Web サービスのサービス参照を .NET Standard ライブラリに追加します。 Visual Studio 2017 で Microsoft WCF Web Service Reference Provider を使ってプロキシを作成する代わりに、ServiceModel メタデータ ユーティリティ ツール (svcutil.exe) を使うこともできます。 詳細については、「ServiceModel メタデータ ユーティリティ ツール (Svcutil.exe)」を参照してください。

生成されたプロキシ クラスには、非同期プログラミング モデル (APM) 設計パターンを使う Web サービスを使うためのメソッドが用意されています。 このパターンでは、非同期操作は、非同期操作を開始および終了する BeginOperationNameEndOperationName という 2 つのメソッドとして実装されます。

BeginOperationName メソッドは非同期操作を開始し、IAsyncResult インターフェイスを実装するオブジェクトを返します。 BeginOperationName を呼び出した後、アプリケーションは呼び出し元のスレッドに対して命令の実行を継続できますが、非同期操作はスレッド プール スレッドに対して行われます。

BeginOperationName を呼び出すたびに、アプリケーションで EndOperationName も呼び出して操作の結果を取得する必要があります。 EndOperationName の戻り値は、同期 Web サービス メソッドから返されるものと同じ型です。 たとえば、EndGetTodoItems メソッドは TodoItem インスタンスのコレクションを返します。 EndOperationName メソッドには IAsyncResult パラメーターもあります。これは、BeginOperationName メソッドへの対応する呼び出しから返されるインスタンスに設定する必要があります。

タスク並列ライブラリ (TPL) は、非同期操作を同じ Task オブジェクトにカプセル化することで、APM の開始/終了メソッド ペアを使用するプロセスを簡略化できます。 このカプセル化は、TaskFactory.FromAsync メソッドの複数のオーバーロードによって提供されます。

APM の詳細については、MSDN の「非同期プログラミング モデルTPL と従来の .NET Framework 非同期プログラミングに関する記事を参照してください。

TodoServiceClient オブジェクトを作成する

生成されたプロキシ クラスは、HTTP 経由で WCF サービスと通信するために使われる TodoServiceClient クラスを提供します。 これには、URI で指定されたサービス インスタンスから Web サービス メソッドを非同期操作として呼び出す機能があります。 非同期操作の詳細については、「非同期サポートの概要」を参照してください。

次のコード例に示すように、TodoServiceClient インスタンスはクラス レベルで宣言されているため、アプリケーションが WCF サービスを使う必要がある限り、オブジェクトは存続します。

public class SoapService : ISoapService
{
  ITodoService todoService;
  ...

  public SoapService ()
  {
    todoService = new TodoServiceClient (
      new BasicHttpBinding (),
      new EndpointAddress (Constants.SoapUrl));
  }
  ...
}

TodoServiceClient インスタンスはバインディング情報とエンドポイント アドレスで構成されます。 バインディングを使って、アプリケーションとサービスが相互に通信するために必要なトランスポート、エンコード、プロトコルの詳細を指定します。 BasicHttpBinding には、テキストエンコードされた SOAP メッセージが HTTP トランスポート プロトコル経由で送信されることを指定します。 エンドポイント アドレスを指定すると、複数の発行済みインスタンスがある場合でも、アプリケーションは WCF サービスのさまざまなインスタンスに接続できます。

サービス参照の構成の詳細については、サービス リファレンスの構成に関する記事を参照してください。

データ転送オブジェクトを作成する

このサンプル アプリケーションは、TodoItem クラスを使ってデータをモデル化しています。 Web サービスに TodoItem 項目を保存するには、まずプロキシで生成された TodoItem 型に変換する必要があります。 これを実行するには、次のコード例に示すように、ToWCFServiceTodoItem メソッドを使います。

TodoWCFService.TodoItem ToWCFServiceTodoItem (TodoItem item)
{
  return new TodoWCFService.TodoItem
  {
    ID = item.ID,
    Name = item.Name,
    Notes = item.Notes,
    Done = item.Done
  };
}

このメソッドを実行すると、単に新しい TodoWCFService.TodoItem インスタンスが作成され、各プロパティは TodoItem インスタンスの同じプロパティに設定されます。

同様に、Web サービスからデータを取得する場合は、プロキシで生成された TodoItem 型から TodoItem インスタンスに変換する必要があります。 これを実行するには、次のコード例に示すように、FromWCFServiceTodoItem メソッドを使います。

static TodoItem FromWCFServiceTodoItem (TodoWCFService.TodoItem item)
{
  return new TodoItem
  {
    ID = item.ID,
    Name = item.Name,
    Notes = item.Notes,
    Done = item.Done
  };
}

このメソッドを実行すると、単にプロキシで生成された TodoItem 型からデータが取得され、新しく作成された TodoItem インスタンスでそれが設定されます。

データを取得する

TodoServiceClient.BeginGetTodoItemsTodoServiceClient.EndGetTodoItems の各メソッドは、Web サービスによって提供される GetTodoItems 操作を呼び出すために使われます。 次のコード例に示すように、これらの非同期メソッドは Task オブジェクトにカプセル化されます。

public async Task<List<TodoItem>> RefreshDataAsync ()
{
  ...
  var todoItems = await Task.Factory.FromAsync <ObservableCollection<TodoWCFService.TodoItem>> (
    todoService.BeginGetTodoItems,
    todoService.EndGetTodoItems,
    null,
    TaskCreationOptions.None);

  foreach (var item in todoItems)
  {
    Items.Add (FromWCFServiceTodoItem (item));
  }
  ...
}

この Task.Factory.FromAsync メソッドを実行すると、TodoServiceClient.BeginGetTodoItems メソッドの完了時に TodoServiceClient.EndGetTodoItems メソッドを実行する Task が作成されます。その際に、BeginGetTodoItems デリゲートにデータが渡されないことを示す null パラメーターが使われます。 最後に、TaskCreationOptions 列挙型の値は、タスクの作成と実行に既定の動作を使う必要があることを指定します。

TodoServiceClient.EndGetTodoItems メソッドは、TodoWCFService.TodoItem インスタンスの ObservableCollection を返します。これは、表示のために TodoItem インスタンスの List に変換されます。

データの作成

TodoServiceClient.BeginCreateTodoItemTodoServiceClient.EndCreateTodoItem の各メソッドは、Web サービスによって提供される CreateTodoItem 操作を呼び出すために使われます。 次のコード例に示すように、これらの非同期メソッドは Task オブジェクトにカプセル化されます。

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  var todoItem = ToWCFServiceTodoItem (item);
  ...
  await Task.Factory.FromAsync (
    todoService.BeginCreateTodoItem,
    todoService.EndCreateTodoItem,
    todoItem,
    TaskCreationOptions.None);
  ...
}

Task.Factory.FromAsync メソッドを実行すると、TodoServiceClient.BeginCreateTodoItem メソッドの完了時に TodoServiceClient.EndCreateTodoItem メソッドを実行する Task が作成されます。その際に、BeginCreateTodoItem デリゲートに渡され、Web サービスによって作成される TodoItem を指定するデータである todoItem パラメーターが使われます。 最後に、TaskCreationOptions 列挙型の値は、タスクの作成と実行に既定の動作を使う必要があることを指定します。

Web サービスは、TodoItem の作成に失敗した場合 (これはアプリケーションによって処理されます) に FaultException をスローします。

データの更新

TodoServiceClient.BeginEditTodoItemTodoServiceClient.EndEditTodoItem の各メソッドは、Web サービスによって提供される EditTodoItem 操作を呼び出すために使われます。 次のコード例に示すように、これらの非同期メソッドは Task オブジェクトにカプセル化されます。

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  var todoItem = ToWCFServiceTodoItem (item);
  ...
  await Task.Factory.FromAsync (
    todoService.BeginEditTodoItem,
    todoService.EndEditTodoItem,
    todoItem,
    TaskCreationOptions.None);
  ...
}

Task.Factory.FromAsync メソッドを実行すると、TodoServiceClient.BeginCreateTodoItem メソッドの完了時に TodoServiceClient.EndEditTodoItem メソッドを実行する Task が作成されます。その際に、BeginEditTodoItem デリゲートに渡され、Web サービスによって更新される TodoItem を指定するデータである todoItem パラメーターが使われます。 最後に、TaskCreationOptions 列挙型の値は、タスクの作成と実行に既定の動作を使う必要があることを指定します。

Web サービスは、TodoItem の特定または更新に失敗した場合 (これはアプリケーションによって処理されます) に FaultException をスローします。

データの削除

TodoServiceClient.BeginDeleteTodoItemTodoServiceClient.EndDeleteTodoItem の各メソッドは、Web サービスによって提供される DeleteTodoItem 操作を呼び出すために使われます。 次のコード例に示すように、これらの非同期メソッドは Task オブジェクトにカプセル化されます。

public async Task DeleteTodoItemAsync (string id)
{
  ...
  await Task.Factory.FromAsync (
    todoService.BeginDeleteTodoItem,
    todoService.EndDeleteTodoItem,
    id,
    TaskCreationOptions.None);
  ...
}

Task.Factory.FromAsync メソッドを実行すると、TodoServiceClient.BeginDeleteTodoItem メソッドの完了時に TodoServiceClient.EndDeleteTodoItem メソッドを実行する Task が作成されます。その際に、BeginDeleteTodoItem デリゲートに渡され、Web サービスによって削除される TodoItem を指定するデータである id パラメーターが使われます。 最後に、TaskCreationOptions 列挙型の値は、タスクの作成と実行に既定の動作を使う必要があることを指定します。

Web サービスは、TodoItem の特定または削除に失敗した場合 (これはアプリケーションによって処理されます) に FaultException をスローします。

IIS Express へのリモート アクセスを構成する

Visual Studio 2017 または Visual Studio 2019 では、追加の構成を行わずに PC 上で UWP アプリケーションをテストできるはずです。 Android および iOS クライアントをテストするには、このセクションの追加の手順が必要になる場合があります。 詳細については、「iOS シミュレーターと Android エミュレーターからローカル Web サービスに接続する」を参照してください。

既定では、IIS Express は localhost への要求のみに応答します。 リモート デバイス (Android デバイス、iPhone、シミュレーターなど) はローカル WCF サービスにアクセスできません。 ローカル ネットワーク上の Windows 10 ワークステーションの IP アドレスを知っている必要があります。 この例では、ワークステーションの IP アドレスが 192.168.1.143 であると想定します。 以下の手順では、リモート接続を受け入れ、物理デバイスまたは仮想デバイスからサービスに接続するように Windows 10 と IIS Express を構成する方法について説明します。

  1. Windows ファイアウォールに例外を追加します。 サブネット上のアプリケーションが WCF サービスとの通信に使用できる Windows ファイアウォール経由のポートを開く必要があります。 ファイアウォールでポート 49393 を開く受信規則を作成します。 管理コマンド プロンプトから次のコマンドを実行します。

    netsh advfirewall firewall add rule name="TodoWCFService" dir=in protocol=tcp localport=49393 profile=private remoteip=localsubnet action=allow
    
  2. リモート接続を受け入れるように IIS Express を構成します。 IIS Express を構成するには、[ソリューション ディレクトリ].vs\config\applicationhost.config にある IIS Express の構成ファイルを編集します。TodoWCFService という site 要素を見つけます。 次の XML のようになります。

    <site name="TodoWCFService" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
            <virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:49393:localhost" />
        </bindings>
    </site>
    

    外部トラフィックと Android エミュレーターに対してポート 49393 を開くには、2 つの binding 要素を追加する必要があります。 バインディングは、IIS Express が要求にどのように応答するかを指定する [IP address]:[port]:[hostname] 形式を使います。 外部要求にはホスト名があり、それを binding として指定する必要があります。 次の XML を bindings 要素に追加し、IP アドレスを実際の IP アドレスに置き換えます。

    <binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
    <binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
    

    変更後の bindings 要素は次のようになります。

    <site name="TodoWCFService" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
            <virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:49393:localhost" />
            <binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
            <binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
        </bindings>
    </site>
    

    重要

    既定では、IIS Express はセキュリティ上の理由から外部ソースからの接続を受け入れません。 リモート デバイスからの接続を有効にするには、管理者アクセス許可を使って IIS Express を実行する必要があります。 これを行う最も簡単な方法は、管理者アクセス許可を使って Visual Studio 2017 を実行することです。 これにより、TodoWCFService の実行時に管理者アクセス許可で IIS Express が起動します。

    これらの手順が完了すると、TodoWCFService を実行し、サブネット上の他のデバイスから接続できるようになります。 これをテストするには、アプリケーションを実行して http://localhost:49393/TodoService.svc にアクセスします。 その URL にアクセスしたときに "無効な要求" エラーが発生した場合は、IIS Express の構成内の bindings が間違っている可能性があります (要求は IIS Express に到達していますが、拒否されています)。 別のエラーが発生した場合は、アプリケーションが実行されていないか、ファイアウォールが正しく構成されていない可能性があります。

    IIS Express が実行を継続してサービスを提供できるようにするには、[プロジェクトのプロパティ] > [Web] > [デバッガー][エディット コンティニュ] オプションをオフにします。

  3. サービスへのアクセスに使うエンドポイント デバイスをカスタマイズします。 この手順には、物理デバイスまたはエミュレートされたデバイス上で実行されるクライアント アプリケーションを構成して、WCF サービスにアクセスすることが含まれます。

    Android エミュレーターは、エミュレーターがホスト コンピューターの localhost アドレスに直接アクセスできないようにする内部プロキシを利用します。 代わりに、エミュレーター上のアドレス 10.0.2.2 は、内部プロキシ経由でホスト コンピューター上の localhost にルーティングされます。 これらのプロキシされた要求には、要求ヘッダーのホスト名として 127.0.0.1 が含まれます。そのため、上記の手順でこのホスト名に対する IIS Express バインディングを作成しました。

    Windows 用リモート iOS シミュレーターを使っている場合でも、iOS シミュレーターは Mac ビルド ホスト上で実行されます。 シミュレーターからのネットワーク要求には、ローカル ネットワーク上のワークステーション IP がホスト名として含まれます (この例では 192.168.1.143 ですが、実際の IP アドレスは異なる可能性があります)。 先ほどの手順でこのホスト名の IIS Express バインディングを作成したのは、このためです。

    TodoWCF (Portable) プロジェクトの Constants.cs ファイルの SoapUrl プロパティに、実際のネットワークに即した正しい値が設定されていることを確認します。

    public static string SoapUrl
    {
        get
        {
            var defaultUrl = "http://localhost:49393/TodoService.svc";
    
            if (Device.RuntimePlatform == Device.Android)
            {
                defaultUrl = "http://10.0.2.2:49393/TodoService.svc";
            }
            else if (Device.RuntimePlatform == Device.iOS)
            {
                defaultUrl = "http://192.168.1.143:49393/TodoService.svc";
            }
    
            return defaultUrl;
        }
    }
    

    適切なエンドポイントを使って Constants.cs を構成すると、Windows 10 ワークステーション上で実行されている TodoWCFService に物理デバイスまたは仮想デバイスから接続できるようになります。