アーカイブ

Archive for the ‘WCF RIA Services’ Category

WCF RIA Services:データ追加の補足

前回のエントリの最後にこんなことを書いてます。

「データを追加したあと、最終ページを表示するようにプログラムを記述しているつもりですが、どうも本来ページが増えるはずの動作のときに最終ページの1つ前のページしか表示されない、という状態になっています。 」

非同期処理に慣れている人ならすぐ原因に気づいたんでしょうねぇ。
データの読み直し(personDomainDataSource.Load();)が非同期で行われるため、正しい最終ページの値を持ってこれていないことが問題でした。
ここを修正します。

まずは修正前のプログラム

————————————————————————————————

private void button4_Click(object sender, RoutedEventArgs e)
{
    // 子ウィンドウのインスタンスを生成
    ChildWindow1 w = new ChildWindow1();
    // 子ウィンドウを閉じたときにデータを読み直し、最終ページを表示するよう設定
    w.Closed += (s, ev) =>
    {
        if (w.DialogResult == true)
        {
            personDomainDataSource.Load();
            dataPager1.PageIndex = dataPager1.PageCount;
        }
    };
    // 子ウィンドウの表示
    w.Show();
}

————————————————————————————————

これを次のように変えます。

————————————————————————————————

private void button4_Click(object sender, RoutedEventArgs e)
{
    // 子ウィンドウのインスタンスを生成
    ChildWindow1 w = new ChildWindow1();
    // 子ウィンドウを閉じたときの処理を記述
    w.Closed += (s, ev) =>
    {
        if (w.DialogResult == true)
        {
            // データを読み直したときのイベントを追加
            personDomainDataSource.LoadedData += new EventHandler<LoadedDataEventArgs>(dataAdded);
            personDomainDataSource.Load();
        }
    };
    // 子ウィンドウの表示
    w.Show();
}

private void dataAdded(object sender, LoadedDataEventArgs e)
{
    // 最終ページに移動
    dataPager1.PageIndex = dataPager1.PageCount;
    // 子ウィンドウを閉じたとき以外に実行されないようデータを読み直したときのイベントを削除
    personDomainDataSource.LoadedData -= dataAdded;
}

————————————————————————————————

このようにデータの読み直しが終了した時点に動作するイベントを追加し、その中で最終ページに移動することで正しく動作するようになります。

さて、本編はここまで。

このプログラムを書いてて思ったんですが、非同期処理がベースとなることで、イベントに関する理解は必須になってしまいますね。
また、上記のプログラム中では説明もせずにさらっと流してますが、匿名デリゲートをラムダ式に置き換えて簡単な記述にしている、なんてことをしてます。

自分でプログラムを書く分には、IntelliSenseが手伝ってくれることもあって難しいところはそうないんですが、このあたりを他人に説明しようとすると、、、大変だよなぁ。。。

広告
カテゴリー:Silverlight, WCF RIA Services

WCF RIA Services:データ追加

WCF RIA Services  「らしい」データの追加方法のサンプルが見つけられず、情報をまとめるのに時間がかかってしまいました。
とりあえず自分なりに納得いく方法として下記にまとめてみます。
何点かまだ気に入らないところがあるんですけどね。。。

Silverlight側にSilverlight子ウィンドウを追加します。

 image

データソースウィンドウでPersonを詳細表示の状態にします。
idは自動的に追加される項目なので、下記のように[なし]の状態にします。

 image_3

この状態で子ウィンドウにPersonデータソースをドラッグ アンド ドロップします。
必要な入力項目が追加されます。

 image_4

personDomainDataSourceのAutoLoadプロパティをfalseにします。

子ウィンドウのコードを以下のように修正します。
修正箇所はコメントを入れているあたりです。

————————————————————————————————

using System.Windows;
using System.Windows.Controls;
using SilverlightApplication10.Web;

namespace SilverlightApplication10
{
    public partial class ChildWindow1 : ChildWindow
    {
        // データ保持のための変数
        Person p = null;

        public ChildWindow1()
        {
            InitializeComponent();
            // 新規Personインスタンスの作成/追加と入力欄への関連づけ
            p = new Person();
            ((DomainService1)personDomainDataSource.DomainContext).Persons.Add(p);
            grid1.DataContext = p;
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            // データの検証および追加
            personDomainDataSource.SubmitChanges();
            if (!p.HasValidationErrors)
                this.DialogResult = true;
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }

        private void personDomainDataSource_LoadedData(object sender, LoadedDataEventArgs e)
        {

            if (e.HasError)
            {
                System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
                e.MarkErrorAsHandled();
            }
        }
    }
}

————————————————————————————————

MainPage.xamlに「追加」ボタンを追加します。

 image_5

