Silverlight2 + ADO .NET Data Services 動いた

Data Servicesでデータを取得しようとすると謎の例外が出てしまっていたのですが、原因が分かって動きました。
エラーメッセージは「callOpen の呼び出しに失敗しました。」「場所 System.Data.Services.Http.ScriptXmlHttpRequest.Open」という感じでした。
検索するとクロスサイト呼び出しがどうのとか出てきますが、ローカルでデバッグしてるのにクロスサイトも何もないだろうと無視したのがよくありませんでした。実際にはSilverlightをスタートアッププロジェクトに指定して実行すると、自動的にホストするページが生成され、その上で実行されるので、Data Serviceにアクセスするとクロスサイト呼び出しになってしまうのです。



Silverlightをスタートアッププロジェクトにして実行すると
デバッグしようとしているSilverligntプロジェクトは、Webサービスを使用しています。サービスを含むWebのコンテキストでプロジェクトを実行しない限り、Webサービスの呼び出しは失敗します。」という警告が出ていたのですが、関係ないだろうと思って消してしまっていました。


プロジェクトを作るときに以下の手順が必要でした。

  • Silverlightのプロジェクト(とソリューション)を作る
  • Silverlightをホストするプロジェクトを作るか聞かれるので作る。(SilverlightApplication1とSilverlightApplication1.Webというプロジェクトができる。)
  • SilverlightApplication1.Webプロジェクトを右クリックして「項目の追加」-「新しい項目の追加」で「データ」-「ADO .NET Entity Data Model」を指定。
  • SilverlightApplication1.Webプロジェクトを右クリックして「項目の追加」-「新しい項目の追加」で、今度は「Web」-「ADO .NET Data Services」を指定。
  • WebDataServices1.svc.csに作ったEntityData名(NorthwindEntitiesとか)を書く。
  • WebDataServices1.svc.csに以下の修正もする。

config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.UseVerboseErrors = true;

  • WebDataServicesが動くかhttp://localhost:ポート番号/WebDataServices.svc/Customers などを指定してテスト。
  • 今度は、SilverlightApplication1の方を右クリックしてサービス参照の追加。「探索」ボタンを押してWebDataService1.svcを指定
  • Page1.xamlにButtonでも追加して、実行して動作チェック。
  • Page1.xamlにDataGridとButtonを追加して、以下のようなコードを書く。
  • 動作チェック




動いた!!
けど、右クリックしても「Silverlight」としか表示されない。


Silverlight2の感想ですが、ホイールでの操作や右クリックの操作が考慮されていないというのがダメダメです。どこかのブログで読みましたが、責任者の人が対応させるつもりはないというような発言をしているらしいです。通常のWebページより操作性を悪くしてどうするつもりなんでしょうか。非同期通信しかサポートしていないというのもどうなんでしょ。非同期を使うかどうかは我々が決めることであって、あんたらはどちらも使えるように作るのが筋だと思うんですがね。
Silverlight3はまだ試してませんが、責任者が変わらない限りあまり期待できないかも。


私の場合は特定の人向けのシステムがメインなのでMacintoshで動くかどうかということは重要ではありません。WPF Browser Applicationはあまり流行っていないようですが、こっちのほうが魅力的に見えてきました。


[Page1.xaml]


<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="600"
KeyDown="UserControl_KeyDown"
>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" x:Name="TextBox1" Text=""></TextBox>
<Button Grid.Row="0" Grid.Column="1" x:Name="Button1" Content="検索" Click="Button1_Click"></Button>
<data:DataGrid Grid.Row="1" Grid.Column="0" x:Name="DataGrid1"></data:DataGrid>
<Button Grid.Row="1" Grid.Column="1" x:Name="Button2" Content="更新" Click="Button2_Click"></Button>
</Grid>

</UserControl>


[Page1.xaml.cs]


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Data.Services.Client;
using System.Collections.ObjectModel;
using System.Windows.Browser;
using SilverlightApplication1.ServiceReference1;

namespace SilverlightApplication1 {
public partial class Page : UserControl {

DataServiceContext m_context = null;

public Page() {
InitializeComponent();
}

private void Button1_Click(object sender, RoutedEventArgs e) {
m_context = new DataServiceContext(new Uri("AdoDataService1.svc", UriKind.Relative));

string sSerach = this.TextBox1.Text;

DataServiceQuery query = null;
if (sSerach == "") {
query = m_context.CreateQuery("Customers");
} else {
query = (DataServiceQuery)(from c in m_context.CreateQuery("Customers") where c.ContactName.StartsWith(sSerach) select c);

}
query.BeginExecute(loadCategoryCallback, query);

this.Button1.Content = "検索中";
}

private void loadCategoryCallback(IAsyncResult result) {

DataServiceQuery query = (DataServiceQuery)result.AsyncState;
if (query == null) {
Console.WriteLine("query == null");
} else {
//いったんObservableCollectionに移す方法もある
List customers = query.EndExecute(result).ToList();
this.DataGrid1.ItemsSource = customers;

this.Button1.Content = "検索";
}

}

private void Button2_Click(object sender, RoutedEventArgs e) {

//TODO: 更新中ダイアログ的なものを出す
this.Button2.Content = "更新中\n 触ってはダメ";

List col = (List)this.DataGrid1.ItemsSource;

//全部の行を更新対象に設定
// 編集した行だけにするのはめんどくさそう
// RC0でCommitChangingイベントがなくなったのでTextBoxのLostFocusを使う
// http://www.onteorasoftware.net/post/Silverlight-2-RC0-DataGrid-CommittingEdit-work-around.aspx
foreach (Customers c in col) {
//Console.WriteLine("ContactName=" + c.ContactName);
m_context.UpdateObject(c);

}

m_context.BeginSaveChanges(SaveChangeCallback, m_context);
}
private void SaveChangeCallback(IAsyncResult result) {
m_context.EndSaveChanges(result);
MessageBox.Show("更新完了 SaveChangeCallback context=" + m_context);
this.Button2.Content = "更新";
}

private void UserControl_KeyDown(object sender, KeyEventArgs e) {
//PerfomClick が無いのでボタンの押し方が分からない
/*
if (e.Key == Key.F3) {
Button1_Click(this.Button1, null);
} else if (e.Key == Key.F8) {
Button2_Click(this.Button1, null);
}

if (Keyboard.Modifiers == ModifierKeys.Alt) {
if (e.Key == Key.S) {
Button1_Click(this.Button1, null);
}
}
*/

}

}
}