本記事の対象APIは既にサポートされていません。記事は参考程度にご利用ください。
はじめに
前回予告したとおり、
作成するWebアプリケーション
作成するWebアプリケーション

アプリケーションは以下のように操作するものとします。
フォルダをクリックするとそのフォルダ直下のフォルダとファイルを表示
ファイルをクリックするとそのファイルを表示
Folder欄にフォルダ名を入力した状態でCreateボタンをクリックすると、
チェックしているフォルダ直下にフォルダを作成 同様にRenameボタンをクリックした場合はチェックしているフォルダの名前を変更
File欄にファイルを入力した状態でUploadボタンをクリックすると、
チェックしたフォルダ直下にそのファイルをアップロード 「Delete a Folder or File」 ボタンをクリックするとチェックしているフォルダまたはファイルを削除
少し使いづらいものになっていますが、
開発環境
今回の開発には、
また、
Webサイトの作成
さっそく作成に取りかかりましょう。まずは適当な名前で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>

続いて今回の本題とは関係ないところを実装してしまいましょう。このWebサイトへユーザーが初めてアクセスした際、
Default.
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
' Cookie に委任トークンが格納されている場合
Me.DelegateionToken = Request.Cookies("delt").Value
ElseIf Request.Form("ConsentToken") <> "" Then
' POSTデータに承認トークンが格納されている場合
' 承認トークンから各パラメータ取得
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
' delt(委任トークン)とlidパラメータ値を Cookie に保存
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
リソースへのアクセスの基本
さて、
まず、
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ヘッダに
これをVB.
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メソッドを使います。アドレスのリソースパスには
<?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属性があり、
<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>要素は、
以下のコードはアイテムの種類がPhotoの場合の<entry>要素内の一部です。画像や文書のアイテムにもrel=”
<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" />
画像と文書のアイテムの場合、
RootFoldersの表示
Feed形式のXMLを取得しWebページに表示してみましょう。TreeViewコントロールを使用して階層を表示します。最初にツリーの最上位ノードを
ツリーのノードとなるTreeNodeクラスは、
まずはツリーにRootFoldersを表示します。リソースへのパスはlidパラメータさえわかれば決定しますのでコードは次のようになります。Cookieに保存しておいたlidパラメータを使用してURL文字列を作り、
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例外がスローされるため、
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を使います。また、
' Imports System.Xml
Protected NamespaceManager As XmlNamespaceManager
続いてPage_
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()
' Feed XMLの取得
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
' <entry>要素の取得
Dim nodeList As XmlNodeList = document.SelectNodes("/atom:feed/atom:entry", NamespaceManager)
For Each node As XmlNode In nodeList
' <link>要素からリソースパスの取得
Dim url As String
url = node.SelectSingleNode("atom:link[@rel='related']", NamespaceManager).Attributes("href").Value
' パスの末尾が「Streams」の場合、rel="edit-media"の<link>要素のパスを取得
If url.EndsWith("Streams") Then
url = node.SelectSingleNode("atom:link[@rel='edit-media']", NamespaceManager).Attributes("href").Value
End If
' <title>要素からアイテム名を取得
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プロパティには、
ここまでで一度実行してみましょう。

フォルダの作成
フォルダの作成はPOSTメソッドを使います。リソースパスにはフォルダを作成する場所を示すコレクション
<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>要素にフォルダ名を指定します。このとき日本語は使用できません。もともとユーザーに直接見せるものではないので不便はそうありませんが、
フォルダに成功した場合はHTTPステータスコード201 (Created)が応答されます。
それでは、
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>要素にその内容を返すようにしていますが、
Dim location As String = response.Headers(HttpResponseHeader.Location)
Createボタンをクリックしたときに、
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.
フォルダ名の変更
作成したフォルダ名を変更するにはPUTメソッドを使います。リクエスト方法は、
リクエストするリソースのパスは名前を変更するフォルダへのパスを指定し、
フォルダ名を変更するメソッドのコードは次のようになります。
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")
'(以下 CreateFolderメソッドと同じ)
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
' アイテムを示すパスに変換(末尾の/Itemsなど部分を削除)
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.
ちなみにApplication Based Storage APIではファイル名の変更はできません。
ファイルのアップロード
ファイルのアップロードにはフォルダ作成時と同じPOSTメソッドを使用します。フォルダ作成時にはPOSTデータにXMLを指定しましたが、
ファイルをアップロードするメソッドは以下のように記述します。引数にアップロード先のフォルダを示すパス、
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)
' ContentTypeを指定(image/pjpegは対応していないのでimage/jpegに置換している)
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文書には公開されていませんが、
上記のUploadFileメソッドをUploadボタンクリック時に呼び出し、
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プロパティから、
フォルダ・ファイルの削除
フォルダ・
削除を行うコードは次のようになります。
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」
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
' アイテムを示すパスに変換(末尾の/Itemsなど部分を削除)
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.
ファイルのダウンロード
最後はアップロードしたファイルのダウンロードについてです。ダウンロードはフォルダ・
ダウンロードを行うメソッドは次のようになります。リクエストの応答で得られたバイナリデータをそのまま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_
Dim selectedNode As TreeNode = ResourceTreeView.SelectedNode
If selectedNode.Value.EndsWith("/$value") Then
DownloadFile(selectedNode.Value)
Exit Sub
End If
TreeNode.
おわりに
以上で最初に示したWebアプリケーションが完成しました。ながながと書きましたが、
次回も、