このボタンは「再取得」ボタンと同じように、データを更新しているときは使えないようにしたいと思います。
そのため、IsEnabledプロパティにソースとして「ElementName」-「PersonDomainDataSource」、パスとして「CanLoad」を設定します。
「追加」ボタンをダブルクリックし、以下のプログラムを記述します。

————————————————————————————————

private void button4_Click(object sender, RoutedEventArgs e)
{
    // 子ウィンドウのインスタンスを生成
    ChildWindow1 w = new ChildWindow1();
    // 子ウィンドウを閉じたときにデータを読み直し、最終ページを表示するよう設定
    w.Closed += (s, ev) =>
    {
        if (w.DialogResult == true)
        {
            personDomainDataSource.Load();
            dataPager1.PageIndex = dataPager1.PageCount;
        }
    };
    // 子ウィンドウの表示
    w.Show();
}

————————————————————————————————

なお、PersonCheck.shared.csの内容を以下のように変更しておきます。
mailの値が空文字でもnullでもエラーになるようにしています。
また、エラー時のエラー設定項目としてageとmailの項目を指定しています。

————————————————————————————————

using System.ComponentModel.DataAnnotations;

namespace SilverlightApplication10.Web
{
    public class PersonCheck
    {
        public static ValidationResult CheckAge_Mail(Person p)
        {
            if (p.age >= 20 && (p.mail == string.Empty || p.mail == null))
                return new ValidationResult("成人はメールアドレス必須です", new string[] {"age", "mail"});
            return ValidationResult.Success;
        }

    }
}

————————————————————————————————

これで動作のチェックができるようになりますが、気をつけないといけないのは、通常のデバッグ開始を実行してしまうと、検証のチェックにひっかかったときにエラーが発生したことを検知して動作が止まってしまうことです。
必ず「デバッグなしで開始」を実行します。

下図のように追加データの検証が行われます。

 image_6

 image_7

 image_8

なお、上記の動作確認を行っていると、どうもクライアント側だけで検証できずにサーバ上での検証が走っているように思われます。
逆に考えると、サーバ上で検証のプログラムを記述すると、特になにもしなくてもクライアント側にサーバ上で発生した検証エラーを伝えられることがわかります。

検証エラーがでなければ、データを正常に追加することができます。

 image_9

データを追加したあと、最終ページを表示するようにプログラムを記述しているつもりですが、どうも本来ページが増えるはずの動作のときに最終ページの1つ前のページしか表示されない、という状態になっています。
その点解消する方法がないか、調査は続けたいと思います。

カテゴリー:Silverlight, WCF RIA Services

WCF RIA Services:データ検証

WCF RIA Servicesでは、必須項目/範囲チェック/正規表現チェックといったデータの検証はメタデータクラスに属性を設定するだけで可能になります。
DomainServiceクラスを追加した際、同時に作成されるメタデータクラスに下記のように属性を設定してみます。

———————————————————————————————–
[MetadataTypeAttribute(typeof(Person.PersonMetadata))]
public partial class Person
{

    internal sealed class PersonMetadata
    {

        private PersonMetadata()
        {
        }

        [Display(Name = "年齢", Order = 2)]
        [Range(0, 120, ErrorMessage = "年齢に誤りがあります")]
        public int age { get; set; }

        [Display(Name="ユーザID", Order=0)]
        public int id { get; set; }

        [Display(Name = "メールアドレス", Order = 3)]
        [RegularExpression(@"\w+([-+.’]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage = "正しいメールアドレスを入力してください")]
        public string mail { get; set; }

        [Display(Name = "備考", Order = 4)]
        public string memo { get; set; }

        [Display(Name = "名前", Order = 1)]
        [Required(ErrorMessage = "名前は必須項目です")]
        public string name { get; set; }
    }
}
———————————————————————————————–

この変更を行って実行すると、下記のようにデータの検証が行われます。

 image

このとき「更新」ボタンが有効になっています。
で、このまま押してみると、、、画面が真っ白になってエラーとなってしまいます。
ただ、これはデバッグ実行をしているときのエラー制御が通常実行時と異なるために発生する現象のようです。
Ctrl+F5 を押して、デバッグなしで実行すると、「更新」ボタンを押しても何も起きないことを確認することができます。

さて、カスタムのデータ検証も追加してみましょう。そのためには、まずカスタム検証のメソッドを追加するクラスを作成します。
PersonCheck.shared.csというようにクラス名にsharedという名前を追加したファイルを作成します。
このようにファイル名にsharedをつけることで、クライアント側に自動的にこのファイルが追加されるようになります。
ファイルの中は下記のように記述します。

———————————————————————————————–
using System.ComponentModel.DataAnnotations;

namespace SilverlightApplication10.Web
{
    public class PersonCheck
    {
        public static ValidationResult CheckAge_Mail(Person p)
        {
            if (p.age >= 20 && p.mail == string.Empty)
                return new ValidationResult("成人はメールアドレス必須です");
            return ValidationResult.Success;
        }

    }
}
———————————————————————————————–

メタデータクラスのPersonMetadataクラスにこのカスタム検証を使用するよう、以下の属性を追加します。

[MetadataTypeAttribute(typeof(Person.PersonMetadata))]

これで、年齢が20歳以上の場合でメールアドレスの入力がない場合にエラーが表示されるようになります。

