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 サービスを使うためのメソッドが用意されています。 このパターンでは、非同期操作は、非同期操作を開始および終了する BeginOperationName と EndOperationName という 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.BeginGetTodoItems
と TodoServiceClient.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.BeginCreateTodoItem
と TodoServiceClient.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.BeginEditTodoItem
と TodoServiceClient.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.BeginDeleteTodoItem
と TodoServiceClient.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 を構成する方法について説明します。
Windows ファイアウォールに例外を追加します。 サブネット上のアプリケーションが WCF サービスとの通信に使用できる Windows ファイアウォール経由のポートを開く必要があります。 ファイアウォールでポート 49393 を開く受信規則を作成します。 管理コマンド プロンプトから次のコマンドを実行します。
netsh advfirewall firewall add rule name="TodoWCFService" dir=in protocol=tcp localport=49393 profile=private remoteip=localsubnet action=allow
リモート接続を受け入れるように 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] > [デバッガー] で [エディット コンティニュ] オプションをオフにします。
サービスへのアクセスに使うエンドポイント デバイスをカスタマイズします。 この手順には、物理デバイスまたはエミュレートされたデバイス上で実行されるクライアント アプリケーションを構成して、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 に物理デバイスまたは仮想デバイスから接続できるようになります。