Silverlight の入出力
|

|
お菓子検索のサンプルを作りながら、ブラウザの内側のSilverlight アプリケーションがどう外界とやり取りするのかをまとめて見ました。
お菓子検索のサンプルプログラムは、左図をクリックするとポップアップウインドウを開いて実行します。
お菓子の虜 Web API の検索機能を使っています。
|
1.Silverlight アプリケーションの実行と入出力
Silverlight アプリケーションは、ブラウザを実行しているクライアント側で実行されるプログラムです。
サーバーの動作は、通常の http サーバの動作です。http サーバは、要求されたファイルをブラウザへ送ります。Web APIのように、サーバー上でプログラムが、XMLなどのストリームを作って返す動作もしますが、要求も応答もファイルのときの動作を模倣したものです。
Silverlight アプリケーションは、XAPファイルとそれをホストするHTMLファイルからできています。サーバーは、これを格納していて、ブラウザからの要求に応じて送信します。
サーバーはファイルの内容には関与しないので、サーバの OS や CPU は、まったく動作に関係しません。
ブラウザ側で実行している Silverlight プログラムは、ブラウザ側のパソコンに被害を与えないように、分離された環境で実行されます。どのように「分離」されているかと言うと、ファイルの入出力に制限が付けられています。
機能的には、アップロードもダウンロードもできるのですが、ブラウザ側で、ユーザが操作して選んだり、指定したファイル以外はアクセスできないようになっています。プログラムが自律的に、ファイルをアクセスすることはできません。
また、ファイルが他のサーバにある場合、そのサーバに明示的な許可が設定されていないとアクセスしないような仕組みがあります。
これらを前提にして考えると、Silverlight アプリケーションの入出力は、4つに分けるのが理解し易そうです。

2.展開フォルダの入出力
正式な名称は分かりませんが、XAPにパッケージングされているファイルは、展開して使われるはずです。
XAP ファイルには、プログラム(DLLなど)が格納されていますが、プログラムで使うためのデータを含めることができます。ここでは、プログラムの作成時に追加したデータファイルのアクセス方法を挙げます。
2.1.埋め込まれたリソース
Visual Studio のプロジェクトに追加したファイルのプロパティの「ビルドアクション」に「埋め込まれたリソース」があります。これを指定すると、DLL中に埋め込まれます。
例では、イメージを読み込んで、Imageクラスのソースにして表示しています。
イメージ・ファイル名の前に、ファイルを含めたプロジェクト(アセンブリ)のネームスペースが付きます。
- Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("namespace.logo.png");
- BitmapImage bi = new BitmapImage();
- bi.SetSource(stream);
- image1.Source = bi;
2.2.ビルドアクションで「Resource」を指定したファイル
「埋め込まれたリソース」にしなくても、同じようにDLL中に含まれるようです 。
「埋め込まれたリソース」と「Resource」の使い方を「Silverlightのマニフェスト・リソース」に書きます。
2.3.コンテンツ
Visual Studio のプロジェクトに追加したファイルのプロパティの「ビルドアクション」に「コンテンツ」があります。これを指定すると、XAPファイルの中に、独立したファイルのままパッケージングされます。
例では、イメージを読み込んで、Imageクラスのソースにして表示しています。
- System.Windows.Resources.StreamResourceInfo sri = App.GetResourceStream(new Uri("logo.png",UriKind.Relative));
- BitmapImage bi = new BitmapImage();
- bi.SetSource(sri.Stream);
- image1.Source = bi;
2.4.XAPの参照
XmlXapResolver で、参照できるようです。
- XmlXapResolver xxr = new XmlXapResolver();
- object obj = xxr.GetEntity(new Uri("logo.png", UriKind.Relative), null, typeof(Stream));
- Stream stream = obj as Stream;
- BitmapImage bi = new BitmapImage();
- bi.SetSource(stream);
- image1.Source = bi;
3.パソコン内のファイルのアクセス
ブラウザを実行しているパソコンのファイルには、OpenFileDialog、SaveFileDialog でのみアクセスできます。
このダイアログは、プログラムでいつでも開けるわけではなく、ボタンをユーザが押したときの処理の中など、操作したタイミングでだけ開けるようになっています。
ファイルのダウンロード、アップロードに対応した処理を記述することが想定されているものと思います。
その他のファイル関連のクラスは隠されておらず、そのままプログラミングできるようになっています。実行時にエラーになります。
4.分離ストレージ
2つの目的があるようです。
- 一時ファイルとして
- 永続ストレージとして
作成できるファイルのサイズには制限があり、後者が主要な目的のようです。
一度作ったファイルは消さない限り残るので、アプリケーションで一時ファイルに使うなら配慮する必要があります。
IsolatedStorageには、Site と Application の2種類があります。
ブラウザ側に Silverlight アプリケーション空間が作られると言ったイメージのようです。
このストレージは、Silverlight アプリケーション以外からは隠されています。
エクスプローラ等で表示することはできないようです。
4.1.分離ストレージのサンプル