 image_3

カテゴリー:Silverlight, WCF RIA Services

WCF RIA Services:データ更新/キャンセル/再取得

WCF RIA Servicesで作成したサンプルはデフォルトでDataGird上でのデータの修正が可能です。
ただ、DataPagerを利用してページングを行っている場合、データを修正すると下図のようにページ番号がグレーアウトし、ページの移動ができなくなります。

 image

この状態を解消し、ページの移動を可能にするには、データの修正をキャンセルするか、データベースにたいして修正内容を更新する必要があります。

キャンセルボタンと更新ボタンを追加します。

image_3

キャンセルボタンのCommandプロパティの下図のボタンをクリックし、「データバインドの適用」を選択します。

image_4 

表示されるダイアログ上でソースとして「ElementName」-「PersonDomainDataSource」を選択します。

image_5

パスとして「RejectChangesCommand」を選択します。

image_6

更新ボタンは同様にCommandプロパティにソースとして「ElementName」-「PersonDomainDataSource」、パスとして「SubmitChangesCommand」を設定します。

この状態でデバッグを実行すると、アプリケーションを開始した時点ではキャンセルボタン、更新ボタンともに実行できない状態になっています。

image_7

データを修正すると、キャンセルボタン、更新ボタンが有効になります。

image_8

キャンセルボタンを押すと、修正内容が破棄され、ページの移動が可能になります。
更新ボタンを押すと、修正内容がデータベースに反映され、ページの移動が可能になります。

これで普通にデータ更新が可能になるのですが、いろいろと試していると他からデータが更新された場合、その内容が反映されないことがわかりました。

アプリケーションを実行し、Silverlight上からuser1のデータを修正します。

image_9

更新ボタンを押す前にデータベース上のuser2のデータを修正します。

image_10

この状態で更新ボタンを押すと、user1のデータは正しく書き換えられますが、user2の変更はSilverlight側には適用されません。

image_11

image_12 

データベース上の最新状態をSilverlight側に反映させるには、データを再取得するしか方法がなさそうです。
再取得ボタンを追加し、Commandプロパティにソースとして「ElementName」-「PersonDomainDataSource」、パスとして「LoadCommand」を設定します。

image_13

これで大丈夫、、、と思ったのですが、実行してもSilverlight側にはデータベースで直接修正した内容がやっぱり反映されません。
いろいろ調べてみると、どうやらLoadBehaviorを変更する必要がありそうです。
この変更のタイミングとして、PersonDomainDataSourceのLoad時(LodingDataイベント)が使えるようです。

まずドキュメントアウトラインからDomainDataSourceを選択します。

image_14

PersonDomainDataSourceのイベントからLoadingDataをダブルクリックし、イベント発生時のメソッドを追加します。

image_15

personDomainDataSource_LoadingDataメソッド内で、LoadBehaviorの設定を変更します。

image_16 

この設定の変更をすることで、はじめてSilverlight側でデータを再取得した際にデータベース上の修正内容が反映されるようになります。

カテゴリー:Silverlight, WCF RIA Services

WCF RIA Services:レイアウトの設定

WCF RIA Servicesというより、VS2010(VWD2010)の機能の説明になってますが。

まず、MainPageに追加しているDataGridのColumnsプロパティのボタンをクリックし、Columnsのコレクションエディターを表示します。
ここですべてのDataGridTextColumnのWidthプロパティをAutoに設定します。

 1_Columns

次にDataGridのWidthプロパティとHeightプロパティをAutoに設定します。

 2_DataGrid

画面上の表示領域外側の薄く青い部分をクリックすることでGridを縦に分割します。
このとき、DataGridの下端に合わせるようにします。

3_Grid

その後、マウスをGridの高さの表示のあたりにポイントすることで設定値のガイドが表示されるので、ここで上部のGirdの列の高さをAutoに設定します。

 4_Grid

同様にして、Gridを横に分割し、幅をAutoに設定します。

3_Grid_3

DataPagerのWidthをAutoに、HorizontalAlignmentをStretchに、Marginを0に設定します。

 5_DataPager

ここでデバッグ実行すると、DataGridとDataPagerがきれいに揃って表示されることが確認できます。

