本記事の対象APIは既にサポートされていません。記事は参考程度にご利用ください。
はじめに
前回 予告したとおり、今回は実際にWebアプリケーションを作りながらApplication Based Storage APIを利用したユーザーデータへのアクセス方法をみていきます。本連載第14回 のWindows Live委任認証から内容が続いていますので併せて参照してください。
作成するWebアプリケーション
作成するWebアプリケーション(Webサイト)は、図1 のようなユーザーデータへのアクセスをGUI化した低機能なエクスプローラのような、単純なものを作ってみようと思います。
図1 作成するWebアプリケーション
アプリケーションは以下のように操作するものとします。
フォルダをクリックするとそのフォルダ直下のフォルダとファイルを表示
ファイルをクリックするとそのファイルを表示
Folder欄にフォルダ名を入力した状態でCreateボタンをクリックすると、チェックしているフォルダ直下にフォルダを作成
同様にRenameボタンをクリックした場合はチェックしているフォルダの名前を変更
File欄にファイルを入力した状態でUploadボタンをクリックすると、チェックしたフォルダ直下にそのファイルをアップロード
「Delete a Folder or File」ボタンをクリックするとチェックしているフォルダまたはファイルを削除
少し使いづらいものになっていますが、ユーザーデータに対して基本のアクセス操作が一通り可能です。また、最初に承認トークンを得るために承認要求ページへのリンクを示すものとします。
開発環境
今回の開発には、Visual Web Developer 2008 Express Edition を使用します。言語はVB.NETで記述しています。Application Based Storage APIの利用にはXMLの操作が必須となりVisual Basic 9.0の得意とするところですが、C#にも読み替えやすいように9.0の機能は使用せず、サーバ環境も考慮してASP.NET2.0で動作する内容になっています。Application Based Storage APIは言語に依存するものではないので、この環境・言語が必要というわけではありません。
また、開発にはWindows Liveサービスから開発PCへアクセスできる必要があり、URLドメインが必要になります。ダイナミックDNSの利用やhostsファイルを編集して対応するとよいでしょう。このことについては本連載の第11回 の「開発環境」にて少し述べています。
Webサイトの作成
さっそく作成に取りかかりましょう。まずは適当な名前でWebサイトを新規作成します(図2 ) 。
図2 新しいWebサイトの作成
デフォルトのページにコントロールを配置します。formタグ以下のソースコードは次のようになります。図3 のデザイナ画面も参考にして配置してください。このアプリケーションではTreeViewコントロールを使用してフォルダの階層を表し、フォルダやファイルの名前を表示します。
< form id = "form1" runat = "server" >
< div >
< asp:HyperLink ID = "ConsentHyperLink" runat = "server" Visible = "False" > Request Consent </ asp:HyperLink >
</ div >
< asp:Panel ID = "ResoucePanel" runat = "server" >
< asp:TreeView ID = "ResourceTreeView" runat = "server" ></ asp:TreeView >
< hr />
< div >
Folder: < asp:TextBox ID = "FolderNameTextBox" runat = "server" ></ asp:TextBox >
< asp:Button ID = "CreateButton" runat = "server" Text = "Create" />
< asp:Button ID = "RenameButton" runat = "server" Text = "Rename" />
< br />
File: < asp:FileUpload ID = "FileUpload" runat = "server" />
< asp:Button ID = "UploadButton" runat = "server" Text = "Upload" />
< br />
< asp:Button ID = "DeleteButton" runat = "server" Text = "Delete a Folder or File" />
</ div >
< div >
< asp:Literal ID = "MessageLiteral" runat = "server" ></ asp:Literal >
</ div >
</ asp:Panel >
</ form >
図3 デザイナ画面
続いて今回の本題とは関係ないところを実装してしまいましょう。このWebサイトへユーザーが初めてアクセスした際、まだユーザーデータへのアクセスはできません。最初に承認要求ページへのリンクを表示します。また、承認要求ページから応答があった場合、リソースのアクセスに必要になる承認トークンのdeltとlidパラメータをCookieに保存し、Cookieにデータがある場合は承認要求ページへのリンクを表示しません。
Default.aspx.vbにページの初期動作を次のように記述します。deltパラメータはこのアプリケーションの他の場所でも使用することになるのでString型の変数DelegationTokeに格納しています。コード中にあるParseメソッドやDecryptTokeメソッドについては本連載第14回 と15回 にて作成したメソッドです。
Protected DelegateionToken As String = ""
Protected Sub Page_Init(ByVal sender As Object , ByVal e As System.EventArgs) Handles Me .Init
If Request.Cookies("delt" ) IsNot Nothing AndAlso Request.Cookies("delt" ).Value <> "" Then
Me .DelegateionToken = Request.Cookies("delt" ).Value
ElseIf Request.Form("ConsentToken" ) <> "" Then
Dim consentToken As String = Request.Form("ConsentToken" )
Dim pairs As Specialized.NameValueCollection = Parse(HttpUtility.UrlDecode(consentToken))
If pairs("eact" ) <> "" Then
pairs = Parse(HttpUtility.UrlDecode(DecryptToken(pairs("eact" ), "*** Secret key ***" )))
End If
Dim expires As DateTime = New DateTime(1970 , 1 , 1 , 0 , 0 , 0 , DateTimeKind.Utc)
expires = expires.AddSeconds(CUInt(pairs("exp" ))).ToLocalTime()
If pairs("delt" ) <> "" Then
Response.Cookies("delt" ).Value = pairs("delt" )
Response.Cookies("delt" ).Expires = expires
Response.Cookies("lid" ).Value = pairs("lid" )
Response.Cookies("lid" ).Expires = expires
End If
Me .DelegateionToken = Request.Cookies("delt" ).Value
Else
Dim ru As String = HttpUtility.UrlEncode("http://***/" )
Dim pl As String = HttpUtility.UrlEncode("http://***/policy.html" )
Dim ps As String = "ApplicationStorage.ReadWrite"
Dim mkt As String = "ja-JP"
ConsentHyperLink.NavigateUrl = String .Format("https://consent.live.com/Delegation.aspx?ru={0}&ps={1}&pl={2}&mkt={3}" , ru, ps, pl, mkt)
ConsentHyperLink.Visible = True
ResoucePanel.Visible = False
End If
End Sub
リソースへのアクセスの基本
さて、Application Based Storage APIを利用してこれからユーザーデータにアクセスすることになりますが、その方法については前回にも書いたように次に示す手順になります。
まず、Windows Live委任認証により承認トークンを取得します。
承認トークンのユーザーデータの場所を表すlidパラメータを使用した以下の書式のURLへHTTPによりアクセスします。
https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage[/リソースへのパス]
※[LID]にはlidパラメータ、[/リソースへのパス]にはリソースへのパスを指定します。
上記アドレスへアクセスするときに認証用のヘッダを追加します。このときに必要になるのが承認トークンのdeltパラメータ(委任トークン)です。HTTPのヘッダは次のようになります。
[Method] /AtomApplicationStorage[/リソースへのパス] HTTP/1.1
Host: cumulus.services.live.com
User-Agent: [Name of User Agent]
Authorization: DelegatedToken dt="[委任トークン] "
Authorizationヘッダに「DelegatedToken dt="[委任トークン]"」という書式で指定します。Application Based Storage APIを利用したすべてのリソースアクセスにおいてこのヘッダ情報を付ける必要があります。[Method]にはGETやPOSTといったHTTPメソッド名が入ります。
これをVB.NETのコードで表すと次のようになります。Authorizationヘッダを追加したHttpWebRequestクラスを返すメソッドとしてあります。DelegateionTokenは先ほどのコードで作成した変数です。
Protected Function CreatedNewRequest(ByVal path As String , ByVal method As String ) As HttpWebRequest
Dim request As HttpWebRequest = DirectCast (WebRequest.Create(path), HttpWebRequest)
request.Method = method
request.Headers(HttpRequestHeader.Authorization) = String .Format("DelegatedToken dt=""{0}""" , Me .DelegateionToken)
Return request
End Function
次からこのメソッドを使用してリソースへアクセスします。
フォルダ・ファイル情報一覧の取得
フォルダやファイル情報の一覧を取得するにはGETメソッドを使います。アドレスのリソースパスには「/RootFolders」や「/Items」などのコレクションを表すパスを指定します。そうすると次のようなAtomプロトコルによるFeed形式のXMLが返ってきます。
<? xml version = "1.0" encoding = "utf-8" ?>
< feed xmlns = "http://www.w3.org/2005/Atom"
xmlns : AS = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns : AppStorage = "http://dev.live.com/AppStorage"
xmlns : Live = "LiveAtomBase:" >
...
< entry AS : type = "Folder" >
...
</ entry >
< entry AS : type = "Photo" >
...
</ entry >
< entry AS : type = "Document" >
...
</ entry >
</ feed >
<feed>要素内には指定したコレクション内にあるフォルダやファイルの数だけ<entry>要素が含まれています。
以下に<entry>要素を示します。<entry>要素にはAS:type属性があり、Folder・Photo・Documentのいずれかの値になっています。これによりアイテムの種類が判別できます。この属性以外にも<category>のtermやlabel属性も使用できそうです。またアイテムの名前は<title>要素にてわかります。
< entry AS : type = "Folder" >
< category scheme = "http://dev.live.com/AppStorage/scheme" term = "Folder" label = "Folder" />
< id > https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage/RootFolders(136)/Items('150F') </ id >
...
< title > Sample </ title >
...
< link href = "https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage/RootFolders(136)/Items('150F')/Items" rel = "related" type = "application/atom+xml;type=feed" title = "Items" />
...
</ entry >
<id>要素は、<entry>要素が示すアイテム自身へのリソースパスになっています。また、そのアイテムが持つコレクションへのパスはrel属性が"related"となっている<link>要素のhref属性をみることで取得できます。
以下のコードはアイテムの種類がPhotoの場合の<entry>要素内の一部です。画像や文書のアイテムにもrel=” related” の<link>要素がありパスが設定されています。画像の場合は「PhotoStreams」 、文書の場合は「DocumentStreams」というコレクションを表すパスになっています。画像や文書といったファイルとなるアイテムは階層の終端のように思いますが、ひとつのファイルの中にも複数のオブジェクトを持つもの、たとえばサムネイル画像や要約文書などを個別に指定するために用意されているものと思われます。
< link href = "https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage/RootFolders(136)/Items('173P')" rel = "edit" type = "application/atom+xml;type=entry" title = "Photo" />
< content type = "image/jpeg" src = "https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage/RootFolders(136)/Items('173P')/$value" length = "2857" />
< link href = "https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage/RootFolders(136)/Items('173P')/$value" rel = "edit-media" type = "image/jpeg" title = "PhotoStream" />
< link href = "https://cumulus.services.live.com/@C@[LID] /AtomApplicationStorage/RootFolders(136)/Items('173P')/PhotoStreams" rel = "related" type = "application/atom+xml;type=feed" title = "PhotoStreams" />
画像と文書のアイテムの場合、rel="edit-media"の<link>要素がありアイテム自身のバイナリデータを表すリソースへのパスが取得できます。その場合のパスの末尾は「$value」です。<content>要素にも同様の記述があります。
RootFoldersの表示
Feed形式のXMLを取得しWebページに表示してみましょう。TreeViewコントロールを使用して階層を表示します。最初にツリーの最上位ノードを「RootFolders」として、その直下にRootFoldersのフォルダを表示できるようにします。
ツリーのノードとなるTreeNodeクラスは、Webページ上に表示されるTextプロパティのほかに非表示のデータをValueプロパティに格納することができます。このValueプロパティにフォルダの場合はそのフォルダが持つコレクションを表すリソースへのURL文字列を設定しアプリケーションで利用することにします。
まずはツリーにRootFoldersを表示します。リソースへのパスはlidパラメータさえわかれば決定しますのでコードは次のようになります。Cookieに保存しておいたlidパラメータを使用してURL文字列を作り、TreeNodeオブジェクトのコンストラクタに渡しています。そして生成したTreeNodeオブジェクトをTreeVewに追加します。コードでは「folder.png」というフォルダを表す画像がDefault.aspxファイルと同階層に用意してあるものとします。
Protected Sub ResourceTreeView_Load(ByVal sender As Object , ByVal e As System.EventArgs) Handles ResourceTreeView.Load
If Me .DelegateionToken <> "" AndAlso ResourceTreeView.Nodes.Count = 0 Then
Dim rootNode As New TreeNode("RootFolders" , _
"https://cumulus.services.live.com/@C@" & Request.Cookies("lid" ).Value & "/AtomApplicationStorage/RootFolders" , _
"folder.png" )
ResourceTreeView.Nodes.Add(rootNode)
End If
End Sub
Feed要素の取得
次にWindows LiveサービスへアクセスしてXMLを取得するメソッドを作ります。メソッドはURL文字列を引数に受け取りXmlDocumentオブジェクトを返すものとしました。コード内では先に作成したCreateNewRequestメソッドを使用しています。リソースのパスの指定に誤りなどがあるとWebException例外がスローされるため、その場合はメッセージを<exception>タグで囲ったXmlDocumentとして返すようにしています。
Protected Function RetrieveResource(ByVal path As String ) As XmlDocument
Dim document As New XmlDocument
Dim request As HttpWebRequest = CreatedNewRequest(path, "GET" )
Try
Using response As HttpWebResponse = DirectCast (request.GetResponse, HttpWebResponse), _
reader As New System.IO.StreamReader(response.GetResponseStream, System.Text.Encoding.UTF8)
document.LoadXml(reader.ReadToEnd)
End Using
Return document
Catch webEx As WebException
document.InnerXml = "<exception>" & webEx.Message & "</exception>"
Return document
End Try
End Function
ツリーノードクリック時の処理
TreeVewコントロールのノードを選択するとSelectedNodeChangedイベントが発生します。このイベントを使用して作成したRetrieveResourceメソッドを呼ぶようにします。
XMLの操作にはXPathを使います。また、名前空間の指定のためにXmlNamespaceManagerを使用します。まず、_Defaultクラスに次のように変数を追加します。
Protected NamespaceManager As XmlNamespaceManager
続いてPage_Initメソッドに次のコードを追加してインスタンスを生成します。
NamespaceManager = New XmlNamespaceManager(New NameTable)
NamespaceManager.AddNamespace("metadata" , "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" )
NamespaceManager.AddNamespace("photos" , "http://dev.live.com/photos" )
NamespaceManager.AddNamespace("atom" , "http://www.w3.org/2005/Atom" )
NamespaceManager.AddNamespace("appstorage" , "http://dev.live.com/AppStorage" )
今回作成するアプリケーションでは、これらすべての名前空間を利用していませんが、改良時などの利便性のために書いています。
以下のコードがSelectedNodeChangedイベント時の処理になります。
Protected Sub ResourceTreeView_SelectedNodeChanged(ByVal sender As Object , ByVal e As System.EventArgs) Handles ResourceTreeView.SelectedNodeChanged
Dim selectedNode As TreeNode = ResourceTreeView.SelectedNode
selectedNode.ChildNodes.Clear()
Dim document As XmlDocument = RetrieveResource(selectedNode.Value)
If document.SelectNodes("/exception" ).Count > 0 Then
MessageLiteral.Text = document.SelectNodes("/exception" ).Item(0 ).InnerText
Exit Sub
End If
Dim nodeList As XmlNodeList = document.SelectNodes("/atom:feed/atom:entry" , NamespaceManager)
For Each node As XmlNode In nodeList
Dim url As String
url = node.SelectSingleNode("atom:link[@rel='related']" , NamespaceManager).Attributes("href" ).Value
If url.EndsWith("Streams" ) Then
url = node.SelectSingleNode("atom:link[@rel='edit-media']" , NamespaceManager).Attributes("href" ).Value
End If
Dim name As String = node.SelectSingleNode("atom:title" , NamespaceManager).FirstChild.Value
Dim child As New TreeNode(name, url)
Select Case node.Attributes(0 ).Value
Case "Folder"
child.ImageUrl = "folder.png"
Case "Document"
child.ImageUrl = "document.png"
Case "Photo"
child.ImageUrl = "photo.png"
End Select
child.ShowCheckBox = True
child.Checked = False
selectedNode.ChildNodes.Add(child)
Next
selectedNode.Expand()
End Sub
XMLの内容を解析しTreeNodeオブジェクトの各プロパティに値を設定しています。Valueプロパティには、フォルダの場合はコレクションへのパスを、画像・文書の場合はそのアイテムのバイナリデータへのパスを指定しています。フォルダ・画像・文書を表す画像は適当なものを用意してください。
ここまでで一度実行してみましょう。「 Request Consent」をクリックしてサインインします。初期状態でもRootFoldersにふたつのフォルダがあるので「RootFolders」ノードをクリックするとその下にフォルダが表示されると思います(図4 ) 。
図4 RootFoldersのフォルダの表示
フォルダの作成
フォルダの作成はPOSTメソッドを使います。リソースパスにはフォルダを作成する場所を示すコレクション(Items)を指定します。作成するフォルダの名前を指定するにはPOSTデータにAtomプロトコルによる<entry>要素を格納します。<entry>要素の書式は次のようになります。
< entry xmlns = "http://www.w3.org/2005/Atom" xmlns : LP = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns : LivePhotos = "http://dev.live.com/photos" LP : type = "Folder" >
< category scheme = "http://dev.live.com/AppStorage/scheme" term = "Folder" label = "Folder" />
< title > [フォルダ名] </ title >
</ entry >
<title>要素にフォルダ名を指定します。このとき日本語は使用できません。もともとユーザーに直接見せるものではないので不便はそうありませんが、指定したい場合はURLエンコードするなどしてアプリケーション側での対応が必要です。
フォルダに成功した場合はHTTPステータスコード201 (Created)が応答されます。
それでは、フォルダを作成するメソッドを作ります。コードは以下のとおりです。リソースパスのほかにファイル名を引数に受け取るようにしています。またフォルダ・ファイル情報一覧を取得したRetrieveResourceメソッドと同じようにXmlDocumentを返すようにしています。
Protected Function CreateFolder(ByVal path As String , ByVal folderName As String ) As XmlDocument
Dim document As New XmlDocument
Dim request As HttpWebRequest = CreatedNewRequest(path, "POST" )
Dim sb As New StringBuilder
sb.Append("<entry xmlns=""http://www.w3.org/2005/Atom"" xmlns:LP=""http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"" xmlns:LivePhotos=""http://dev.live.com/photos"" LP:type=""Folder"">" )
sb.Append("<category scheme=""http://dev.live.com/AppStorage/scheme"" term=""Folder"" label=""Folder"" />" )
sb.Append("<title>" & folderName & "</title>" )
sb.Append("</entry>" )
Dim buffer() As Byte = System.Text.Encoding.ASCII.GetBytes(sb.ToString)
request.ContentType = "application/atom+xml"
request.ContentLength = buffer.Length
Dim stream As System.IO.Stream = request.GetRequestStream
stream.Write(buffer, 0 , buffer.Length)
Try
Using response As HttpWebResponse = DirectCast (request.GetResponse, HttpWebResponse)
document.InnerXml = "<response>" & response.StatusCode & "</response>"
End Using
Return document
Catch webEx As WebException
document.InnerXml = "<exception>" & webEx.Message & "</exception>"
Return document
End Try
End Function
成功した場合は<response>要素にその内容を返すようにしていますが、今回のアプリケーションでは利用していません。また同じく今回は利用しませんでしたが、次のようにレスポンスのLocationヘッダを参照すると新しく作成したフォルダのパスがわかります。
Dim location As String = response.Headers(HttpResponseHeader.Location)
Createボタンをクリックしたときに、このメソッドを呼び出すようにします。CreateButtonのClickイベント処理を次のように記述します。
Protected Sub CreateButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles CreateButton.Click
If ResourceTreeView.CheckedNodes.Count <> 1 OrElse _
Not ResourceTreeView.CheckedNodes(0 ).Value.EndsWith("/Items" ) Then
MessageLiteral.Text = "フォルダをひとつチェックしてください。"
Exit Sub
End If
Dim document As XmlDocument = CreateFolder(ResourceTreeView.CheckedNodes(0 ).Value, FolderNameTextBox.Text)
If document.SelectNodes("/exception" ).Count > 0 Then
MessageLiteral.Text = document.SelectNodes("/exception" ).Item(0 ).InnerText
Else
ResourceTreeView.CheckedNodes(0 ).Select()
ResourceTreeView_SelectedNodeChanged(Me, New EventArgs)
End If
End Sub
フォルダを作成する場所の指定はツリーノードのチェックボックスを利用します。示したコードではフォルダを示すノードがひとつだけチェックされているかをまず確認しています。フォルダを示すノードかどうかはValueプロパティに設定したリソースパスの末尾をみて判別しています。フォルダ名はFolderNameTextBox.Textの値を使用します。
フォルダ名の変更
作成したフォルダ名を変更するにはPUTメソッドを使います。リクエスト方法は、フォルダ作成と同様に<entry>要素をPOSTデータに指定します。応答は成功した場合、ステータスコード204(No Content)が返ります。
リクエストするリソースのパスは名前を変更するフォルダへのパスを指定し、POSTデータの<title>属性には変更後のフォルダ名を指定します。
フォルダ名を変更するメソッドのコードは次のようになります。
Protected Function RenameFolder(ByVal path As String , ByVal folderName As String ) As XmlDocument
Dim document As New XmlDocument
Dim request As HttpWebRequest = CreatedNewRequest(path, "PUT" )
End Function
このメソッドをRenameボタンのClickイベントから呼び出しましょう。
Protected Sub RenameButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles RenameButton.Click
If ResourceTreeView.CheckedNodes.Count <> 1 Then
MessageLiteral.Text = "フォルダまたはファイルををひとつチェックしてください。"
Exit Sub
End If
Dim value As String = ResourceTreeView.CheckedNodes(0 ).Value
Dim path As String = value.Substring(0 , value.LastIndexOf("/" ))
Dim document As XmlDocument = RenameFolder(path, FolderNameTextBox.Text)
If document.SelectNodes("/exception" ).Count > 0 Then
MessageLiteral.Text = document.SelectNodes("/exception" ).Item(0 ).InnerText
Else
ResourceTreeView.CheckedNodes(0 ).Parent.Select()
ResourceTreeView_SelectedNodeChanged(Me, New EventArgs)
End If
End Sub
リソースへのパスはコレクションではなくフォルダを表すアイテムへのパスを指定する必要があります。TreeNode.Valueプロパティに指定していた値はコレクションへのノードパスまたはバイナリデータを示すものでしたので、末尾の「/Items」や「/$value」を削除してからメソッドに渡しています。
ちなみにApplication Based Storage APIではファイル名の変更はできません。
ファイルのアップロード
ファイルのアップロードにはフォルダ作成時と同じPOSTメソッドを使用します。フォルダ作成時にはPOSTデータにXMLを指定しましたが、ファイルアップロードの場合はファイルのバイナリデータを指定します。またリクエストのContentTypeをアップロードするファイルのContentTypeにします。アップロードされたときのファイル名を指定するにはslugヘッダを使用します。
ファイルをアップロードするメソッドは以下のように記述します。引数にアップロード先のフォルダを示すパス、ファイル名、バイナリデータとそのContentTypeの文字列を渡すようにしています。
Protected Function UploadFile(ByVal path As String , ByVal fileName As String , ByVal data() As Byte , ByVal contentType As String ) As XmlDocument
Dim document As New XmlDocument
Dim request As HttpWebRequest = CreatedNewRequest(path, "POST" )
request.Headers.Add("slug" , fileName)
request.ContentType = contentType.Replace("/pjpeg" , "/jpeg" )
request.ContentLength = data.Length
Dim stream As System.IO.Stream = request.GetRequestStream
stream.Write(data, 0 , data.Length)
Try
Using response As HttpWebResponse = DirectCast (request.GetResponse, HttpWebResponse)
document.InnerXml = "<response>" & response.StatusCode & "</response>"
End Using
Return document
Catch webEx As WebException
document.InnerXml = "<exception>" & webEx.Message & "</exception>"
Return document
End Try
End Function
アップロードできるファイルの種類はSDK文書には公開されていませんが、あまり多くないようです。画像の場合はJPEG、PNG、GIF形式のものを受け付けます。ファイルの種類は指定したContentTypeによって判別されます。JPEGの場合、ContentTypeを” image/pjpeg” と指定するとエラーレスポンスが返ります。” image/jpeg” としても問題ないためコード中では置換操作をしています。文書形式で対応しているものは、テキストファイル(text/plane)程度のようです。
上記のUploadFileメソッドをUploadボタンクリック時に呼び出し、FileUploadコントロールに指定されたファイルをアップロードするようにします。コードを以下に示します。
Protected Sub UploadButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles UploadButton.Click
If ResourceTreeView.CheckedNodes.Count <> 1 OrElse _
Not ResourceTreeView.CheckedNodes(0 ).Value.EndsWith("/Items" ) Then
MessageLiteral.Text = "フォルダをひとつチェックしてください。"
Exit Sub
ElseIf Not FileUpload.HasFile Then
MessageLiteral.Text = "アップロードするファイルを入力してください。"
Exit Sub
End If
Dim document As XmlDocument = UploadFile(ResourceTreeView.CheckedNodes(0 ).Value, _
HttpUtility.UrlEncode(FileUpload.FileName), _
FileUpload.FileBytes, _
FileUpload.PostedFile.ContentType)
If document.SelectNodes("/exception" ).Count > 0 Then
MessageLiteral.Text = document.SelectNodes("/exception" ).Item(0 ).InnerText
Else
ResourceTreeView.CheckedNodes(0 ).Select()
ResourceTreeView_SelectedNodeChanged(Me, New EventArgs)
End If
End Sub
アップロードするファイルのバイトデータはFileBytesプロパティから、ContentTypeはPostedFile.ContentTypeプロパティから取得できます。アップロードする際に付けるファイル名は指定されたファイル名をそのまま利用していますが、日本語は使用できないためURLエンコードしています。
フォルダ・ファイルの削除
フォルダ・ファイルの削除にはDELETEメソッドを使用します。一度のリクエストで削除できるのはひとつのフォルダまたはファイルだけです。フォルダを削除した場合、そのフォルダ以下のアイテムも削除されます。リソースへのパスはアイテムを表すパスを指定します。削除に成功した場合はステータスコード200(OK)が応答されます。
削除を行うコードは次のようになります。
Protected Function DeleteFoldoerOrFile(ByVal path As String ) As XmlDocument
Dim document As New XmlDocument
Dim request As HttpWebRequest = CreatedNewRequest(path, "DELETE" )
Try
Using response As HttpWebResponse = DirectCast (request.GetResponse, HttpWebResponse)
document.InnerXml = "<response>" & response.StatusCode & "</response>"
End Using
Return document
Catch webEx As WebException
document.InnerXml = "<exception>" & webEx.Message & "</exception>"
Return document
End Try
End Function
「Delete a Folder or File」ボタンをクリックしたときに、DeleteFoldoerOrFileメソッドを呼ぶようにします。
Protected Sub DeleteButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles DeleteButton.Click
If ResourceTreeView.CheckedNodes.Count <> 1 Then
MessageLiteral.Text = "フォルダまたはファイルををひとつチェックしてください。"
Exit Sub
End If
Dim value As String = ResourceTreeView.CheckedNodes(0 ).Value
Dim path As String = value.Substring(0 , value.LastIndexOf("/" ))
Dim document As XmlDocument = DeleteFoldoerOrFile(path)
If document.SelectNodes("/exception" ).Count > 0 Then
MessageLiteral.Text = document.SelectNodes("/exception" ).Item(0 ).InnerText
Else
ResourceTreeView.CheckedNodes(0 ).Parent.Select()
ResourceTreeView_SelectedNodeChanged(Me, New EventArgs)
End If
End Sub
フォルダ名変更のときと同様にTreeNode.Valueプロパティの値からアイテムのパスを作りメソッドに渡しています。
ファイルのダウンロード
最後はアップロードしたファイルのダウンロードについてです。ダウンロードはフォルダ・ファイル情報一覧の取得と同様にGETメソッドを使います。リソースへのパスにバイナリデータを示す末尾が「$value」のものを指定するとバイナリデータとして応答されます。
ダウンロードを行うメソッドは次のようになります。リクエストの応答で得られたバイナリデータをそのままWebサイトのレスポンスとして使用しています。
Public Function DownloadFile(ByVal path As String ) As XmlDocument
Dim document As New XmlDocument
Dim request As HttpWebRequest = CreatedNewRequest(path, "GET" )
Try
Using response As HttpWebResponse = DirectCast (request.GetResponse, HttpWebResponse), _
reader As New System.IO.BinaryReader(response.GetResponseStream)
Me .Response.ContentType = response.ContentType
Me .Response.BinaryWrite(reader.ReadBytes(CInt (response.ContentLength)))
Me .Response.End()
End Using
Return Nothing
Catch webEx As WebException
document.InnerXml = "<exception>" & webEx.Message & "</exception>"
Return document
End Try
End Function
ファイルを示すツリーノードをクリックした場合はDownloadFileメソッドを呼ぶようにResourceTreeView_SelectedNodeChangedメソッドを修正しましょう。メソッドの最初の部分を次のように変更します。
Dim selectedNode As TreeNode = ResourceTreeView.SelectedNode
If selectedNode.Value.EndsWith("/$value" ) Then
DownloadFile(selectedNode.Value)
Exit Sub
End If
TreeNode.Valueプロパティの末尾をみてファイルのツリーノードかどうかを判別しています。これでクリックしたときにそのファイルがダウンロードおよび表示されるようになります。DownloadFileメソッドは値を返すようにしていますが上のコードでは利用していません。
おわりに
以上で最初に示したWebアプリケーションが完成しました。ながながと書きましたが、HTTPメソッドによりほぼ共通した方法でユーザーデータの各操作ができることがわかります。作成したWebアプリケーションはUIの表示がくずれたり操作によっては例外が発生したりすることもあると思いますが、基本的な操作は確認できるのではないかと思います。このアプリケーションがApplication Based Storage APIの理解の手助けになれば幸いです。
次回も、まだもう少しApplication Based Storage APIを扱う予定です。