左図をクリックすると、ポップアップウインドウでプログラムが実行されます。
このプログラムは、Site と Application のファイルを列挙します。
また、表示の後で、Site と Application に、1つずつファイルを作っています。
最初に起動すると、ファイルがない状態で表示されます。
ポップアップウインドウを閉じて、再度、左の図をクリックして開きなおします。
すると、最初に開いたときに作られたファイルが表示されます。
その後、ページの再表示やブラウザの「更新」の度にファイルが増えていきます。
このストレージは永続的なものだと言うことです。
(ここで作られるファイルの削除方法は後の方で記述します。)
この例のプログラムは、以下の通りです。
- <UserControl x:Class="sla_isolatedStorage1.Page"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Width="400" Height="300">
- <Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
- <StackPanel>
- <Image x:Name="image1" Width="32" Height="32"/>
- <ListBox x:Name="listBox1"/>
- </StackPanel>
- </Grid>
- </UserControl>
- private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
- {
- //これは、サーバーからの動的ダウンロードになる
- BitmapImage bi = new BitmapImage(new Uri("cherry.png", UriKind.Relative));
- image1.Source = bi;
- //分離ストレージ(Application)のファイルをリストボックスに列挙する
- IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
- string[] fns = isf.GetFileNames();
- listBox1.Items.Add("Application のファイル");
- foreach (string fn in fns)
- listBox1.Items.Add(fn);
- //分離ストレージ(Application)に連番のファイル名のファイルを作る
- isf.CreateFile(fns.Length.ToString() + ".app");
- //分離ストレージ(Site)のファイルをリストボックスに列挙する
- isf = IsolatedStorageFile.GetUserStoreForSite();
- fns = isf.GetFileNames();
- listBox1.Items.Add("Site のファイル");
- foreach (string fn in fns)
- listBox1.Items.Add(fn);
- //分離ストレージ(Site)に連番のファイル名のファイルを作る
- isf.CreateFile(fns.Length.ToString() + ".site");
- }
4.2.IsolatedStorageの削除
IsolatedStorageにファイルのない状態に戻すには、ブラウザで Silverright アプリケーションを実行した状態で、マウス右ボタンをします。「Silverlight」と表示されます。これをクリックしてダイアログを開きます。
「アプリケーション記憶領域」タブを選ぶと、ホスト単位に削除ができます。
この記憶領域は永続的ですが以下が考慮点だと思います。
- 空から始まる。
- 永続的だが、ユーザがいつでも初期化できる。
4.3.IsolatedStorageSettings
(key, value)のペアでプログラムのセッティングデータなどを永続的に保持できます
5.ウエブ空間上のファイル
5.1.ドメイン間ポリシーファイル
Silverlight アプリケーションのプログラムから、http サーバ にアクセスするためには、サーバが積極的にSilverlightアプリケーションの接続を許可している必要があるようです。
これは、サーバが無制限に接続できるようにしていても接続できないと言うことです。多くの公開Web APIと接続できないのは、提供者が規制しているからではなく、Silverlightが自主規制しているためです。
- http://ホスト名/crossdomain.xml
Flash(Active Script)のプログラムで決められていることに準じています。
アクセスしようとするホストに、上記のファイルがある場合、その設定によってアクセスが制限されます。
ファイルがない場合は、接続しません。
- http://ホスト名/clientaccesspolicy.xml
1.と同様の目的で使われます。1.より先に調べられるようです。
- ポリシーサーバー
これは、マイクロフォンを使ったアプリケーションを作った時に出てきました。
ActiveScriptのマイクの機能と、Silverlight を組み合わせたのですが、音声データはRTMPプロトコルで出力されます。RTMPサーバーを疑似的に行うサーバを作って折り返すのにポートを開く必要がありました。ポートを開くには、ポリシーサーバーが許可するポートの番号を返す必要がありました。
5.2.ホームの概念
URL でファイルを取得する場合、場所は4種類考えられます。
- ブラウザがホームと認識している場所
TestPage.html など、XAPをホストしたページを開くと、ブラウザはその位置をホームとして認識して、相対的なURLの解決に使います。
- ブラウザがホームと認識している場所があるHOST上のホーム以外
- ブラウザを実行しているパソコンの http サーバ下のファイル
- その他の場所
5.2.1.ブラウザがホームと認識している場所
相対的なURL記述は、ブラウザと同様に解釈されます。
デバッグじには、bin/Debug に、TestPage.html が作られ、ここがホームです。
もし、Webサイトにホストした場合は、TestPage.htmlはサイトのルートに作られ、ClientBinフォルダにXAPファイルが作られます。
この場合も、ホームは html のある方です。
相対的なURL記述ができる以外にホームが特別かどうかは良くわかりません。
5.2.2.ブラウザがホームと認識している場所があるHOST上のホーム以外
TestPage.html や XAPの置かれているHOSTが特別な扱いかどうかは分かりません。
いまのところ区別なく同じ方法でアクセスしています。
5.2.3.ブラウザを実行しているパソコンの http サーバ下のファイル
デバッグ時には、デバッグ用のhttpサーバが起動されます。同じパソコン上でブラウザとサーバーが動作しています。
このパソコンで、IISサーバーなど他のhttpサーバが動いている場合を考えます。テストサーバに繋がっているデバッグ中の Silverlight アプリケーションから、http://localhost/sitemap.xml などとすると、クロスドメイン状態になります。
これも、形態は独特ですが、特に区別する必要はないようです。
5.2.4.その他の場所
これは、ドメイン間ポリシーがサーバーにあれば、普通にアクセスできます。
5.3.アクセス方法
以下の方法を試してみました。
- BitmapImage で URL を開く
- HttpWebRequestでURLを開く(テキスト)
- HttpWebRequestでURLを開く(イメージ
- WebClientでURLを開く(テキスト)
- WebClientでURLを開く(イメージ)
5.3.1.BitmapImage で URL を開く
- BitmapImage bi = new BitmapImage(new Uri("http://mikeo410.wsp1.net/logo.png", UriKind.Absolute));
- image1.Source = bi;
5.3.2.HttpWebRequestでURLを開く(テキスト)
URL を指定して、XMLファイルを取得しTextBlockに表示します。
応答はコールバク関数で処理します。
- string text_url = "http://mikeo410.wsp1.net/sitemap.xml";
- SynchronizationContext syncContext;
- private void Button_Click_2(object sender, RoutedEventArgs e)
- {
- textBlock1.Text = "";
- syncContext = SynchronizationContext.Current;
- HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(text_url);
- req.BeginGetResponse(new AsyncCallback(c2BeginGetResponseCallback), req);
- }
受信完了でコールバックされます。コールバック関数では、表示ができないので、エラーメッセージは、error_string に入れて置きます。
最後の syncContext.Post(c2ExtractResponse, resp); は、UIのタイミングで、c2ExtractResponse()の呼び出しを登録するものです。
- string error_string;
- void c2BeginGetResponseCallback(IAsyncResult ar)
- {
- HttpWebRequest req = ar.AsyncState as HttpWebRequest;
- WebResponse resp = null;
- try
- {
- resp = req.EndGetResponse(ar);
- }
- catch (WebException we)
- {
- error_string = we.Status.ToString();
- }
- catch (SecurityException se)
- {
- error_string = se.Message;
- if (error_string == "")
- error_string = se.InnerException.Message;
- }
- syncContext.Post(c2ExtractResponse, resp);
- }
コールバック関数の続きの処理を、UIスレッドで行います。
この例では、ストリームを取得して、ReadEnd()ですべての文字を読み出しています。
- void c2ExtractResponse(object state)
- {
- HttpWebResponse resp = state as HttpWebResponse;
- if ((resp != null) && (resp.StatusCode == HttpStatusCode.OK))
- {
- StreamReader sr = new StreamReader(resp.GetResponseStream());
- textBlock1.Text = resp.StatusCode.ToString()
- + " --- "
- + sr.ReadToEnd();
- }
- else
- textBlock1.Text = "ERROR: " + error_string;
- }
5.3.3.HttpWebRequestでURLを開く(イメージ)
バイナリもテキストと同様です。テキストの例の最後の関数を、イメージファイルを表示するようにしたものです。
- void c3ExtractResponse(object state)
- {
- HttpWebResponse resp = state as HttpWebResponse;
- if ((resp != null) && (resp.StatusCode == HttpStatusCode.OK))
- {
- StreamReader sr = new StreamReader(resp.GetResponseStream());
- BitmapImage bi = new BitmapImage();
- bi.SetSource(sr.BaseStream);
- image1.Source = bi;
- textBlock1.Text = "OK:" + resp.StatusCode.ToString();
- }
- else
- textBlock1.Text = "EROR: " + error_string;
- }
5.3.4.WebClientでURLを開く(テキスト)
HttpWebRequest の例と同じことを、WebClient で行って見ます。
この場合は、2段階で、いずれもUIスレッドで実行されます。
- string text_url = "http://mikeo410.wsp1.net/sitemap.xml";
- private void Button_Click_4(object sender, RoutedEventArgs e)
- {
- textBlock1.Text = "";
- WebClient wc = new WebClient();
- wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
- wc.DownloadStringAsync(new Uri(text_url));
- }
応答を受け取る関数は、UIのタイミングで実行され、e.Result の型も応答に合わせて、string になっています。(イメージの受信では、Stream型になります。)
- void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
- {
- if (e.Error == null)
- textBlock1.Text = e.Result;
- else
- textBlock1.Text = e.Error.ToString();
- }
5.3.5.WebClientでURLを開く(イメージ)
テキストと同じ手順でイメージをダウンロードします。
- string image_url = "http://mikeo410.wsp1.net/logo.png";
- private void Button_Click_5(object sender, RoutedEventArgs e)
- {
- textBlock1.Text = "";
- WebClient wc = new WebClient();
- wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
- wc.OpenReadAsync(new Uri(image_url));
- }
応答は、Stream型でした。
- void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
- {
- if (e.Error == null)
- {
- BitmapImage bi = new BitmapImage();
- bi.SetSource(e.Result);
- image1.Source = bi;
- }
- else
- textBlock1.Text = e.Error.ToString();
- }
|