 6_1 7_3

カテゴリー:Silverlight, WCF RIA Services

WCF RIA Services:ページングの追加

DataGridにページングを追加するのはとても簡単です。
まず、ツールボックスからDataPagerをドラッグ アンド ドロップして追加します。

DataPager

次にデータソースからPersonをDataPagerに対してドラッグ アンド ドロップします。

DataPagerとデータソースの関連付け

DataPagerのPageSizeプロパティを適当な値に設定します。
ここでは2にしてみました。
この状態で実行してみましょう。

実行結果

PageSizeで指定したとおり、2件のデータだけが表示されます。
ただ、次のページを表示させようとすると、エラーが表示されます。

エラー

どうもGetPersonメソッドに問題があるようです。
DomainService1クラスでデータを取り出すGetPersonメソッドを確認してみます。

    public IQueryable<Person> GetPerson()
    {
        return this.ObjectContext.Person;
    }

このとおりOrderByの指定がないので、エラーで要求されているようにこの指定を追加します。
id順に表示するようにしておきます。

    public IQueryable<Person> GetPerson()
    {
        return this.ObjectContext.Person.OrderBy(p => p.id);
    }

再度実行してみましょう。
今度はページの移動が可能です。

3ページ目を表示

ページングに関しては、実はこれだけの作業しかありません。
ただ、このページング、ページが遷移した際に必要なデータをとってきているのか、それともあらかじめすべてのデータをクライアント側に取り出していてそのデータをもとにページングを行っているのか、気になるところだと思います。
これを調べてみましょう。

DomainService1クラスにQueryメソッドを追加します。
QueryメソッドはDomainService1クラスの親のLinqToEntitiesDomainServiceクラスの親のDomainServiceクラスにviretualなメソッドとして定義されています。
このため、DomainService1クラス内でoverrideと入力し、スペースを押して表示されるインテリセンスからQueryメソッドを選択すれば、自動的に継承元のQueryメソッドを呼び出すための適切なソースが挿入されます。
このQueryメソッドにブレークポイントを設定して実行します。

初期表示時のクエリ

初期表示の場合、Queryメソッドには"Person[].Take(2)"というクエリが渡されていることがわかります。
これは最初の2件だけを取り出すLINQのクエリになります。
これが例えば3ページ目を表示する場合だと次のように変わります

3ページ目表示時のクエリ

"Person[].Skip(4).Take(2)"というのは最初から4つのデータ(2ページ分)をとばして、その次から2件取り出す、というクエリです。
OrderByを追加する前のエラーはこのSkipを実行しようとして発生したエラーでした。

この結果から、ページングではLINQを利用し、必要なデータだけを毎回取り出していることが確認できます。

なお、DataGridでは項目名をクリックすることでその項目での並べ替えが可能です。

年齢逆順ソート

こんな場合にどのようなクエリが実行されているのか、とか確認してみるのも面白いと思います。

カテゴリー:Silverlight, WCF RIA Services

WCF RIA Services:項目の表示名と表示順の変更

とりあえず動かしてみたWCF RIA Servicesですが、このときの実行結果をみると項目の並び順がアルファベット順になっています。

デフォルト実行

ウィザードで生成されたDomainService1.metadata.csファイルの中を見ると、この時点でプロパティがアルファベット順となっていることがわかります。

———————————————————————-

    [MetadataTypeAttribute(typeof(Person.PersonMetadata))]
    public partial class Person
    {
        internal sealed class PersonMetadata
        {
            private PersonMetadata()
            {
            }

            public int age { get; set; }
            public int id { get; set; }
            public string mail { get; set; }
            public string memo { get; set; }
            public string name { get; set; }
        }
    }

———————————————————————-

この並び順を変更するためにDisplay属性を利用することができます。
Display属性では表示する項目名の変更もできますので、ついでにその設定もしてしまいましょう。
DomainService1.metadata.csファイルの中を以下のように書き換えます。

———————————————————————-

    [MetadataTypeAttribute(typeof(Person.PersonMetadata))]
    public partial class Person
    {
        internal sealed class PersonMetadata
        {
            private PersonMetadata()
            {
            }

            [Display(Name = "年齢", Order = 2)]
            public int age { get; set; }

            [Display(Name="ユーザID", Order=0)]
            public int id { get; set; }

            [Display(Name = "メールアドレス", Order = 3)]
            public string mail { get; set; }

            [Display(Name = "備考", Order = 4)]
            public string memo { get; set; }

            [Display(Name = "名前", Order = 1)]
            public string name { get; set; }
        }
    }

———————————————————————-

ビルドを実行した後で、すでに追加していたDataGridを削除し、もう一度データソースからPersonをドラッグ アンド ドロップして新たにDataGridを追加します。

DataGrid変更

これで表示される項目名と項目の順序が変更されました。
実際に実行して、結果を確認してみましょう。

項目変更後

Silverlightプロジェクト側で自動生成されているプログラムの中に、Webプロジェクト側で追加した上記のDisplay属性が自動的に追加されていることを確認しておきましょう。

カテゴリー:Silverlight, WCF RIA Services