使ってみよう! Windows Live SDK/API

第17回Windows Live Application Based Storage API(2)

本記事の対象APIは既にサポートされていません。記事は参考程度にご利用ください。

はじめに

前回予告したとおり、今回は実際にWebアプリケーションを作りながらApplication Based Storage APIを利用したユーザーデータへのアクセス方法をみていきます。本連載第14回のWindows Live委任認証から内容が続いていますので併せて参照してください。

作成するWebアプリケーション

作成するWebアプリケーション(Webサイト)は、図1のようなユーザーデータへのアクセスをGUI化した低機能なエクスプローラのような、単純なものを作ってみようと思います。

図1 作成するWebアプリケーション
図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サイトの作成
図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 デザイナ画面
図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
        ' 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

リソースへのアクセスの基本

さて、Application Based Storage APIを利用してこれからユーザーデータにアクセスすることになりますが、その方法については前回にも書いたように次に示す手順になります。

  1. まず、Windows Live委任認証により承認トークンを取得します。

  2. 承認トークンのユーザーデータの場所を表すlidパラメータを使用した以下の書式のURLへHTTPによりアクセスします。

    https://cumulus.services.live.com/@C@[LID]/AtomApplicationStorage[/リソースへのパス]

    ※[LID]にはlidパラメータ、[/リソースへのパス]にはリソースへのパスを指定します。

  3. 上記アドレスへアクセスするときに認証用のヘッダを追加します。このときに必要になるのが承認トークンの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クラスに次のように変数を追加します。

' Imports System.Xml
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()

    ' 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プロパティには、フォルダの場合はコレクションへのパスを、画像・文書の場合はそのアイテムのバイナリデータへのパスを指定しています。フォルダ・画像・文書を表す画像は適当なものを用意してください。

ここまでで一度実行してみましょう。⁠Request Consent」をクリックしてサインインします。初期状態でもRootFoldersにふたつのフォルダがあるので「RootFolders」ノードをクリックするとその下にフォルダが表示されると思います図4⁠。

図4 RootFoldersのフォルダの表示
図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")

    '(以下 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.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)

    ' 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文書には公開されていませんが、あまり多くないようです。画像の場合は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

    ' アイテムを示すパスに変換(末尾の/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.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を扱う予定です。

おすすめ記事

記事・ニュース一覧