2012年12月26日水曜日
2012年11月15日木曜日
◆「Silverlightナビゲーションアプリケーション」テンプレート
「VisualStudio2012」で「Silverlight」用テンプレートには以下の5種類が用意されている。
基本は「Silverlightアプリケーション」、画面遷移機能が予め組み込まれた「Silverlightナビゲーションアプリケーション」、「Silverlightナビゲーションアプリケーション」の機能に加えてユーザー管理機能やリソース管理機能等もろもろ追加してあるのが「Silverlightビジネスアプリケーション」。
私が主に作る社内ツール(ユーザーはAD管理)などには「Silverlightナビゲーションアプリケーション」がちょうど良さそうだ。
というわけでちょっとだけ触ってみる。
データベースアプリケーションを作るには「WCF RIAサービスを有効にする」をチェックするのが普通と思うが、今回はテンプレートの確認だけなのでそのまま「OK」
サーバーおよびクライアントプロジェクトが作られ、「MainPage.xaml」「About.xaml」「ErrorWindow.xaml」「Home.xaml」の4つの画面が存在する。
以下のように「MainPage」の中に「Frame」コントロールが張り付けられ、そのコンテンツを「Home.xaml」や「About.xaml」に入れ替えて表示する仕組みの様だ。
どちらかというとページ遷移というよりはHTMLでのフレームに近い感覚だが、遷移するごとに「URI」がちゃんと変わってくれる。
クライアントプロジェクトの「Views」フォルダーに「Page」を追加していき、そのページへのリンクを「MainPage」メニューに加えていくのが基本スタイルになりそうだ。
試しに、「マイページ1」というページを一つ追加してメニューに遷移を加えてみた。
<Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}">
<StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}">
<HyperlinkButton x:Name="Link1" Style="{StaticResource LinkStyle}"
NavigateUri="/Home" TargetName="ContentFrame" Content="ホーム"/>
<Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/>
<HyperlinkButton x:Name="Link3" Style="{StaticResource LinkStyle}"
NavigateUri="/マイページ1" TargetName="ContentFrame" Content="マイページ1"/>
<Rectangle x:Name="Divider2" Style="{StaticResource DividerStyle}"/>
<HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}"
NavigateUri="/About" TargetName="ContentFrame" Content="バージョン情報"/>
</StackPanel>
</Border>
フレーの定義はこんな感じになっていて、「UriMapper」なるものが使われている。
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
<navigation:Frame.UriMapper>
<uriMapper:UriMapper>
<uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
<uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
</uriMapper:UriMapper>
</navigation:Frame.UriMapper>
</navigation:Frame>
先ほどの「HyperlinkButton」で「NavigateUri」属性に「/マイページ1」という指定をしていたのだが、これを「/Views/マイページ1.xaml」に補ってくれる指定がここでされているようだ。
2012年11月13日火曜日
◆Visual Studio 2012 で Silverlight RIA Service を試してみる2
以下のチュートリアルを順を追って試してみる。
http://msdn.microsoft.com/ja-jp/library/ee707376(v=vs.91).aspx
サンプルデータベースは必ずしもチュートリアルと同じものを使わなくても試せると思うが、AdventureWorksを使うのならば以下からダウンロードできる。(AdventureWorksLTが小さくて良いかもしれない)
Microsoft SQL Server Product Samples: Database - Download: SQL Server 2005 SP2a
定番のNorthwndあたりでも良さそうに思う。
<プロジェクト間にRIA Services リンクを使用したソリューションの作成>
RIA Service ソリューションを設定するには
- 新しいプロジェクトをクリック
- [インストールされたテンプレート] の [Silverlight] で [Silverlight アプリケーション] テンプレートをクリックし、新しいプロジェクトにRIAServicesExample という名前を付けて「OK」をクリック
- 表示された「新しいSilverlightアプリケーション」ダイアログで、「WCF RIA サービスを有効にする」をチェックして「OK」ボタンをクリックする
<データモデルの作成>
予め「AdventureWorksLT」への接続が作成してある前提とする
データを中間層で使用できるようにするには
- ソリューション エクスプローラーで、サーバー プロジェクト RIAServicesExample.Web を右クリックし、[追加] をポイントして、[新しい項目] をクリック
- 左ペインで「データ」カテゴリを選び、「ADO.NET Entity Data Model」をクリックする
- 「名前」欄に「AdventureWorksModel」という名前をつけて「追加ボタン」をクリックする
- 「Entity Data Model ウィザード」にて「データベースから生成」を選択して「次へ」ボタンをクリック
- 「Entity Data Model ウィザード」画面にて、予め作成済みの「AdventureWorks」への接続を指定して「次へ」ボタンをクリック
- 「データベースオブジェクトと設定の選択」画面にて「Address」「Customer」「CustomerAddress」のテーブルをチェックして「完了」ボタンをクリック
- ソリューションエクスプローラにて「AdventureWorksModel.edmx」配下の「*.tt」ファイルを削除
- edmxダイアグラムの空白部分をクリックして「AdventureWorksLTModel」のプロパティを表示させ、「コード生成方法」に「規定」を指定する
- 「F6」キーを押して一旦ビルド
<ドメインサービスの作成>
ドメインサービスを作成するには
- ソリューション エクスプローラーにて「RIAServicesExample.Web」プロジェクトを右クリックし「追加」「新しい項目」をクリック
- カテゴリの一覧で「Web」を選択し、「DomainServiceクラス」テンプレートをクリック
- クラスに「CustomerDomainService」という名前を付ける
- 「追加」ボタンをクリック
- 「新しいドメインサービスクラスの追加」ダイアログが表示されるので「Customer」エンティティを選択し、編集の有効化をチェックし、「OK」ボタンをクリック
<Silverlightクライアントの作成>
データを表示するには
-
「RIAServicesExample」プロジェクトの「MainPage.xaml」を開く
-
ツールボックスからXAMLビューのGrid要素内に「DataGridコントロール」をドラッグする
-
名前を「CustomerGrid」とする
1: <Grid x:Name="LayoutRoot" Background="White">
2: <sdk:DataGrid x:Name="CustomerGrid"/>
3: </Grid>
「F7」キーを押してコードビハインドを表示させ、ネームスペースに次の2つのステートメントを追加する(
using RIAServicesExample.Web;
およびusing System.ServiceModel.DomainServices.Client;
)「GetCustomerQuery」メソッドを呼び出してデータを取得し、「CustomerGrid」にバインドする
1: public partial class MainPage : UserControl
2: {
3: CustomerDomainContext _customerContext = new CustomerDomainContext();
4:
5: public MainPage()
6: {
7: InitializeComponent();
8:
9: LoadOperation<Customer> loadOp = this._customerContext.Load(_customerContext.GetCustomersQuery());
10: CustomerGrid.ItemsSource = loadOp.Entities;
11: }
12: }
<クライアントでのクエリのカスタマイズ>
「CustomerID」が20以下のデータのみを表示する
- 「RIAServicesExample」プロジェクトで「MainPage.xaml.cs」を開く
- コンストラクタメソッドを以下のように修正する
public MainPage()
{
InitializeComponent();
var query = this._customerContext.GetCustomersQuery().Where(c => c.CustomerID <= 20);
LoadOperation<Customer> loadOp = this._customerContext.Load(query);
//LoadOperation<Customer> loadOp = this._customerContext.Load(_customerContext.GetCustomersQuery());
CustomerGrid.ItemsSource = loadOp.Entities;
}
- 「F5」キーを押して実行し、「CustomerID」が20以下のデータが表示されるのを確認する
(確認できたらすべてのCustomerIDを表示するよう、元に戻しておく)
<サーバーでのクエリのカスタマイズ>
パラメータを受け取り単一の結果(エンティティ)を返すクエリを追加する
- 「RIAServicesExample.Web」プロジェクトの「CustomerDomainService.cs」を開き以下のクエリーを追加する。(CustomerIDを指定して1件抽出するクエリー)
public Customer GetCustomerByID(int customerID)
{
return this.ObjectContext.Customers.SingleOrDefault(c => c.CustomerID == customerID);
}
なお、MSのチュートリアルではこのメソッドに「Query」属性を指定しているが基本的には要らないようだ。(Customerとかのエンティティではない型を返す場合には必要になるのかもしれない)
パラメータを受け取りエンティティのコレクションを返すクエリを追加する
- 「RIAServicesExample.Web」プロジェクトの「CustomerDomainService」を開き以下のクエリーを追加する。(LastNameの開始文字を指定して複数件抽出するクエリー)
public IQueryable<Customer> GetCustomersByLastNameLetter(string startingLastNameLetter)
{
return this.ObjectContext.Customers.
Where(c => c.LastName.StartsWith(startingLastNameLetter) == true);
}
クエリ結果を表示する
- 「RIAServicesExample」プロジェクトで「MainPage.xaml」を開き抽出条件を指定するためにXAMLを修正する。
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Text="IDで検索:" />
<TextBox x:Name="idParam" Width="50"/>
<Button x:Name="btnID" Content="実行" Click="btnID_Click" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Text="名前で検索:" />
<TextBox x:Name="nameParam" Width="50"/>
<Button x:Name="btnName" Content="実行" Click="btnName_Click" />
</StackPanel>
<sdk:DataGrid x:Name="CustomerGrid" Grid.Row="1" Grid.ColumnSpan="2"/>
</Grid>
- コードビハインドを開き「実行」ボタンのイベントハンドラーを以下のように指定する。
private void btnID_Click(object sender, RoutedEventArgs e)
{
btnID.IsEnabled = false;
btnName.IsEnabled = false;
LoadOperation<Customer> loadOp = this._customerContext.Load(this._customerContext.
GetCustomerByIDQuery(int.Parse(idParam.Text)), CustomerLoadedCallBack, null);
CustomerGrid.ItemsSource = loadOp.Entities;
}
private void btnName_Click(object sender, RoutedEventArgs e)
{
btnID.IsEnabled = false;
btnName.IsEnabled = false;
LoadOperation<Customer> loadOp = this._customerContext.Load(this._customerContext.
GetCustomersByLastNameLetterQuery(nameParam.Text), CustomerLoadedCallBack, null);
CustomerGrid.ItemsSource = loadOp.Entities;
}
private void CustomerLoadedCallBack(LoadOperation<Customer> obj)
{
btnID.IsEnabled = true;
btnName.IsEnabled = true;
}
- 「F5」キーを押して実行し、「CustomerID」での抽出、「LastName」での抽出を試してみる。
<InvokeOperationを使ってドメインサービスを呼び出す>
ドメインサービスを呼び出すにはこれまで使用した「LoadOperation」以外に「InvokeOperation」というクラスが存在する。
今のところ、サンプルや資料を見た限りその違いは今一つ良く判らない。(今後使っていくと判るのかもしれない)
さしあたって、エンティティではなくプリミティブなデータ型を取得する場合に使用するのかも?ってところ。
呼び出し操作を追加するには
- 「RIAServicesExample.Web」プロジェクトで「CustomerDomainService.cs」を開く
- 以下のメソッドを追加する
public DateTime GetToday()
{
return DateTime.Today;
}
- 「F6」キーを押して一旦ビルド
- 「RIAServicesExample」クライアントプロジェクトで「MainPage.xaml」を開く
- 「Grid」に1行追加
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
- 「DataGrid」の下にサービス呼び出し用のボタンと結果表示用の「TextBlock」を追加
<sdk:DataGrid x:Name="CustomerGrid" Grid.Row="1" Grid.ColumnSpan="2"/>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button x:Name="btnGetToday" Content="GetToday" Click="btnGetToday_Click"/>
<TextBlock x:Name="txtToday" Width="100" />
</StackPanel>
- 「MainPage.xaml.cs」を開きボタンのイベントハンドラーに以下の処理を追加する
private void btnGetToday_Click(object sender, RoutedEventArgs e)
{
InvokeOperation<DateTime> invokeOp = this._customerContext.GetToday((op) =>
{
if (op.HasError)
{
MessageBox.Show("Error");
return;
}
else
{
txtToday.Text = op.Value.ToString("yyyy年MM月dd日");
}
}, null);
}
2012年11月2日金曜日
◆RIAサービスを使ってデータを更新
前回MTG Blog: ◆RIAサービスを使ってデータ表示2の続きで、今度はデータを更新してみる。
<データベースの更新>
レコードを更新するには
- 「RIA1」プロジェクトの「EmployeeList.xaml」を開く。
- 「DataForm」コントロールの下に以下のXAMLを追加して、「Submit」ボタンを追加する。
1: <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,12,0,0">
2: <Button x:Name="submitButton" Width="75" Height="23"
3: Content="Sbumit" Click="submitButton_Click_1" />
4: </StackPanel>
- 「DomainDataSource」コントロールに「SubmittedChanges」イベントハンドラーを指定する。
1: <riaControls:DomainDataSource Name="employeeDataSource"
2: QueryName="GetEmployeesQuery" AutoLoad="False"
3: SubmittedChanges="employeeDataSource_SubmittedChanges_1">
- 「EmployeeList.xaml」のコードビハインドを開く。
- 「Submit」ボタンのイベントハンドラーに以下の処理を記述する。
(更新処理中に再びクリックできないようにボタンを無効にする)1: private void submitButton_Click_1(object sender, RoutedEventArgs e)
2: {
3: submitButton.IsEnabled = false;
4: employeeDataSource.SubmitChanges();
5:
6: }
- 「SubmitChanges」イベントハンドラーに以下の処理を記述する。
(更新処理が正常に完了したか確認し、「Submit」ボタンを有効に戻す。1: private void employeeDataSource_SubmittedChanges_1(
2: object sender, SubmittedChangesEventArgs e)
3: {
4: if (e.HasError)
5: {
6: MessageBox.Show(string.Format(
7: "Changes were not saved: {0}", e.Error.Message));
8: e.MarkErrorAsHandled();
9: }
10: submitButton.IsEnabled = true;
11: }
- アプリケーションを実行し「EmployeeList」を開く
- 一覧からデータを選択し、「DataFrom」の右上隅にある鉛筆のアイコンをクリックして編集モードにする
- データを修正する
(複数件修正する時はこれを繰り返す) - 「Submit」ボタンをクリックしデータベースに変更を更新する
エンティティクラスにカスタムメソッドを追加する
「EmployeeList」に表示している「Employee」テーブルは、それに対応するエンティティクラスが自動的に作られている。
今回の例で行くと、サーバー側の自動生成ソースである「RIA1.Web.g.cs」に存在している。自動生成ソースなので直接的に触るものでは無いのだが、間接的にここにメソッドを追加できるようだ。
詳しく調べたわけではないが、サービスクラス(ここではMyService.cs)の中に「Employees」クラスを引数とするメソッドを作ると「Employees」クラス自身のメソッドとして生成してくれる。
- 「RIA1.Web」プロジェクトの「MyService.cs」を開き、以下のメソッドを追加する。
1: public void 敬称Add(Employees current)
2: {
3: // Start custom workflow here
4: this.ObjectContext.Employees.AttachAsModified(current);
5: if (current.LastName.EndsWith("様"))
6: {
7: current.LastName = current.LastName.Substring(0,current.LastName.Length - 1);
8: }
9: else
10: {
11: current.LastName += "様";
12: }
13: }
- 一旦ビルドして自動生成を走らせる
- 「RIA1」クライアントプロジェクトの「EmployeeList.xaml」を開く
- 「Submit」ボタンの隣に以下のボタンを追加する
1: <Button x:Name="敬称Add" Width="75" Height="23"
2: Content="敬称追加" Click="敬称Add_Click_1" />
- コードビハインドに以下のイベントハンドラーを追加する
1: private void 敬称Add_Click_1(object sender, RoutedEventArgs e)
2: {
3: Employees emp = (Employees)(dataGrid1.SelectedItem);
4: emp.敬称Add();
5: employeeDataSource.SubmitChanges();
6: }
- アプリケーションを実行し、「Employee List」を開く
- リストから変更対象の行を選択し、「敬称追加」ボタンをクリックする
- 「LastName」に「様」が追加されるのを確認する
- 追加するメソッド自体に引数が必要な場合は、第1引数のエンティティクラスに続けて指定すれば良さそうである。
1: public void 敬称Add(Employees current,string surfix)
2: {
3: // Start custom workflow here
4: this.ObjectContext.Employees.AttachAsModified(current);
5: if (current.LastName.EndsWith(surfix))
6: {
7: current.LastName = current.LastName.Substring(0,current.LastName.Length - 1);
8: }
9: else
10: {
11: current.LastName += surfix;
12: }
13: }
1: private void 敬称Add_Click_1(object sender, RoutedEventArgs e)
2: {
3: Employees emp = (Employees)(dataGrid1.SelectedItem);
4: emp.敬称Add("殿");
5: employeeDataSource.SubmitChanges();
6: }