この ガイド は、Azure ファイル API を実装する方法を示しています。 これらの手順により、Web サイトのユーザーまたは訪問者は、Azure に保存されている添付ファイルを表示、編集、作成、削除できるようになります。
注意
- Azure では最大 10 GB のファイルサイズがサポートされます。
- この例の手順に従って、列名を変更したり、別のテーブルを使用したりできます。
- これらのAPIは、すでに作成されているレコードに対してのみ機能します。 これらの API を使用してアップロードされた添付ファイルは、そのレコードにリンクされます。
- これらの API は、ファイルがチャンク単位でアップロードされると、ノート (注釈) エンティティに対して複数の更新を実行します。
前提条件
このウォークスルーは、File Test という名前のカスタムテーブルに依存しています。 同じ名前のカスタム テーブルを作成することも、別のテーブルを使用することもできます。 カスタム テーブルの作成方法については、Power Appsを使用したテーブルの作成と編集をご参照ください。
ステップ 1: サイト設定の作成
ポータル Azure File API を使用する前に、ポータル管理アプリで必要なサイト設定を有効にする必要があります。 サイト設定は、Azure File API を操作する場合に使用するテーブルによって異なります。
ポータル管理アプリを開きます。
ポータル管理アプリの左側のウィンドウで、サイト設定 を選択します。
新規を選択します。
名前には、Site/FileManagement/EnableWebAPIを入力します。
Web サイト リストで、Web サイト レコードを選択します。
値フィールドに、True と入力します。
保存して閉じるを選択します。
次のサイト設定を追加します:
サイト設定の名前 DefaultValue プロパティ Mandatory Site/FileManagement/EnableWebAPI ファイル管理 Web API を有効にします 可 Site/FileManagement/BlobStorageAccountName ストレージ アカウント名 可 Site/FileManagement/BlobStorageContainerName コンテナー名 可 Site/FileManagement/SupportedFileType ファイル タイプのコンマ区切り値。 例: .jpeg、.jpg 可 Site/FileManagement/SupportedMimeType ; 区切られた MIME タイプ 可 Site/FileManagement/MaxFileSize 1048576 (1 GB) 最大ファイル サイズ (KB 単位) いいえ Site/FileManagement/DownloadViaSASUri True (デフォルトで有効化) ファイルのダウンロードに SAS URI を使用するかどうかを決定する方法 いいえ Site/FileManagement/DownloadSASUriExpiryTimeSpan 00:10:00 (10 分) SAS URI の最大有効期限 (7 が true の場合に必要) いいえ Site/FileManagement/DownloadChunkSizeInKB 4096 (4 MB) ダウンロードするチャンク サイズを決定する方法 (7 が false の場合に必要) いいえ 注釈 (メモ) 用の Web API を有効にするには、次の追加のサイト設定を追加します
サイト設定の名前 価値 Webapi/annotation/enabled 正 Webapi/annotation/fields *
ステップ 2: アクセス許可の構成
ユーザーが Azure File API 機能を使用できるように、アクセス許可を構成する必要があります。 この例では、API を使用する新しい Web ロールを設定または作成する必要があります。 次に、ファイル テスト テーブルとメモ テーブルのテーブル権限を追加し、テーブル権限を Web ロールに関連付けます。 最後に、ユーザーが API を使用できるようにするために、Web ロールをユーザーに割り当てます。
注意
API は、認証されたユーザーまたは匿名 Web ロールの Web ロール コンテキストからのテーブル権限に従います。 ユーザーが、API に必要な Web サイト内の特定のテーブルにアクセスできる Web ロールを既に持っているかどうかを検討してください。 API を使用するためだけに追加の Web ロールを作成する必要はありません。
Web ロールを作成する
必要なテーブルへの権限を持つ Web ロールがない場合は、以下の手順を使用して Web ロールを作成し、テーブルの権限を割り当てます。
ポータル管理アプリを開きます。
左側のペインの セキュリティ セクションで、Web ロールを選択します。
新規をクリックします。
名前には、Azure File API User またはこの機能にアクセスするユーザーの役割を最もよく反映した名前を入力します。
Web サイト リストで、Web サイト レコードを選択します。
保存 を選びます。
テーブルのアクセス許可の作成
Power Pages デザイン スタジオを開きます。
セキュリティワークスペースを選択します。
保護 セクション配下で、テーブルのアクセス許可 を選択します。
新しいアクセス許可を選択します。
名前には、ファイル テスト テーブルの権限を入力します。
テーブル名 リストで、ファイル テスト (cr463_filetest) を選択します。
アクセスの種類リストで、グローバル アクセスを選択します。
特権の読み取り、追加を選択します。
+ ロールの追加を選択 し、先ほど作成した Web ロールを選択します。
保存 を選びます。
同様の手順で、Note (注釈) テーブルにWrite、Read、Create、Append権限を持つ権限をもうひとつ作成し、同じ Web ロールを追加します。
取引先担当者を Web ロールに追加する
ポータル管理アプリを開きます。
左側のペインの セキュリティ セクションで、取引先担当者を選択します。
この例の Web API で使用する取引先担当者を選択します。
注意
この連絡先は、Web API をテストするためにこの例で使用されるユーザー アカウントです。 必ずポータルで正しい連絡先を選択してください。
関連>Web ロール を選択します。 ポータルの取引先担当者フォームを使用していることを確認してください。 Power Pages Management アプリを使用する場合、Web ロール サブグリッドは「全般」タブの下部にあります。
既存の Web ロールの追加を選択します。
先ほど作成した Azure ファイル API ユーザー ロールを選択します。
追加を選択します。
保存して閉じるを選択します。
Microsoft Entra アプリにロール ベースの権限を追加する
ストレージ アカウントを作成した Azure にログインします。
ストレージ アカウントを含むリソース グループに移動します。
アクセス制御 (IAM)>追加>ロール割り当ての追加 を選択します。
リーダーの役割を選択し、次へを選択します。
ユーザー、グループ、またはサービス プリンシパルを選択し、+ メンバーの選択を選択します。
右側のパネルで、サイト名を検索してポータル エンタープライズ アプリケーションを選択し、選択ボタンを選択します。 アプリケーション名は以下の形式です:
Portals-<site name>
レビュー+ 割り当て>レビュー+ 割り当て を選択します。
ストレージ アカウントにアクセスし、アクセスの制御 (IAM)>追加>ロールの割り当て追加を選択します。
ストレージ BLOB データ共同作成者 ロールを選択し、次へを選択します。
ユーザー、グループ、またはサービス プリンシパルを選択し、+ メンバーの選択を選択します。
右側のパネルで、サイト名を検索してポータル エンタープライズ アプリケーションを選択し、選択ボタンを選択します。
レビュー+ 割り当て>レビュー+ 割り当て を選択します。
ステップ 3: Web ページの作成
Azure File API を有効にし、ユーザー権限を構成したので、File Test テーブルのエンティティ リストを含む Web ページを作成します。
Power Pages デザイン スタジオを開きます。
ページ ワークスペースで、+ ページを選択し、その他の方法でページを追加するを選択します。
ページの追加ダイアログで、ページ名に ファイルのテスト ページ と入力し、空白から始める を選択します。
追加を選択します。
リスト を選択し、ファイル テスト テーブルに新しいリストを追加するか、既存のリストを選択します。
以下のサンプル コードで添付ファイル用のページを作成し、レコードの表示、編集、作成、削除を行います。
Power Pages デザイン スタジオを開きます。
ページ ワークスペースで、+ ページを選択し、その他の方法でページを追加するを選択します。
ページの追加ダイアログで、ページ名に 添付 と入力し、空白から始める を選択します。
追加を選択します。
右上隅にあるコードの編集オプションを選択します。
Visual Studio Code を開く を選択します。
次のサンプル コード スニペットをコピーして、ページ セクションの
<div></div>
タグ間に貼り付けます。<style> .containerforfile { display:flex; margin-bottom:30px; } .btn-for-file { margin-right:10px; } .file-name { padding-top:6px; } .fileinput { margin-right:10px; } .container-progress { width: 70%; max-width: 400px; margin-top: 10px; position: relative; } .parent-progress { width: 100%; background-color: #2f5fef; height: 30px; margin-top: 25px; margin-bottom: 20px; } .child-progress { width: 0%; background-color: #53b453; height: 100%; } .prog { position: absolute; display: block; right: 0; } #attachments{ font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; width: 100%; } #attachments td, #attachments th { border: 1px solid #ddd; padding: 8px; } #attachments tr:nth-child(even) { background-color: #f2f2f2; } #attachments tr:hover { background-color: #ddd; } #attachments th { padding-top: 12px; padding-bottom: 12px; text-align: left; background-color: #2f5fef; color: white; } </style> <script> function selectFile() {var child = document.getElementsByClassName("child-progress")[0]; var progSpan = document.getElementsByClassName("prog")[0]; child.style.width = 0 + "%"; progSpan.innerHTML = 0 + "%"; var elementToChooseFile = document.getElementById("fileinput"); elementToChooseFile.click(); } function chooseFile() { var elementToChooseFile = document.getElementById("fileinput"); var filename =elementToChooseFile.files[0].name; var elementforfilename = document.getElementById("filename"); elementforfilename.innerText = filename; uploadFileinChunks(); } (function(webapi, $){ function safeAjax(ajaxOptions) { var deferredAjax = $.Deferred(); shell.getTokenDeferred().done(function (token) { // add headers for AJAX if (!ajaxOptions.headers) { $.extend(ajaxOptions, { headers: { "__RequestVerificationToken": token } }); } else { ajaxOptions.headers["__RequestVerificationToken"] = token; } $.ajax(ajaxOptions) .done(function(data, textStatus, jqXHR) { validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve); }).fail(deferredAjax.reject); //AJAX }).fail(function () { deferredAjax.rejectWith(this, arguments); // on token failure pass the token AJAX and args }); return deferredAjax.promise(); } webapi.safeAjax = safeAjax; })(window.webapi = window.webapi || {}, jQuery) function getFileName(fileName) { return fileName.replace(/\.azure\.txt$/, ''); } function downloadFile() { var entityName = document.getElementById("entity_name").value; var entityId = document.getElementById("entity_id").value; var url = "/_api/file/download/" + entityName + "(" + entityId + ")/blob/$value"; window.open(url, "_blank"); } function uploadFileinChunks() { var filesizeelement = document.getElementById("filesize"); var starttimelement = document.getElementById("starttime"); starttimelement.innerText = new Date().toLocaleString(); var endtimeelement = document.getElementById("endtime"); var entityName = "cr463_filetest"; var entityId = window.location.search.substring(4); var url = "/_api/file/InitializeUpload/" + entityName + "(" + entityId + ")/blob" var elementToChooseFile = document.getElementById("fileinput"); var filename = ""; if (elementToChooseFile.files.length > 0) { filename = elementToChooseFile.files[0].name; filesizeelement.innerText = elementToChooseFile.files[0].size / 1048576; const encodedFileName = encodeURIComponent(filename); filename = encodedFileName; } if (elementToChooseFile.files.length > 0 && elementToChooseFile.files[0].size > 0) { const chunkSize = 50*1024 *1024; let numberOfBlocks; let token; if (elementToChooseFile.files[0].size % chunkSize == 0) { numberOfBlocks = elementToChooseFile.files[0].size / chunkSize; } else { numberOfBlocks = parseInt(elementToChooseFile.files[0].size / chunkSize, 10) + 1; } webapi.safeAjax({ type: "POST", url: url,//replace this with url headers: { "x-ms-file-name": elementToChooseFile.files[0].name, "x-ms-file-size": elementToChooseFile.files[0].size }, contentType: "application/octet-stream", processData: false, data: {}, success: function (response, status, xhr) { token = response; uploadFileChunk(0); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest.responseText); } }); function uploadFileChunk(blockno) { var fileReader = new FileReader(); if (blockno < numberOfBlocks) { var end = (blockno * chunkSize + chunkSize) > elementToChooseFile.files[0].size ? blockno * chunkSize + elementToChooseFile.files[0].size % chunkSize : blockno * chunkSize + chunkSize; var content = elementToChooseFile.files[0].slice(blockno * chunkSize, end); fileReader.readAsArrayBuffer(content); } fileReader.onload = function () { webapi.safeAjax({ type: "PUT", url: "/_api/file/UploadBlock/blob?offset=" + (blockno * chunkSize) + "&fileSize=" + elementToChooseFile.files[0].size + "&chunkSize=" + chunkSize + "&token=" + token, headers: { "x-ms-file-name": elementToChooseFile.files[0].name }, contentType: "application/octet-stream", processData: false, data: content, success: function (res) { var child = document.getElementsByClassName("child-progress")[0]; var progSpan = document.getElementsByClassName("prog")[0]; var percentComplete = ((parseFloat(end) / parseFloat(elementToChooseFile.files[0].size)) * 100).toFixed(2); child.style.width = percentComplete + "%"; progSpan.innerHTML = percentComplete + "%"; if (percentComplete == 100) { var table = document.getElementById('attachments'); if(table.hidden) { var divForNoAttachment = document.getElementById("no-attachment-found"); divForNoAttachment.hidden = true; table.hidden = false; } var row = document.createElement("tr"); row.id = token; row.innerHTML=`<td><a target="_blank" href="/_api/file/download/annotation(` + token + `)/blob/$value" >` + elementToChooseFile.files[0].name + `</a></td> <td>`+ new Date().toLocaleString() + `</td> <td><button class="btn btn-default" onClick="deleteFile('` + token + `');"><i class="glyphicon glyphicon-trash" aria-hidden="true"></i></button></td>`; table.firstElementChild.nextElementSibling.appendChild(row); endtimeelement.innerText = new Date().toLocaleString(); } uploadFileChunk(blockno + 1); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest.responseText); } }); } } } else { alert("no file chosen"); } } function loadAllAttachments() { var fetchXmlQuery = `<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"> <entity name="annotation"> <attribute name="filename" /> <attribute name="annotationid" /> <attribute name="createdon"/> <attribute name="objectid" /> <attribute name="objecttypecode" /> <filter type="and"> <condition attribute="filename" operator="like" value="%.azure.txt" /> </filter> <link-entity name="cr463_filetest" from="cr463_filetestid" to="objectid" link-type="inner" alias="ad"> <filter type="and"> <condition attribute="cr463_filetestid" operator="eq" value="{` + window.location.search.substring(4) +`}"/> </filter> </link-entity> </entity> </fetch>`; var req = new XMLHttpRequest(); req.open("GET", "/_api/annotations?fetchXml=" + encodeURIComponent(fetchXmlQuery), true); req.setRequestHeader("Accept", "application/json"); req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); req.onreadystatechange = function () { if (this.readyState === 4) { this.onreadystatechange = null; if (this.status === 200) { var returned = JSON.parse(this.responseText); var results = returned.value; var loading = document.getElementById('loading'); if (results.length == 0) { var divForNoAttachment = document.getElementById("no-attachment-found"); divForNoAttachment.hidden = false; } else { for (var i = 0; i < results.length; i++) { var table = document.getElementById('attachments'); if(table.hidden) { var divForNoAttachment = document.getElementById("no-attachment-found"); divForNoAttachment.hidden = true; table.hidden = false; } var row = document.createElement("tr"); var fileName = results[i]["filename"]; fileName = fileName.replace(".azure.txt", ""); var createdOn = results[i]["createdon"]; var annotationid = results[i]["annotationid"]; row.id = annotationid; row.innerHTML=`<td><a target="_blank" href="/_api/file/download/annotation(` + annotationid + `)/blob/$value" >` + fileName + `</a></td> <td>`+ createdOn + `</td> <td><button class="btn btn-default" onClick="deleteFile('` + annotationid + `');"><i class="glyphicon glyphicon-trash" aria-hidden="true"></i></button></td>`; table.firstElementChild.nextElementSibling.appendChild(row); } } loading.hidden= true; } else { alert(this.status); } } }; req.send(); } document.addEventListener("DOMContentLoaded", function(){ loadAllAttachments(); }); function deleteFile(entityId) { var entityName = "annotation"; var url = "/_api/file/delete/" + entityName + "(" + entityId + ")/blob/$value"; webapi.safeAjax({ url: url, type: "DELETE", success: function(){ var row = document.getElementById(entityId); row.parentNode.removeChild(row); var table = document.getElementById('attachments'); if(table.hidden == false && table.tBodies[0].children.length == 0) { var divForNoAttachment = document.getElementById("no-attachment-found"); divForNoAttachment.hidden = false; table.hidden = true; } }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest.responseText); } }); } </script> <div style="margin-left:40px;"> <div class="containerforfile" style="display: flex;"> <input type="file" multiple="true" id="fileinput" onchange="chooseFile()" style="display: none;"> <button type="button" id="button-to-choosefile" onclick="selectFile()" class="btn btn-default btn-for-file">Choose File</button> <div id="filename" class="file-name">No File Selected</div> </div> <br> <div> <label for="filesize" id="file_size_label" class="field-label">FileSize(In MB): </label><div class="filesize" id="filesize"></div> <label for="starttime" id="start_time_label" class="field-label">StartTime:</label><div class="starttime" id="starttime"></div> <label for="endtime" id="end_time_label" class="field-label">EndTime:</label><div class="endtime" id="endtime"></div> </div> <div class="container-progress"> <div class="parent-progress" style="width: 100%;background-color: #c1c1c1; height: 30px; margin-top: 25px; margin-bottom: 20px;"> <div class="child-progress" style="width: 0%; background-color: #53b453; height: 100%;"></div> </div> <span class="prog">0%</span> </div> <br> <br> <h1>Attachments:</h1> <div id="loading"> Loading Attachments...</div> <div id="no-attachment-found" hidden>No Attachment Found!!</div> <table id="attachments" hidden> <thead> <tr> <th>File</th> <th>Created On</th> <th>Actions</th> </tr> </thead> <tbody> </tbody> </table> </div>
CTRL+S を選択して、コードを保存します。
ファイル テスト ページに戻り、リストを選択し、リストの編集を選択します。
アクション に移動し、レコードの編集を有効にします。
ターゲットの種類 で、Webpage を選択します。
Webpage で、添付 を選択します。
表示ラベルに 添付ファイルのアップロードと入力します。
完了を選択します。
デザイン スタジオの右上隅で、同期を選択して、コードの編集内容をサイトに適用します。
ステップ 4: API を使用して添付ファイルをアップロード、ダウンロード、削除します
Web API 機能をテストするには:
プレビューを選択してからデスクトップを選択します。
以前に作成した Azure ファイル API ユーザー ロールに割り当てられたユーザー アカウントでサイトにログインします。
以前作成したファイル テスト ページ の Web ページに移動します。
画面の右側から添付のアップロードを選択します。
ファイルを選択してアップロードします。
既存のファイルをダウンロードして削除してみてください。
添付ファイルをアップロード、ダウンロード、削除するサンプル付きの Web ページを作成したので、フォームとレイアウトをカスタマイズすることができます。
エラー コードとメッセージ
次の表には、Web API を使用して Azure にファイルをアップロードする際に発生する可能性のあるさまざまなエラー コードとメッセージが含まれています。
プロパティ | HTTP の状態 | エラー コード | エラー メッセージ |
---|---|---|---|
アップロードするファイルが添付されていません | 400 | FU00001 | ファイルの内容が指定されていません |
パラメータで指定されたエンティティ ID または名前が正しくありません | 404 | FU00002 | レコードが見つかりません |
ユーザーに権限が付与されていません | 403 | FU00003 | この操作を実行するためのアクセス許可がありません |
指定されたファイル拡張子が設定されていません | 400 | FU00004 | ファイル拡張子がサポートされていません。 |
ファイルの MIME がサポートされてません | 400 | FU00005 | ファイルの mimetype がサポートされてません |
ファイルサイズが構成されたサイズを超過しています | 400 | FU00006 | ファイル サイズが {0} MB を超えています |
Azure の構成が正しくありません | 400 | FU00007 | Azure の構成が正しくありません |
ファイル名が指定されていません | 400 | FU00008 | ヘッダー x-ms-file-name が要求にありません |
API が有効化されていません | 501 | FU00009 | Azure webapi が有効化されていないため使用できません |
更新/アップロードブロック/削除/ダウンロードに有効な Azure ファイルではありません | 400 | FU00010 | 要求されたファイルは {0} では利用できません |
ストレージ アカウントの Azure で IP 制限が有効になっています | 403 | FU00011 | IP 制限が有効になっています |
ダウンロードするファイルが Azure に存在しません | 400 | FU00012 | ファイルが存在しません |
チャンク サイズが100 MB を超えています | 400 | FU00013 | チャンク サイズが100 MB を超えています |
ファイル サイズが指定されていません | 400 | FU00014 | ファイル サイズが指定されていません |
サポートされるファイルの種類が設定されていません | 400 | FU00015 | ファイルの拡張子がサポートされていません |
サポートされる MIME が設定されていません | 400 | FU00016 | ファイル mimetype はサポートされていません |
チャンクなしで 128 MB を超過しています | 400 | FU00017 | ファイルサイズが 128MB を超えています |
Microsoft Entra アプリにアクセス許可が付与されていません | 403 | FU00018 | Microsoft Entra アプリケーションにこの操作を実行するアクセス許可がありません |
Azure との接続を作成するための証明書が存在しません | 400 | FU00019 | 証明書が存在しません |
アプリ設定でテナント ID、クライアント ID、またはサムプリントが設定されていない場合 | 500 | FU00020 | 内部サーバー エラー |
ファイルの MIME タイプ/拡張子がサポートされていません | 400 | FU00021 | ファイルの MIME タイプまたは拡張子がサポートされていません |
アカウント名またはコンテナ名が存在しないか、指定されていません | 400 | FU00022 | Azure の構成が正しくありません |