RESTサービスへ接続するWPFアプリ開発
モバイル ファースト、クラウドファーストと呼ばれる最近のアプリ開発。デスクトップアプリも、モバイル環境で活用できることを意識してみましょう。
WPF ( Windows Presentation Foundation ) は、Windows Vista 、Windows 7 、 Windows 8 、 Windows 8.1 で利用できるデスクトップアプリ用の部品群です。
WPF は .NET Framework の構成要素で、今後も発展を続けます。
この記事ではサービスの対話を REST で行う最初のきっかけとして、データの参照を取り上げます。
RESTを利用したサービスとの対話
WPF を使ったデスクトップアプリから、サービスにアクセスする前に、REST を利用して対話するための基本を学びましょう。
まずは、HTTP GET リクエストでデータを取り出す様子を体験するために、ブラウザーから次の URL にアクセスしてみましょう。
https://boya-catalog2.azurewebsites.net/Api/FoodCatalog/1
1.json というファイルをダウンロードします。ダウンロードが完了したら、ファイルの中身をメモ帳やテキストエディターで開きます。次の JSON 文字列が確認できるはずです。
{"id":1,"name":"リンゴ","price":98.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Apple.jpg","groupName":"果物"}
参考までに、アクセスしたサービスは、 ASP.NET Web API を利用して作成したものを、 Azure Websites に展開しているものです。この記事では RESTサービスにアクセスする WPF のクライアント側を中心にご紹介するので、現時点で ASP.NET Web API や Azure Websites そのものを知らなくても構いません。また RESTサービスにアクセスするクライアントは、URL によって決められた API のみを意識し、サーバーがどのように実装されているか、どのように展開されているかについては、一切気にする必要はありません。
続いて、次のURLにアクセスし、同様にファイルをダウンロードして中身を確認しましょう。
https://boya-catalog2.azurewebsites.net/Api/FoodCatalog/2
今度は、異なるデータが取得できていることがわかります。
{"id":2,"name":"さやえんどう","price":100.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Beans.jpg","groupName":"野菜"}
この REST サービスの例では、 URL の最後に id を指定することでリソースを特定して、データを取得することができます。
さらに、次のURLにアクセスして、ファイルをダウンロードして、中身を確認しましょう。
https://boya-catalog2.azurewebsites.net/Api/FoodCatalog/
今度は、カタログの内容がJSONの配列リテラルとして一覧されました。
[{"id":1,"name":"リンゴ","price":98.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Apple.jpg","groupName":"果物"},
{"id":2,"name":"さやえんどう","price":100.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Beans.jpg","groupName":"野菜"},
{"id":3,"name":"パン","price":120.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Bread.jpg","groupName":"食品"},
{"id":4,"name":"ヘルシーケーキ","price":320.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Cake.jpg","groupName":"食品"},
{"id":5,"name":"シナモン","price":180.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Cinnamon.jpg","groupName":"野菜"},
{"id":6,"name":"クッキー","price":250.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Cookie.jpg","groupName":"食品"},
{"id":7,"name":"果物ジュース","price":140.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/FruitJuice.jpg","groupName":"果物"},
{"id":8,"name":"マスカット","price":230.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Grapes.jpg","groupName":"果物"},
{"id":9,"name":"ハーブティー","price":190.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Herbtea.jpg","groupName":"食品"},
{"id":10,"name":"レモン","price":50.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Lemon.jpg","groupName":"果物"},
{"id":11,"name":"レタス","price":70.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Lettuce.jpg","groupName":"野菜"},
{"id":12,"name":"ミルク","price":210.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Milk.jpg","groupName":"食品"},
{"id":13,"name":"ジャガイモ","price":540.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Potato.jpg","groupName":"野菜"},
{"id":14,"name":"カボチャ","price":350.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Pumpkin.jpg","groupName":"野菜"},
{"id":15,"name":"有機野菜サラダ","price":840.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Sarada.jpg","groupName":"食品"},
{"id":16,"name":"イチゴ","price":750.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Strawberry.jpg","groupName":"果物"},
{"id":17,"name":"トマト","price":290.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Tomato.jpg","groupName":"野菜"},
{"id":18,"name":"スイカ","price":340.00,"quantity":0,"uri":"https://boya-catalog2.azurewebsites.net/Images/Foods/Watermelon.jpg","groupName":"果物"}]
このように、RESTを使ったサービスでは、クライアントアプリから、URLによって取得するリソースを指定し、目的のデータにアクセスします。
REST を使った WPF アプリとサービスとの対話
それでは、ブラウザーでの対話を WPF アプリからの対話に切り替えてみましょう。
従来の Web 参照とは異なり、事前にサービス用のプロキシ―クラスを作る必要はありません。RESTサービスのアクセスには、 System.Net.Http.HttpClient クラスを使うと便利です。
アプリとサービスとの通信は非同期で行われます。async - await パターンを利用すれば、コードの記述は同期のイメージで、実際の動作は非同期で処理されます。
Visual Studio 2013 を起動し、「WPFREST」という名前で WPF アプリケーションを新規に作成します。
WPF では XAML を利用して画面を作成します。ソリューション エクスプローラー から MainWindow.xaml を開き、XAMLのコードを次の内容で置き換えましょう。
[MainWindow.xaml]
<Window x:Class="WPFREST.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="14*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="67*"/>
<RowDefinition Height="252*"/>
</Grid.RowDefinitions>
<Button x:Name="btnRunQuery" Content="GET" FontSize="32" Grid.Column="1" Margin="10" Click="btnRunQuery_Click"/>
<TextBlock x:Name="txtStatus" Grid.ColumnSpan="2" Grid.Row="1" TextWrapping="Wrap" Text="TextBlock"/>
</Grid>
</Window>
続いて、MainWindow.xaml.cs を開き、次のメソッドを追加します。
[MainWindow.xaml.cs]
private async void btnRunQuery_Click(object sender, RoutedEventArgs e)
{
var client = new HttpClient();
var uri = "https://boya-catalog2.azurewebsites.net/Api/FoodCatalog/";
var result = await client.GetStringAsync(uri);
txtStatus.Text = result;
}
async - await パターンを利用しているので、サービスとの通信が完了した後、 TextBlock.Text プロパティへの設定が行われます。
Visual Studio 2013 IDE から [F5] キーを押してアプリをデバッグ実行し、表示された [GET] ボタンをクリックしましょう。先ほどブラウザーで対話した時と同じデータがウィンドウ内に表示されることを確認します。
JSON文字列の処理
REST サービスとの対話において、XML や JSON が利用されます。最近は、データ転送量を減らす目的で JSON を採用する場合が増えています。
JSON から .NET のオブジェクトに変換する際は、 json.net を使うのが便利です。NuGet パッケージの管理からインストールするのが簡単です。
さきほどのアプリに、Product.cs というファイル名で商品データを取り扱うクラスを追加しましょう。
[Product.cs]
using System.ComponentModel;
namespace WPFREST
{
//
// 取得した商品データ
//
public class Product : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int _id;
public int Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChanged("Id");
}
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
double _price;
public double Price
{
get { return _price; }
set
{
_price = value;
RaisePropertyChanged("Price");
}
}
int _quantity;
public int Quantity
{
get { return _quantity; }
set
{
_quantity = value;
RaisePropertyChanged("Quantity");
}
}
string _imageUri;
public string uri
{
get { return _imageUri; }
set
{
_imageUri = value;
RaisePropertyChanged("uri");
}
}
string _groupName;
public string GroupName
{
get { return _groupName; }
set
{
_groupName = value;
RaisePropertyChanged("GroupName");
}
}
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
続いて、MainWindow.xaml.cs の MainWindow クラスに次のメンバー変数を加えます。
[MainWindow.cs]
private ObservableCollection<Product> _myData;
さらに、btnRunQuery_Clickメソッドを修正します。太字の部分にご注目ください。
[MainWindow.cs]
private async void btnRunQuery_Click(object sender, RoutedEventArgs e)
{
var client = new HttpClient();
var uri = "https://boya-catalog2.azurewebsites.net/Api/FoodCatalog/";
var result = await client.GetStringAsync(uri);
// txtStatus.Text = result;
_myData = JsonConvert.DeserializeObject<ObservableCollection<Product>>(result);
}
_myData = で始まる行にブレークポイントを設置し、 [F5] キーを押してデバッグ実行し、REST サービスから取得した JSON 配列がコレクションに変換されていることを確認しましょう。
XAML とデータバインディングによる UI 作成
サービスとの対話が確認できたので、画面に表示しましょう。
今回は、ListView コントロールで商品を一覧表示するために、データバインディングを活用します。データバインディングの概念は、はがきに住所や名前を印刷する際の差し込み印刷を思い起こすとよいでしょう。
ListViewに対して ItemTemplate を定義し、データを表示するコントロールのプロパティに {Binding } を使って指定します。
<Image Source="{Binding uri}" />
<TextBlock Text="{Binding Name}"/>
WPFのデータバインディングでは、文字書式の指定もできます。例えば、Priceプロパティについては、日本の通貨記号を付けて表示しています。
<TextBlock Text="{Binding Price, StringFormat=C0,ConverterCulture=ja-JP}" />
[MainWindow.xaml]
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WPFREST.MainWindow"
Title="MainWindow" Height="600" Width="800">
<Window.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid d:DesignWidth="620" d:DesignHeight="128.955">
<Grid.RowDefinitions>
<RowDefinition Height="22*"/>
<RowDefinition Height="21*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="124*"/>
<ColumnDefinition Width="345*"/>
<ColumnDefinition Width="151*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Height="46" Margin="10,10,0,0" TextWrapping="Wrap" Text=" {Binding Name} " VerticalAlignment="Top" Width="325" FontSize="32"/>
<TextBlock Grid.Column="2" Height="Auto" Grid.Row="1" TextWrapping="Wrap" Text=" {Binding Price, StringFormat=C0,ConverterCulture=ja-JP} " Width="Auto" FontSize="32"/>
<Image Grid.RowSpan="2" Stretch="Fill" Source=" {Binding uri} " Width="124" Height="124"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="14*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="67*"/>
<RowDefinition Height="252*"/>
</Grid.RowDefinitions>
<Button x:Name="btnRunQuery" Content="GET" FontSize="32" Grid.Column="1" Margin="10" Click="btnRunQuery_Click"/>
<ListView x:Name="myListView" Grid.ColumnSpan="2" Grid.Row="1" FontSize="32" ItemTemplate="{DynamicResource DataTemplate1}">
</ListView>
</Grid>
</Window>
myListViewという名前のListViewには、ItemsSourceプロパティに_myDataのコレクションを指定し、Product オブジェクトのデータを流し込みます。ListViewは、コレクション内のそれぞれの要素を表示する際に、XAML 上に定義された ItemTemplate に従ってレンダリングを実行します。
[MainWindow.xaml.cs]
private async void btnRunQuery_Click(object sender, RoutedEventArgs e)
{
var client = new HttpClient();
var uri = "https://boya-catalog2.azurewebsites.net/Api/FoodCatalog/";
var result = await client.GetStringAsync(uri);
// txtStatus.Text = result;
_myData = JsonConvert.DeserializeObject<ObservableCollection<Product>>(result);
myListView.ItemsSource = _myData;
}
最後に、[F5]キーで実行し、表示を確認しましょう。
まとめ
脱Web参照、軽量なRESTサービスと接続するアプリ開発を始めてみましょう。
WPFデスクトップアプリとRESTサービスが対話するのは非常に簡単です。
・HttpClientクラスとasync-awaitパターンを利用してRESTサービスへアクセス
・json.net を利用して、JSONから.NETオブジェクトへの変換
・ListViewのItemsSource と ItemTemplateを利用して、データバインディングによる画面表示
サンプルアプリを改変して、様々なインターネット上のサービスから情報を取得してみましょう。
今回ご紹介した方法は、もちろんユニバーサル Windows アプリでも活用いただけますので、お試しください。
ここまでお読みいただき、ありがとうございました。